Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/early-bees-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools': patch
---

add featured section to marketplace
69 changes: 69 additions & 0 deletions packages/devtools/src/styles/use-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
52 changes: 52 additions & 0 deletions packages/devtools/src/tabs/marketplace/plugin-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ interface PluginSectionComponentProps {
onCardAction: (card: PluginCard) => void
}

const StarIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
)

const MailIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="4" width="20" height="16" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
)

export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
const styles = useStyles()

Expand All @@ -38,6 +63,33 @@ export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
</div>

<Show when={!props.isCollapsed()}>
<Show when={props.section.id === 'featured'}>
<div class={styles().pluginMarketplaceFeatureBanner}>
<div class={styles().pluginMarketplaceFeatureBannerContent}>
<h4 class={styles().pluginMarketplaceFeatureBannerTitle}>
<span class={styles().pluginMarketplaceFeatureBannerIcon}>
<StarIcon />
</span>
Want to be featured here?
</h4>
<p class={styles().pluginMarketplaceFeatureBannerText}>
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.
</p>
<a
href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"
class={styles().pluginMarketplaceFeatureBannerButton}
>
<span class={styles().pluginMarketplaceFeatureBannerButtonIcon}>
<MailIcon />
</span>
Contact Us
</a>
</div>
</div>
</Show>

<div class={styles().pluginMarketplaceGrid}>
<For each={props.section.cards}>
{(card) => (
Expand Down
36 changes: 22 additions & 14 deletions packages/devtools/src/tabs/marketplace/plugin-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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')
})

Expand All @@ -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)
})
})

Expand Down
29 changes: 14 additions & 15 deletions packages/devtools/src/tabs/marketplace/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ export const groupIntoSections = (
): Array<PluginSection> => {
const sections: Array<PluginSection> = []

// 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,
Expand All @@ -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) =>
Expand Down
Loading