diff --git a/.changeset/early-bees-wonder.md b/.changeset/early-bees-wonder.md new file mode 100644 index 00000000..19a85fa1 --- /dev/null +++ b/.changeset/early-bees-wonder.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools': patch +--- + +add featured section to marketplace diff --git a/packages/devtools/src/styles/use-styles.ts b/packages/devtools/src/styles/use-styles.ts index 95af69a6..b8fb32b0 100644 --- a/packages/devtools/src/styles/use-styles.ts +++ b/packages/devtools/src/styles/use-styles.ts @@ -1168,6 +1168,75 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => { text-transform: uppercase; letter-spacing: 0.05em; `, + pluginMarketplaceFeatureBanner: css` + margin-top: 1rem; + padding: 1.25rem 1.5rem; + background: ${t( + 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', + 'linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%)', + )}; + border-radius: 0.75rem; + border: 1px solid ${t(colors.blue[400], colors.blue[800])}; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); + `, + pluginMarketplaceFeatureBannerContent: css` + display: flex; + flex-direction: column; + gap: 0.75rem; + `, + pluginMarketplaceFeatureBannerTitle: css` + font-size: 1.125rem; + font-weight: 700; + color: white; + margin: 0; + display: flex; + align-items: center; + gap: 0.5rem; + `, + pluginMarketplaceFeatureBannerIcon: css` + width: 24px; + height: 24px; + display: inline-flex; + `, + pluginMarketplaceFeatureBannerText: css` + font-size: 0.95rem; + color: ${t('rgba(255, 255, 255, 0.95)', 'rgba(255, 255, 255, 0.9)')}; + line-height: 1.5; + margin: 0; + `, + pluginMarketplaceFeatureBannerButton: css` + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 1.25rem; + background: white; + color: ${colors.blue[600]}; + font-weight: 600; + font-size: 0.95rem; + border-radius: 0.5rem; + border: none; + cursor: pointer; + transition: all 0.2s ease; + text-decoration: none; + align-self: flex-start; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + &:hover { + background: ${t(colors.gray[50], colors.gray[100])}; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + } + + &:active { + transform: translateY(0); + } + `, + pluginMarketplaceFeatureBannerButtonIcon: css` + width: 18px; + height: 18px; + `, pluginMarketplaceCardDisabled: css` opacity: 0.6; filter: grayscale(0.3); diff --git a/packages/devtools/src/tabs/marketplace/plugin-section.tsx b/packages/devtools/src/tabs/marketplace/plugin-section.tsx index f0c90f20..053915dd 100644 --- a/packages/devtools/src/tabs/marketplace/plugin-section.tsx +++ b/packages/devtools/src/tabs/marketplace/plugin-section.tsx @@ -12,6 +12,31 @@ interface PluginSectionComponentProps { onCardAction: (card: PluginCard) => void } +const StarIcon = () => ( + + + +) + +const MailIcon = () => ( + + + + +) + export const PluginSectionComponent = (props: PluginSectionComponentProps) => { const styles = useStyles() @@ -38,6 +63,33 @@ export const PluginSectionComponent = (props: PluginSectionComponentProps) => { + +
+
+

+ + + + Want to be featured here? +

+

+ If you've built a plugin for TanStack Devtools and would like to + showcase it in the featured section, we'd love to hear from you! + Reach out to us to discuss partnership opportunities. +

+ + + + + Contact Us + +
+
+
+
{(card) => ( diff --git a/packages/devtools/src/tabs/marketplace/plugin-utils.test.ts b/packages/devtools/src/tabs/marketplace/plugin-utils.test.ts index 047201da..1d36585e 100644 --- a/packages/devtools/src/tabs/marketplace/plugin-utils.test.ts +++ b/packages/devtools/src/tabs/marketplace/plugin-utils.test.ts @@ -223,10 +223,11 @@ describe('groupIntoSections', () => { const sections = groupIntoSections(cards) - expect(sections).toHaveLength(1) - expect(sections[0]?.id).toBe('active') - expect(sections[0]?.displayName).toBe('✓ Active Plugins') - expect(sections[0]?.cards).toHaveLength(1) + expect(sections).toHaveLength(2) // Featured (always present) + Active + expect(sections[0]?.id).toBe('featured') + expect(sections[1]?.id).toBe('active') + expect(sections[1]?.displayName).toBe('✓ Active Plugins') + expect(sections[1]?.cards).toHaveLength(1) }) it('should group featured plugins', () => { @@ -266,9 +267,11 @@ describe('groupIntoSections', () => { const sections = groupIntoSections(cards) - expect(sections).toHaveLength(1) - expect(sections[0]?.id).toBe('active') - expect(sections.find((s) => s.id === 'featured')).toBeUndefined() + expect(sections).toHaveLength(2) // Featured (always present) + Active + expect(sections[0]?.id).toBe('featured') + expect(sections[1]?.id).toBe('active') + expect(sections[0]?.cards).toHaveLength(0) // Featured section empty + expect(sections[1]?.cards).toHaveLength(1) // Active has the plugin }) it('should group available plugins', () => { @@ -286,9 +289,10 @@ describe('groupIntoSections', () => { const sections = groupIntoSections(cards) - expect(sections).toHaveLength(1) - expect(sections[0]?.id).toBe('available') - expect(sections[0]?.displayName).toBe('Available Plugins') + expect(sections).toHaveLength(2) // Featured (always present) + Available + expect(sections[0]?.id).toBe('featured') + expect(sections[1]?.id).toBe('available') + expect(sections[1]?.displayName).toBe('Available Plugins') }) it('should not include featured plugins in available section', () => { @@ -345,8 +349,8 @@ describe('groupIntoSections', () => { const sections = groupIntoSections(cards) expect(sections).toHaveLength(3) - expect(sections[0]?.id).toBe('active') - expect(sections[1]?.id).toBe('featured') + expect(sections[0]?.id).toBe('featured') + expect(sections[1]?.id).toBe('active') expect(sections[2]?.id).toBe('available') }) @@ -365,12 +369,16 @@ describe('groupIntoSections', () => { const sections = groupIntoSections(cards) - expect(sections).toHaveLength(0) + expect(sections).toHaveLength(1) // Only featured section (always present, empty) + expect(sections[0]?.id).toBe('featured') + expect(sections[0]?.cards).toHaveLength(0) }) it('should handle empty card array', () => { const sections = groupIntoSections([]) - expect(sections).toHaveLength(0) + expect(sections).toHaveLength(1) // Featured section always present + expect(sections[0]?.id).toBe('featured') + expect(sections[0]?.cards).toHaveLength(0) }) }) diff --git a/packages/devtools/src/tabs/marketplace/plugin-utils.ts b/packages/devtools/src/tabs/marketplace/plugin-utils.ts index 5337ff7b..7dfcffd2 100644 --- a/packages/devtools/src/tabs/marketplace/plugin-utils.ts +++ b/packages/devtools/src/tabs/marketplace/plugin-utils.ts @@ -201,6 +201,20 @@ export const groupIntoSections = ( ): Array => { const sections: Array = [] + // Add Featured section first - always show this section + const featuredCards = allCards.filter( + (c) => + c.metadata?.featured && + c.actionType !== 'already-installed' && + c.isCurrentFramework, // Only show featured plugins for current framework + ) + // Always add featured section, even if no cards to show the partner banner + sections.push({ + id: 'featured', + displayName: '⭐ Featured', + cards: featuredCards, + }) + // Add Active Plugins section const activeCards = allCards.filter( (c) => c.actionType === 'already-installed' && c.isRegistered, @@ -213,21 +227,6 @@ export const groupIntoSections = ( }) } - // Add Featured section - const featuredCards = allCards.filter( - (c) => - c.metadata?.featured && - c.actionType !== 'already-installed' && - c.isCurrentFramework, // Only show featured plugins for current framework - ) - if (featuredCards.length > 0) { - sections.push({ - id: 'featured', - displayName: '⭐ Featured', - cards: featuredCards, - }) - } - // Add Available section - all plugins for current framework (TanStack + third-party) const availableCards = allCards.filter( (c) =>