Skip to content

Commit 93ec7e9

Browse files
authored
add featured section contact info (#238)
* add featured section contact info * changeset * fix tests
1 parent 8d4dd51 commit 93ec7e9

File tree

5 files changed

+162
-29
lines changed

5 files changed

+162
-29
lines changed

.changeset/early-bees-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools': patch
3+
---
4+
5+
add featured section to marketplace

packages/devtools/src/styles/use-styles.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,75 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
11681168
text-transform: uppercase;
11691169
letter-spacing: 0.05em;
11701170
`,
1171+
pluginMarketplaceFeatureBanner: css`
1172+
margin-top: 1rem;
1173+
padding: 1.25rem 1.5rem;
1174+
background: ${t(
1175+
'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
1176+
'linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%)',
1177+
)};
1178+
border-radius: 0.75rem;
1179+
border: 1px solid ${t(colors.blue[400], colors.blue[800])};
1180+
box-shadow:
1181+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
1182+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
1183+
`,
1184+
pluginMarketplaceFeatureBannerContent: css`
1185+
display: flex;
1186+
flex-direction: column;
1187+
gap: 0.75rem;
1188+
`,
1189+
pluginMarketplaceFeatureBannerTitle: css`
1190+
font-size: 1.125rem;
1191+
font-weight: 700;
1192+
color: white;
1193+
margin: 0;
1194+
display: flex;
1195+
align-items: center;
1196+
gap: 0.5rem;
1197+
`,
1198+
pluginMarketplaceFeatureBannerIcon: css`
1199+
width: 24px;
1200+
height: 24px;
1201+
display: inline-flex;
1202+
`,
1203+
pluginMarketplaceFeatureBannerText: css`
1204+
font-size: 0.95rem;
1205+
color: ${t('rgba(255, 255, 255, 0.95)', 'rgba(255, 255, 255, 0.9)')};
1206+
line-height: 1.5;
1207+
margin: 0;
1208+
`,
1209+
pluginMarketplaceFeatureBannerButton: css`
1210+
display: inline-flex;
1211+
align-items: center;
1212+
gap: 0.5rem;
1213+
padding: 0.625rem 1.25rem;
1214+
background: white;
1215+
color: ${colors.blue[600]};
1216+
font-weight: 600;
1217+
font-size: 0.95rem;
1218+
border-radius: 0.5rem;
1219+
border: none;
1220+
cursor: pointer;
1221+
transition: all 0.2s ease;
1222+
text-decoration: none;
1223+
align-self: flex-start;
1224+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1225+
1226+
&:hover {
1227+
background: ${t(colors.gray[50], colors.gray[100])};
1228+
transform: translateY(-1px);
1229+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
1230+
}
1231+
1232+
&:active {
1233+
transform: translateY(0);
1234+
}
1235+
`,
1236+
pluginMarketplaceFeatureBannerButtonIcon: css`
1237+
width: 18px;
1238+
height: 18px;
1239+
`,
11711240
pluginMarketplaceCardDisabled: css`
11721241
opacity: 0.6;
11731242
filter: grayscale(0.3);

packages/devtools/src/tabs/marketplace/plugin-section.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ interface PluginSectionComponentProps {
1212
onCardAction: (card: PluginCard) => void
1313
}
1414

15+
const StarIcon = () => (
16+
<svg
17+
xmlns="http://www.w3.org/2000/svg"
18+
viewBox="0 0 24 24"
19+
fill="currentColor"
20+
>
21+
<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" />
22+
</svg>
23+
)
24+
25+
const MailIcon = () => (
26+
<svg
27+
xmlns="http://www.w3.org/2000/svg"
28+
viewBox="0 0 24 24"
29+
fill="none"
30+
stroke="currentColor"
31+
stroke-width="2"
32+
stroke-linecap="round"
33+
stroke-linejoin="round"
34+
>
35+
<rect x="2" y="4" width="20" height="16" rx="2" />
36+
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
37+
</svg>
38+
)
39+
1540
export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
1641
const styles = useStyles()
1742

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

4065
<Show when={!props.isCollapsed()}>
66+
<Show when={props.section.id === 'featured'}>
67+
<div class={styles().pluginMarketplaceFeatureBanner}>
68+
<div class={styles().pluginMarketplaceFeatureBannerContent}>
69+
<h4 class={styles().pluginMarketplaceFeatureBannerTitle}>
70+
<span class={styles().pluginMarketplaceFeatureBannerIcon}>
71+
<StarIcon />
72+
</span>
73+
Want to be featured here?
74+
</h4>
75+
<p class={styles().pluginMarketplaceFeatureBannerText}>
76+
If you've built a plugin for TanStack Devtools and would like to
77+
showcase it in the featured section, we'd love to hear from you!
78+
Reach out to us to discuss partnership opportunities.
79+
</p>
80+
<a
81+
href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"
82+
class={styles().pluginMarketplaceFeatureBannerButton}
83+
>
84+
<span class={styles().pluginMarketplaceFeatureBannerButtonIcon}>
85+
<MailIcon />
86+
</span>
87+
Contact Us
88+
</a>
89+
</div>
90+
</div>
91+
</Show>
92+
4193
<div class={styles().pluginMarketplaceGrid}>
4294
<For each={props.section.cards}>
4395
{(card) => (

packages/devtools/src/tabs/marketplace/plugin-utils.test.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,11 @@ describe('groupIntoSections', () => {
223223

224224
const sections = groupIntoSections(cards)
225225

226-
expect(sections).toHaveLength(1)
227-
expect(sections[0]?.id).toBe('active')
228-
expect(sections[0]?.displayName).toBe('✓ Active Plugins')
229-
expect(sections[0]?.cards).toHaveLength(1)
226+
expect(sections).toHaveLength(2) // Featured (always present) + Active
227+
expect(sections[0]?.id).toBe('featured')
228+
expect(sections[1]?.id).toBe('active')
229+
expect(sections[1]?.displayName).toBe('✓ Active Plugins')
230+
expect(sections[1]?.cards).toHaveLength(1)
230231
})
231232

232233
it('should group featured plugins', () => {
@@ -266,9 +267,11 @@ describe('groupIntoSections', () => {
266267

267268
const sections = groupIntoSections(cards)
268269

269-
expect(sections).toHaveLength(1)
270-
expect(sections[0]?.id).toBe('active')
271-
expect(sections.find((s) => s.id === 'featured')).toBeUndefined()
270+
expect(sections).toHaveLength(2) // Featured (always present) + Active
271+
expect(sections[0]?.id).toBe('featured')
272+
expect(sections[1]?.id).toBe('active')
273+
expect(sections[0]?.cards).toHaveLength(0) // Featured section empty
274+
expect(sections[1]?.cards).toHaveLength(1) // Active has the plugin
272275
})
273276

274277
it('should group available plugins', () => {
@@ -286,9 +289,10 @@ describe('groupIntoSections', () => {
286289

287290
const sections = groupIntoSections(cards)
288291

289-
expect(sections).toHaveLength(1)
290-
expect(sections[0]?.id).toBe('available')
291-
expect(sections[0]?.displayName).toBe('Available Plugins')
292+
expect(sections).toHaveLength(2) // Featured (always present) + Available
293+
expect(sections[0]?.id).toBe('featured')
294+
expect(sections[1]?.id).toBe('available')
295+
expect(sections[1]?.displayName).toBe('Available Plugins')
292296
})
293297

294298
it('should not include featured plugins in available section', () => {
@@ -345,8 +349,8 @@ describe('groupIntoSections', () => {
345349
const sections = groupIntoSections(cards)
346350

347351
expect(sections).toHaveLength(3)
348-
expect(sections[0]?.id).toBe('active')
349-
expect(sections[1]?.id).toBe('featured')
352+
expect(sections[0]?.id).toBe('featured')
353+
expect(sections[1]?.id).toBe('active')
350354
expect(sections[2]?.id).toBe('available')
351355
})
352356

@@ -365,12 +369,16 @@ describe('groupIntoSections', () => {
365369

366370
const sections = groupIntoSections(cards)
367371

368-
expect(sections).toHaveLength(0)
372+
expect(sections).toHaveLength(1) // Only featured section (always present, empty)
373+
expect(sections[0]?.id).toBe('featured')
374+
expect(sections[0]?.cards).toHaveLength(0)
369375
})
370376

371377
it('should handle empty card array', () => {
372378
const sections = groupIntoSections([])
373-
expect(sections).toHaveLength(0)
379+
expect(sections).toHaveLength(1) // Featured section always present
380+
expect(sections[0]?.id).toBe('featured')
381+
expect(sections[0]?.cards).toHaveLength(0)
374382
})
375383
})
376384

packages/devtools/src/tabs/marketplace/plugin-utils.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,20 @@ export const groupIntoSections = (
201201
): Array<PluginSection> => {
202202
const sections: Array<PluginSection> = []
203203

204+
// Add Featured section first - always show this section
205+
const featuredCards = allCards.filter(
206+
(c) =>
207+
c.metadata?.featured &&
208+
c.actionType !== 'already-installed' &&
209+
c.isCurrentFramework, // Only show featured plugins for current framework
210+
)
211+
// Always add featured section, even if no cards to show the partner banner
212+
sections.push({
213+
id: 'featured',
214+
displayName: '⭐ Featured',
215+
cards: featuredCards,
216+
})
217+
204218
// Add Active Plugins section
205219
const activeCards = allCards.filter(
206220
(c) => c.actionType === 'already-installed' && c.isRegistered,
@@ -213,21 +227,6 @@ export const groupIntoSections = (
213227
})
214228
}
215229

216-
// Add Featured section
217-
const featuredCards = allCards.filter(
218-
(c) =>
219-
c.metadata?.featured &&
220-
c.actionType !== 'already-installed' &&
221-
c.isCurrentFramework, // Only show featured plugins for current framework
222-
)
223-
if (featuredCards.length > 0) {
224-
sections.push({
225-
id: 'featured',
226-
displayName: '⭐ Featured',
227-
cards: featuredCards,
228-
})
229-
}
230-
231230
// Add Available section - all plugins for current framework (TanStack + third-party)
232231
const availableCards = allCards.filter(
233232
(c) =>

0 commit comments

Comments
 (0)