Skip to content

Commit adad021

Browse files
feat: add new components (tag, button) and improve JsonTree (#47)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 1fd52ca commit adad021

File tree

8 files changed

+377
-93
lines changed

8 files changed

+377
-93
lines changed

.changeset/fancy-words-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools-ui': patch
3+
---
4+
5+
new ui components and enhancements for json tree

packages/devtools-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"build": "vite build"
5454
},
5555
"dependencies": {
56+
"clsx": "^2.1.1",
5657
"goober": "^2.1.16",
5758
"solid-js": "^1.9.7"
5859
},
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { splitProps } from 'solid-js'
2+
import clsx from 'clsx'
3+
import { useStyles } from '../styles/use-styles'
4+
import type { JSX } from 'solid-js'
5+
6+
export type ButtonVariant =
7+
| 'primary'
8+
| 'secondary'
9+
| 'danger'
10+
| 'success'
11+
| 'info'
12+
| 'warning'
13+
type ButtonProps = JSX.ButtonHTMLAttributes<HTMLButtonElement> & {
14+
variant?: ButtonVariant
15+
outline?: boolean
16+
ghost?: boolean
17+
children?: any
18+
className?: string
19+
}
20+
21+
export function Button(props: ButtonProps) {
22+
const styles = useStyles()
23+
const [local, rest] = splitProps(props, [
24+
'variant',
25+
'outline',
26+
'ghost',
27+
'children',
28+
'className',
29+
])
30+
const variant = local.variant || 'primary'
31+
const classes = clsx(
32+
styles().button.base,
33+
styles().button.variant(variant, local.outline, local.ghost),
34+
local.className,
35+
)
36+
37+
return (
38+
<button {...rest} class={classes}>
39+
{local.children}
40+
</button>
41+
)
42+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Show } from 'solid-js'
2+
import { useStyles } from '../styles/use-styles'
3+
import type { tokens } from '../styles/tokens'
4+
5+
export const Tag = (props: {
6+
color: keyof typeof tokens.colors
7+
label: string
8+
count?: number
9+
disabled?: boolean
10+
}) => {
11+
const styles = useStyles()
12+
return (
13+
<button disabled={props.disabled} class={styles().tag.base}>
14+
<span class={styles().tag.dot(props.color)} />
15+
<span class={styles().tag.label}>{props.label}</span>
16+
17+
<Show when={props.count && props.count > 0}>
18+
<span class={styles().tag.count}>{props.count}</span>
19+
</Show>
20+
</button>
21+
)
22+
}
Lines changed: 125 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { For } from 'solid-js'
1+
import { For, Show, createSignal } from 'solid-js'
2+
import clsx from 'clsx'
23
import { useStyles } from '../styles/use-styles'
34

45
export function JsonTree(props: { value: any }) {
@@ -16,119 +17,151 @@ function JsonValue(props: {
1617

1718
return (
1819
<span class={styles().tree.valueContainer(isRoot)}>
20+
{keyName && typeof value !== 'object' && !Array.isArray(value) && (
21+
<span class={styles().tree.valueKey}>&quot;{keyName}&quot;: </span>
22+
)}
1923
{(() => {
2024
if (typeof value === 'string') {
2125
return (
22-
<span>
23-
{keyName && (
24-
<span class={styles().tree.valueKey}>
25-
&quot;{keyName}&quot;:{' '}
26-
</span>
27-
)}
28-
<span class={styles().tree.valueString}>&quot;{value}&quot;</span>
29-
</span>
26+
<span class={styles().tree.valueString}>&quot;{value}&quot;</span>
3027
)
3128
}
3229
if (typeof value === 'number') {
33-
return (
34-
<span>
35-
{keyName && (
36-
<span class={styles().tree.valueKey}>
37-
&quot;{keyName}&quot;:{' '}
38-
</span>
39-
)}
40-
<span class={styles().tree.valueNumber}>{value}</span>
41-
</span>
42-
)
30+
return <span class={styles().tree.valueNumber}>{value}</span>
4331
}
4432
if (typeof value === 'boolean') {
45-
return (
46-
<span>
47-
{keyName && (
48-
<span class={styles().tree.valueKey}>
49-
&quot;{keyName}&quot;:{' '}
50-
</span>
51-
)}
52-
<span class={styles().tree.valueBoolean}>{String(value)}</span>
53-
</span>
54-
)
33+
return <span class={styles().tree.valueBoolean}>{String(value)}</span>
5534
}
5635
if (value === null) {
57-
return (
58-
<span>
59-
{keyName && (
60-
<span class={styles().tree.valueKey}>
61-
&quot;{keyName}&quot;:{' '}
62-
</span>
63-
)}
64-
<span class={styles().tree.valueNull}>null</span>
65-
</span>
66-
)
36+
return <span class={styles().tree.valueNull}>null</span>
6737
}
6838
if (value === undefined) {
39+
return <span class={styles().tree.valueNull}>undefined</span>
40+
}
41+
if (typeof value === 'function') {
6942
return (
70-
<span>
71-
{keyName && (
72-
<span class={styles().tree.valueKey}>
73-
&quot;{keyName}&quot;:{' '}
74-
</span>
75-
)}
76-
<span class={styles().tree.valueNull}>undefined</span>
77-
</span>
43+
<span class={styles().tree.valueFunction}>{String(value)}</span>
7844
)
7945
}
8046
if (Array.isArray(value)) {
81-
return (
82-
<span>
83-
{keyName && (
84-
<span class={styles().tree.valueKey}>
85-
&quot;{keyName}&quot;:{' '}
86-
</span>
87-
)}
88-
<span class={styles().tree.valueBraces}>[</span>
89-
<For each={value}>
90-
{(item, i) => {
91-
const isLastKey = i() === value.length - 1
92-
return (
93-
<>
94-
<JsonValue value={item} isLastKey={isLastKey} />
95-
</>
96-
)
97-
}}
98-
</For>
99-
<span class={styles().tree.valueBraces}>]</span>
100-
</span>
101-
)
47+
return <ArrayValue keyName={keyName} value={value} />
10248
}
10349
if (typeof value === 'object') {
104-
const keys = Object.keys(value)
105-
const lastKeyName = keys[keys.length - 1]
106-
return (
107-
<span>
108-
{keyName && (
109-
<span class={styles().tree.valueKey}>
110-
&quot;{keyName}&quot;:{' '}
111-
</span>
112-
)}
113-
<span class={styles().tree.valueBraces}>{'{'}</span>
114-
<For each={keys}>
115-
{(k) => (
116-
<>
117-
<JsonValue
118-
value={value[k]}
119-
keyName={k}
120-
isLastKey={lastKeyName === k}
121-
/>
122-
</>
123-
)}
124-
</For>
125-
<span class={styles().tree.valueBraces}>{'}'}</span>
126-
</span>
127-
)
50+
return <ObjectValue keyName={keyName} value={value} />
12851
}
12952
return <span />
13053
})()}
13154
{isLastKey || isRoot ? '' : <span>,</span>}
13255
</span>
13356
)
13457
}
58+
59+
const ArrayValue = ({
60+
value,
61+
keyName,
62+
}: {
63+
value: Array<any>
64+
keyName?: string
65+
}) => {
66+
const styles = useStyles()
67+
const [expanded, setExpanded] = createSignal(true)
68+
return (
69+
<span>
70+
{keyName && (
71+
<span
72+
onclick={(e) => {
73+
e.stopPropagation()
74+
e.stopImmediatePropagation()
75+
setExpanded(!expanded())
76+
}}
77+
class={clsx(styles().tree.valueKey, styles().tree.collapsible)}
78+
>
79+
&quot;{keyName}&quot;:{' '}
80+
</span>
81+
)}
82+
<span class={styles().tree.valueBraces}>[</span>
83+
<Show when={expanded()}>
84+
<For each={value}>
85+
{(item, i) => {
86+
const isLastKey = i() === value.length - 1
87+
return (
88+
<>
89+
<JsonValue value={item} isLastKey={isLastKey} />
90+
</>
91+
)
92+
}}
93+
</For>
94+
</Show>
95+
<Show when={!expanded()}>
96+
<span
97+
onClick={(e) => {
98+
e.stopPropagation()
99+
e.stopImmediatePropagation()
100+
setExpanded(!expanded())
101+
}}
102+
class={clsx(styles().tree.valueKey, styles().tree.collapsible)}
103+
>
104+
{`... ${value.length} more`}
105+
</span>
106+
</Show>
107+
<span class={styles().tree.valueBraces}>]</span>
108+
</span>
109+
)
110+
}
111+
112+
const ObjectValue = ({
113+
value,
114+
keyName,
115+
}: {
116+
value: Record<string, any>
117+
keyName?: string
118+
}) => {
119+
const styles = useStyles()
120+
const [expanded, setExpanded] = createSignal(true)
121+
const keys = Object.keys(value)
122+
const lastKeyName = keys[keys.length - 1]
123+
124+
return (
125+
<span>
126+
{keyName && (
127+
<span
128+
onClick={(e) => {
129+
e.stopPropagation()
130+
e.stopImmediatePropagation()
131+
setExpanded(!expanded())
132+
}}
133+
class={clsx(styles().tree.valueKey, styles().tree.collapsible)}
134+
>
135+
&quot;{keyName}&quot;:{' '}
136+
</span>
137+
)}
138+
<span class={styles().tree.valueBraces}>{'{'}</span>
139+
<Show when={expanded()}>
140+
<For each={keys}>
141+
{(k) => (
142+
<>
143+
<JsonValue
144+
value={value[k]}
145+
keyName={k}
146+
isLastKey={lastKeyName === k}
147+
/>
148+
</>
149+
)}
150+
</For>
151+
</Show>
152+
<Show when={!expanded()}>
153+
<span
154+
onClick={(e) => {
155+
e.stopPropagation()
156+
e.stopImmediatePropagation()
157+
setExpanded(!expanded())
158+
}}
159+
class={clsx(styles().tree.valueKey, styles().tree.collapsible)}
160+
>
161+
{`... ${keys.length} more`}
162+
</span>
163+
</Show>
164+
<span class={styles().tree.valueBraces}>{'}'}</span>
165+
</span>
166+
)
167+
}

packages/devtools-ui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export { Input } from './components/input'
33
export { Select } from './components/select'
44
export { TanStackLogo } from './components/logo'
55
export { JsonTree } from './components/tree'
6+
export { Button } from './components/button'
7+
export { Tag } from './components/tag'

0 commit comments

Comments
 (0)