@tanstack/devtools 0.6.20 → 0.6.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ getBadgeClass,
4
+ getBadgeText,
5
+ getButtonText,
6
+ getButtonVariant,
7
+ } from './card-utils'
8
+ import type { PluginCard } from './types'
9
+
10
+ const createMockCard = (overrides: Partial<PluginCard>): PluginCard => ({
11
+ requiredPackageName: '@tanstack/react-query',
12
+ devtoolsPackage: '@tanstack/react-query-devtools',
13
+ framework: 'react',
14
+ hasPackage: false,
15
+ hasDevtools: false,
16
+ isRegistered: false,
17
+ actionType: 'install',
18
+ status: 'idle',
19
+ isCurrentFramework: true,
20
+ metadata: {
21
+ packageName: '@tanstack/react-query-devtools',
22
+ title: 'Query Devtools',
23
+ framework: 'react',
24
+ },
25
+ ...overrides,
26
+ })
27
+
28
+ describe('getButtonText', () => {
29
+ it('should return Installing... when status is installing', () => {
30
+ const card = createMockCard({ status: 'installing' })
31
+ expect(getButtonText(card)).toBe('Installing...')
32
+ })
33
+
34
+ it('should return Installed! when status is success', () => {
35
+ const card = createMockCard({ status: 'success' })
36
+ expect(getButtonText(card)).toBe('Installed!')
37
+ })
38
+
39
+ it('should return Error when status is error', () => {
40
+ const card = createMockCard({ status: 'error' })
41
+ expect(getButtonText(card)).toBe('Error')
42
+ })
43
+
44
+ it('should return Install for install action', () => {
45
+ const card = createMockCard({ actionType: 'install' })
46
+ expect(getButtonText(card)).toBe('Install')
47
+ })
48
+
49
+ it('should return Install Devtools for install-devtools action', () => {
50
+ const card = createMockCard({ actionType: 'install-devtools' })
51
+ expect(getButtonText(card)).toBe('Install Devtools')
52
+ })
53
+
54
+ it('should return Add to Devtools for add-to-devtools action', () => {
55
+ const card = createMockCard({ actionType: 'add-to-devtools' })
56
+ expect(getButtonText(card)).toBe('Add to Devtools')
57
+ })
58
+
59
+ it('should return package name for requires-package action', () => {
60
+ const card = createMockCard({
61
+ actionType: 'requires-package',
62
+ requiredPackageName: '@tanstack/react-query',
63
+ })
64
+ expect(getButtonText(card)).toBe('Requires @tanstack/react-query')
65
+ })
66
+
67
+ it('should return Different Framework for wrong-framework action', () => {
68
+ const card = createMockCard({ actionType: 'wrong-framework' })
69
+ expect(getButtonText(card)).toBe('Different Framework')
70
+ })
71
+
72
+ it('should return Already Installed for already-installed action', () => {
73
+ const card = createMockCard({ actionType: 'already-installed' })
74
+ expect(getButtonText(card)).toBe('Already Installed')
75
+ })
76
+
77
+ it('should return Bump Version for bump-version action', () => {
78
+ const card = createMockCard({ actionType: 'bump-version' })
79
+ expect(getButtonText(card)).toBe('Bump Version')
80
+ })
81
+
82
+ it('should return Version Mismatch for version-mismatch action', () => {
83
+ const card = createMockCard({ actionType: 'version-mismatch' })
84
+ expect(getButtonText(card)).toBe('Version Mismatch')
85
+ })
86
+ })
87
+
88
+ describe('getButtonVariant', () => {
89
+ it('should return danger for requires-package', () => {
90
+ const card = createMockCard({ actionType: 'requires-package' })
91
+ expect(getButtonVariant(card)).toBe('danger')
92
+ })
93
+
94
+ it('should return danger for wrong-framework', () => {
95
+ const card = createMockCard({ actionType: 'wrong-framework' })
96
+ expect(getButtonVariant(card)).toBe('danger')
97
+ })
98
+
99
+ it('should return danger for version-mismatch', () => {
100
+ const card = createMockCard({ actionType: 'version-mismatch' })
101
+ expect(getButtonVariant(card)).toBe('danger')
102
+ })
103
+
104
+ it('should return warning for bump-version', () => {
105
+ const card = createMockCard({ actionType: 'bump-version' })
106
+ expect(getButtonVariant(card)).toBe('warning')
107
+ })
108
+
109
+ it('should return secondary for already-installed', () => {
110
+ const card = createMockCard({ actionType: 'already-installed' })
111
+ expect(getButtonVariant(card)).toBe('secondary')
112
+ })
113
+
114
+ it('should return primary for install actions', () => {
115
+ const installCard = createMockCard({ actionType: 'install' })
116
+ expect(getButtonVariant(installCard)).toBe('primary')
117
+
118
+ const installDevtoolsCard = createMockCard({
119
+ actionType: 'install-devtools',
120
+ })
121
+ expect(getButtonVariant(installDevtoolsCard)).toBe('primary')
122
+
123
+ const addToDevtoolsCard = createMockCard({ actionType: 'add-to-devtools' })
124
+ expect(getButtonVariant(addToDevtoolsCard)).toBe('primary')
125
+ })
126
+ })
127
+
128
+ describe('getBadgeClass', () => {
129
+ const mockStyles = () => ({
130
+ pluginMarketplaceCardBadge: 'badge',
131
+ pluginMarketplaceCardBadgeInstall: 'badge-install',
132
+ pluginMarketplaceCardBadgeAdd: 'badge-add',
133
+ pluginMarketplaceCardBadgeRequires: 'badge-requires',
134
+ })
135
+
136
+ it('should return install badge class for install', () => {
137
+ const card = createMockCard({ actionType: 'install' })
138
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-install')
139
+ })
140
+
141
+ it('should return install badge class for install-devtools', () => {
142
+ const card = createMockCard({ actionType: 'install-devtools' })
143
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-install')
144
+ })
145
+
146
+ it('should return add badge class for add-to-devtools', () => {
147
+ const card = createMockCard({ actionType: 'add-to-devtools' })
148
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-add')
149
+ })
150
+
151
+ it('should return add badge class for already-installed', () => {
152
+ const card = createMockCard({ actionType: 'already-installed' })
153
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-add')
154
+ })
155
+
156
+ it('should return requires badge class for bump-version', () => {
157
+ const card = createMockCard({ actionType: 'bump-version' })
158
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-requires')
159
+ })
160
+
161
+ it('should return requires badge class for version-mismatch', () => {
162
+ const card = createMockCard({ actionType: 'version-mismatch' })
163
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-requires')
164
+ })
165
+
166
+ it('should return requires badge class for requires-package', () => {
167
+ const card = createMockCard({ actionType: 'requires-package' })
168
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-requires')
169
+ })
170
+
171
+ it('should return requires badge class for wrong-framework', () => {
172
+ const card = createMockCard({ actionType: 'wrong-framework' })
173
+ expect(getBadgeClass(card, mockStyles)).toBe('badge badge-requires')
174
+ })
175
+ })
176
+
177
+ describe('getBadgeText', () => {
178
+ it('should return Available for install', () => {
179
+ const card = createMockCard({ actionType: 'install' })
180
+ expect(getBadgeText(card)).toBe('Available')
181
+ })
182
+
183
+ it('should return Available for install-devtools', () => {
184
+ const card = createMockCard({ actionType: 'install-devtools' })
185
+ expect(getBadgeText(card)).toBe('Available')
186
+ })
187
+
188
+ it('should return Installed for add-to-devtools', () => {
189
+ const card = createMockCard({ actionType: 'add-to-devtools' })
190
+ expect(getBadgeText(card)).toBe('Installed')
191
+ })
192
+
193
+ it('should return Active for already-installed', () => {
194
+ const card = createMockCard({ actionType: 'already-installed' })
195
+ expect(getBadgeText(card)).toBe('Active')
196
+ })
197
+
198
+ it('should return Incompatible for version-mismatch', () => {
199
+ const card = createMockCard({ actionType: 'version-mismatch' })
200
+ expect(getBadgeText(card)).toBe('Incompatible')
201
+ })
202
+
203
+ it('should return Unavailable for requires-package', () => {
204
+ const card = createMockCard({ actionType: 'requires-package' })
205
+ expect(getBadgeText(card)).toBe('Unavailable')
206
+ })
207
+
208
+ it('should return Other Framework for wrong-framework', () => {
209
+ const card = createMockCard({ actionType: 'wrong-framework' })
210
+ expect(getBadgeText(card)).toBe('Other Framework')
211
+ })
212
+
213
+ it('should return empty string for unknown action type', () => {
214
+ const card = createMockCard({ actionType: 'install' })
215
+ // @ts-expect-error - testing invalid action type
216
+ card.actionType = 'invalid'
217
+ expect(getBadgeText(card)).toBe('')
218
+ })
219
+ })
@@ -0,0 +1,85 @@
1
+ import type { PluginCard } from './types'
2
+
3
+ export const getButtonText = (card: PluginCard): string => {
4
+ if (card.status === 'installing') return 'Installing...'
5
+ if (card.status === 'success') return 'Installed!'
6
+ if (card.status === 'error') return 'Error'
7
+
8
+ switch (card.actionType) {
9
+ case 'install':
10
+ return 'Install'
11
+ case 'install-devtools':
12
+ return 'Install Devtools'
13
+ case 'add-to-devtools':
14
+ return 'Add to Devtools'
15
+ case 'requires-package':
16
+ return `Requires ${card.requiredPackageName}`
17
+ case 'wrong-framework':
18
+ return 'Different Framework'
19
+ case 'already-installed':
20
+ return 'Already Installed'
21
+ case 'bump-version':
22
+ return 'Bump Version'
23
+ case 'version-mismatch':
24
+ return 'Version Mismatch'
25
+ default:
26
+ return 'Install'
27
+ }
28
+ }
29
+
30
+ export const getButtonVariant = (
31
+ card: PluginCard,
32
+ ): 'primary' | 'secondary' | 'danger' | 'warning' => {
33
+ if (
34
+ card.actionType === 'requires-package' ||
35
+ card.actionType === 'wrong-framework' ||
36
+ card.actionType === 'version-mismatch'
37
+ )
38
+ return 'danger'
39
+ if (card.actionType === 'bump-version') return 'warning'
40
+ if (card.actionType === 'already-installed') return 'secondary'
41
+ return 'primary'
42
+ }
43
+
44
+ export const getBadgeClass = (card: PluginCard, styles: any): string => {
45
+ const s = styles()
46
+ const base = s.pluginMarketplaceCardBadge
47
+ switch (card.actionType) {
48
+ case 'install':
49
+ case 'install-devtools':
50
+ return `${base} ${s.pluginMarketplaceCardBadgeInstall}`
51
+ case 'add-to-devtools':
52
+ return `${base} ${s.pluginMarketplaceCardBadgeAdd}`
53
+ case 'already-installed':
54
+ return `${base} ${s.pluginMarketplaceCardBadgeAdd}`
55
+ case 'bump-version':
56
+ return `${base} ${s.pluginMarketplaceCardBadgeRequires}`
57
+ case 'version-mismatch':
58
+ return `${base} ${s.pluginMarketplaceCardBadgeRequires}`
59
+ case 'requires-package':
60
+ case 'wrong-framework':
61
+ return `${base} ${s.pluginMarketplaceCardBadgeRequires}`
62
+ default:
63
+ return base
64
+ }
65
+ }
66
+
67
+ export const getBadgeText = (card: PluginCard): string => {
68
+ switch (card.actionType) {
69
+ case 'install':
70
+ case 'install-devtools':
71
+ return 'Available'
72
+ case 'add-to-devtools':
73
+ return 'Installed'
74
+ case 'already-installed':
75
+ return 'Active'
76
+ case 'version-mismatch':
77
+ return 'Incompatible'
78
+ case 'requires-package':
79
+ return 'Unavailable'
80
+ case 'wrong-framework':
81
+ return 'Other Framework'
82
+ default:
83
+ return ''
84
+ }
85
+ }
@@ -0,0 +1,54 @@
1
+ import { SearchIcon, SettingsIcon } from '@tanstack/devtools-ui'
2
+ import { useStyles } from '../../styles/use-styles'
3
+ import { TagFilters } from './tag-filters'
4
+ import type { Accessor } from 'solid-js'
5
+
6
+ interface MarketplaceHeaderProps {
7
+ searchInput: Accessor<string>
8
+ onSearchInput: (value: string) => void
9
+ onSettingsClick: () => void
10
+ tags: Accessor<Array<string>>
11
+ selectedTags: Accessor<Set<string>>
12
+ onToggleTag: (tag: string) => void
13
+ }
14
+
15
+ export const MarketplaceHeader = (props: MarketplaceHeaderProps) => {
16
+ const styles = useStyles()
17
+
18
+ return (
19
+ <div class={styles().pluginMarketplaceHeader}>
20
+ <div class={styles().pluginMarketplaceTitleRow}>
21
+ <h2 class={styles().pluginMarketplaceTitle}>Plugin Marketplace</h2>
22
+ <div style={{ display: 'flex', 'align-items': 'center' }}>
23
+ <div class={styles().pluginMarketplaceSearchWrapper}>
24
+ <SearchIcon />
25
+ <input
26
+ type="text"
27
+ class={styles().pluginMarketplaceSearch}
28
+ placeholder="Search plugins..."
29
+ value={props.searchInput()}
30
+ onInput={(e) => props.onSearchInput(e.currentTarget.value)}
31
+ />
32
+ </div>
33
+ <button
34
+ class={styles().pluginMarketplaceSettingsButton}
35
+ onClick={props.onSettingsClick}
36
+ >
37
+ <SettingsIcon />
38
+ </button>
39
+ </div>
40
+ </div>
41
+
42
+ <p class={styles().pluginMarketplaceDescription}>
43
+ Discover and install devtools for TanStack Query, Router, Form, and
44
+ Pacer
45
+ </p>
46
+
47
+ <TagFilters
48
+ tags={props.tags}
49
+ selectedTags={props.selectedTags}
50
+ onToggleTag={props.onToggleTag}
51
+ />
52
+ </div>
53
+ )
54
+ }
@@ -0,0 +1,165 @@
1
+ import { For, Show } from 'solid-js'
2
+ import {
3
+ Button,
4
+ CheckCircleIcon,
5
+ ExternalLinkIcon,
6
+ PackageIcon,
7
+ XCircleIcon,
8
+ } from '@tanstack/devtools-ui'
9
+ import { useStyles } from '../../styles/use-styles'
10
+ import {
11
+ getBadgeClass,
12
+ getBadgeText,
13
+ getButtonText,
14
+ getButtonVariant,
15
+ } from './card-utils'
16
+ import type { PluginCard } from './types'
17
+
18
+ interface PluginCardComponentProps {
19
+ card: PluginCard
20
+ onAction: (card: PluginCard) => void
21
+ }
22
+
23
+ export const PluginCardComponent = (props: PluginCardComponentProps) => {
24
+ const styles = useStyles()
25
+ const { card } = props
26
+
27
+ return (
28
+ <div
29
+ class={styles().pluginMarketplaceCard}
30
+ classList={{
31
+ [styles().pluginMarketplaceCardDisabled]:
32
+ !card.isCurrentFramework && card.actionType !== 'already-installed',
33
+ [styles().pluginMarketplaceCardFeatured]:
34
+ !!card.metadata?.featured && card.actionType !== 'already-installed',
35
+ [styles().pluginMarketplaceCardActive]:
36
+ card.actionType === 'already-installed',
37
+ }}
38
+ style={{ position: 'relative' }}
39
+ >
40
+ {/* New Banner */}
41
+ <Show when={card.metadata?.isNew}>
42
+ <div class={styles().pluginMarketplaceNewBanner}>New</div>
43
+ </Show>
44
+
45
+ <span class={getBadgeClass(card, styles)}>{getBadgeText(card)}</span>
46
+ <div
47
+ class={styles().pluginMarketplaceCardIcon}
48
+ classList={{
49
+ 'custom-logo': !!card.metadata?.logoUrl,
50
+ }}
51
+ >
52
+ <Show when={card.metadata?.logoUrl} fallback={<PackageIcon />}>
53
+ <img
54
+ src={card.metadata?.logoUrl}
55
+ alt={card.metadata?.title || card.devtoolsPackage}
56
+ class={styles().pluginMarketplaceCardImage}
57
+ />
58
+ </Show>
59
+ </div>
60
+ <div class={styles().pluginMarketplaceCardHeader}>
61
+ <h3 class={styles().pluginMarketplaceCardTitle}>
62
+ {card.metadata?.title || card.devtoolsPackage}
63
+ </h3>
64
+ <p class={styles().pluginMarketplaceCardPackageBadge}>
65
+ {card.devtoolsPackage}
66
+ </p>
67
+ <p class={styles().pluginMarketplaceCardDescriptionText}>
68
+ {card.actionType === 'requires-package'
69
+ ? `Requires ${card.requiredPackageName}`
70
+ : card.actionType === 'wrong-framework'
71
+ ? `For different framework projects`
72
+ : card.actionType === 'already-installed'
73
+ ? `Active in your devtools`
74
+ : card.actionType === 'version-mismatch'
75
+ ? card.versionInfo?.reason || 'Version incompatible'
76
+ : card.metadata?.description ||
77
+ `For ${card.requiredPackageName}`}
78
+ </p>
79
+ <Show when={card.versionInfo}>
80
+ <p class={styles().pluginMarketplaceCardVersionInfo}>
81
+ <Show
82
+ when={card.versionInfo?.satisfied}
83
+ fallback={
84
+ <span class={styles().pluginMarketplaceCardVersionUnsatisfied}>
85
+ ⚠️ v{card.versionInfo?.current} • Requires v
86
+ {card.versionInfo?.required}+
87
+ </span>
88
+ }
89
+ >
90
+ <span class={styles().pluginMarketplaceCardVersionSatisfied}>
91
+ ✓ v{card.versionInfo?.current} • Min v
92
+ {card.versionInfo?.required}
93
+ </span>
94
+ </Show>
95
+ </p>
96
+ </Show>
97
+ <Show when={card.metadata?.docsUrl}>
98
+ <a
99
+ href={card.metadata?.docsUrl}
100
+ target="_blank"
101
+ rel="noopener noreferrer"
102
+ class={styles().pluginMarketplaceCardDocsLink}
103
+ >
104
+ Documentation <ExternalLinkIcon />
105
+ </a>
106
+ </Show>
107
+
108
+ {/* Tags */}
109
+ <Show when={card.metadata?.tags && card.metadata.tags.length > 0}>
110
+ <div class={styles().pluginMarketplaceCardTags}>
111
+ <For each={card.metadata?.tags}>
112
+ {(tag) => (
113
+ <span class={styles().pluginMarketplaceCardTag}>{tag}</span>
114
+ )}
115
+ </For>
116
+ </div>
117
+ </Show>
118
+ </div>
119
+ <Show
120
+ when={card.status === 'idle'}
121
+ fallback={
122
+ <div class={styles().pluginMarketplaceCardStatus}>
123
+ <Show when={card.status === 'installing'}>
124
+ <div class={styles().pluginMarketplaceCardSpinner} />
125
+ <span class={styles().pluginMarketplaceCardStatusText}>
126
+ Installing...
127
+ </span>
128
+ </Show>
129
+ <Show when={card.status === 'success'}>
130
+ <CheckCircleIcon />
131
+ <span class={styles().pluginMarketplaceCardStatusText}>
132
+ Installed!
133
+ </span>
134
+ </Show>
135
+ <Show when={card.status === 'error'}>
136
+ <XCircleIcon />
137
+ <span class={styles().pluginMarketplaceCardStatusTextError}>
138
+ {card.error || 'Failed to install'}
139
+ </span>
140
+ </Show>
141
+ </div>
142
+ }
143
+ >
144
+ <Button
145
+ variant={getButtonVariant(card)}
146
+ onClick={() => props.onAction(card)}
147
+ disabled={
148
+ card.status !== 'idle' ||
149
+ card.actionType === 'requires-package' ||
150
+ card.actionType === 'wrong-framework' ||
151
+ card.actionType === 'already-installed' ||
152
+ card.actionType === 'version-mismatch'
153
+ }
154
+ class={
155
+ card.actionType === 'already-installed'
156
+ ? styles().pluginMarketplaceButtonInstalled
157
+ : ''
158
+ }
159
+ >
160
+ {getButtonText(card)}
161
+ </Button>
162
+ </Show>
163
+ </div>
164
+ )
165
+ }
@@ -0,0 +1,51 @@
1
+ import { For, Show } from 'solid-js'
2
+ import { ChevronDownIcon } from '@tanstack/devtools-ui'
3
+ import { useStyles } from '../../styles/use-styles'
4
+ import { PluginCardComponent } from './plugin-card'
5
+ import type { Accessor } from 'solid-js'
6
+ import type { PluginCard, PluginSection } from './types'
7
+
8
+ interface PluginSectionComponentProps {
9
+ section: PluginSection
10
+ isCollapsed: Accessor<boolean>
11
+ onToggleCollapse: () => void
12
+ onCardAction: (card: PluginCard) => void
13
+ }
14
+
15
+ export const PluginSectionComponent = (props: PluginSectionComponentProps) => {
16
+ const styles = useStyles()
17
+
18
+ return (
19
+ <div class={styles().pluginMarketplaceSection}>
20
+ <div
21
+ class={styles().pluginMarketplaceSectionHeader}
22
+ onClick={props.onToggleCollapse}
23
+ >
24
+ <div class={styles().pluginMarketplaceSectionHeaderLeft}>
25
+ <div
26
+ class={styles().pluginMarketplaceSectionChevron}
27
+ classList={{
28
+ [styles().pluginMarketplaceSectionChevronCollapsed]:
29
+ props.isCollapsed(),
30
+ }}
31
+ >
32
+ <ChevronDownIcon />
33
+ </div>
34
+ <h3 class={styles().pluginMarketplaceSectionTitle}>
35
+ {props.section.displayName}
36
+ </h3>
37
+ </div>
38
+ </div>
39
+
40
+ <Show when={!props.isCollapsed()}>
41
+ <div class={styles().pluginMarketplaceGrid}>
42
+ <For each={props.section.cards}>
43
+ {(card) => (
44
+ <PluginCardComponent card={card} onAction={props.onCardAction} />
45
+ )}
46
+ </For>
47
+ </div>
48
+ </Show>
49
+ </div>
50
+ )
51
+ }