@gentleduck/registry-ui 0.2.4 → 0.2.6
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.
- package/.turbo/turbo-check-types.log +1 -0
- package/.turbo/turbo-test.log +23 -0
- package/CHANGELOG.md +27 -0
- package/package.json +3 -2
- package/src/_old/_table/index.ts +9 -0
- package/src/_old/_table/table.tsx +7 -7
- package/src/_old/_upload/index.ts +13 -0
- package/src/_old/_upload/upload-sonner.tsx +1 -1
- package/src/audio/audio-visualizer.tsx +26 -2
- package/src/audio/audio.types.ts +1 -2
- package/src/button/__test__/button.test.tsx +80 -0
- package/src/button/button.tsx +1 -1
- package/src/button-group/button-group.tsx +1 -0
- package/src/carousel/carousel.tsx +1 -0
- package/src/chart/__test__/chart.test.tsx +40 -0
- package/src/chart/chart.tsx +5 -1
- package/src/checkbox/checkbox.tsx +1 -0
- package/src/collapsible/collapsible.tsx +2 -1
- package/src/command/command.tsx +8 -2
- package/src/dialog/dialog-responsive.tsx +3 -1
- package/src/field/field.tsx +2 -0
- package/src/input-group/input-group.tsx +3 -0
- package/src/item/item.tsx +1 -0
- package/src/json-editor/json-editor.tsx +58 -60
- package/src/label/label.tsx +1 -0
- package/src/preview-panel/preview-panel.tsx +2 -2
- package/src/separator/separator.tsx +0 -1
- package/src/sidebar/sidebar.tsx +2 -2
- package/src/slider/slider.tsx +1 -0
- package/src/sonner/sonner.chunks.tsx +1 -0
- package/src/switch/switch.tsx +1 -0
- package/src/tabs/tabs.tsx +2 -2
- package/tsconfig.json +11 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ tsc -p tsconfig.json --noEmit --pretty false --skipLibCheck
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
$ bun test
|
|
2
|
+
bun test v1.3.5 (1e86cebd)
|
|
3
|
+
|
|
4
|
+
::group::src/chart/__test__/chart.test.tsx:
|
|
5
|
+
(pass) registry-ui chart > ChartContainer server render does not emit invalid size warnings [47.00ms]
|
|
6
|
+
|
|
7
|
+
::endgroup::
|
|
8
|
+
|
|
9
|
+
::group::src/button/__test__/button.test.tsx:
|
|
10
|
+
(pass) registry-ui button > buttonVariants returns the shared base styles and defaults
|
|
11
|
+
(pass) registry-ui button > buttonVariants applies explicit variant and size overrides [1.00ms]
|
|
12
|
+
(pass) registry-ui button > button exports keep stable display names
|
|
13
|
+
(pass) registry-ui button > Button renders loading state as a busy disabled native button [9.00ms]
|
|
14
|
+
(pass) registry-ui button > Button preserves explicit disabled state even when loading is false
|
|
15
|
+
(pass) registry-ui button > Button collapses into icon-only mode and hides secondary content [4.00ms]
|
|
16
|
+
(pass) registry-ui button > AnimationIcon renders left and right placements around children [1.00ms]
|
|
17
|
+
|
|
18
|
+
::endgroup::
|
|
19
|
+
|
|
20
|
+
8 pass
|
|
21
|
+
0 fail
|
|
22
|
+
25 expect() calls
|
|
23
|
+
Ran 8 tests across 2 files. [742.00ms]
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @gentleduck/registry-ui
|
|
2
2
|
|
|
3
|
+
## 0.2.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2b6e8d0: Resolve all biome lint warnings, improve type safety, and add test coverage across the monorepo.
|
|
8
|
+
- Updated dependencies [2b6e8d0]
|
|
9
|
+
- @gentleduck/primitives@0.2.5
|
|
10
|
+
- @gentleduck/variants@0.1.20
|
|
11
|
+
- @gentleduck/hooks@0.1.12
|
|
12
|
+
- @gentleduck/motion@0.1.17
|
|
13
|
+
- @gentleduck/libs@0.1.15
|
|
14
|
+
- @gentleduck/vim@0.1.16
|
|
15
|
+
|
|
16
|
+
## 0.2.5
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- fix(search): disable primitive's built-in filter when using custom lunr search
|
|
21
|
+
|
|
22
|
+
The command menu had two competing filtering systems: lunr-based search and the primitive's
|
|
23
|
+
substring filter. The primitive's filter was hiding items via `el.hidden = true` even when
|
|
24
|
+
lunr correctly found them, causing search results to not appear. Added `shouldFilter` prop
|
|
25
|
+
to the Command primitive to allow disabling the built-in filter.
|
|
26
|
+
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
- @gentleduck/primitives@0.2.4
|
|
29
|
+
|
|
3
30
|
## 0.2.4
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -52,8 +52,9 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
|
-
"check-types": "tsc -p tsconfig.json --noEmit --pretty false --skipLibCheck"
|
|
55
|
+
"check-types": "tsc -p tsconfig.json --noEmit --pretty false --skipLibCheck",
|
|
56
|
+
"test": "bun test"
|
|
56
57
|
},
|
|
57
58
|
"type": "module",
|
|
58
|
-
"version": "0.2.
|
|
59
|
+
"version": "0.2.6"
|
|
59
60
|
}
|
package/src/_old/_table/index.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated These table components are deprecated and no longer maintained.
|
|
3
|
+
* Use the components from `@duck-ui/registry-ui/table` instead.
|
|
4
|
+
* This module will be removed in a future release.
|
|
5
|
+
*/
|
|
1
6
|
export * from './table'
|
|
7
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/table` instead. */
|
|
2
8
|
export * from './table.constants'
|
|
9
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/table` instead. */
|
|
3
10
|
export * from './table.hook'
|
|
11
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/table` instead. */
|
|
4
12
|
export * from './table.lib'
|
|
13
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/table` instead. */
|
|
5
14
|
export * from './table.types'
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
// DuckTableSearchInputProps
|
|
207
207
|
// >(({ trigger, label, badge, keys }, ref) => {
|
|
208
208
|
// const {
|
|
209
|
-
// children: badgeChildren = '
|
|
209
|
+
// children: badgeChildren = 'Ctrl+Shift+F',
|
|
210
210
|
// className: badgeClassName,
|
|
211
211
|
// ...badgeProps
|
|
212
212
|
// } = badge ?? {}
|
|
@@ -415,7 +415,7 @@
|
|
|
415
415
|
// children: 'View',
|
|
416
416
|
// command: {
|
|
417
417
|
// key: 'ctrl+shift+v',
|
|
418
|
-
// label: '
|
|
418
|
+
// label: 'Ctrl+Shift+V',
|
|
419
419
|
// },
|
|
420
420
|
// icon: {
|
|
421
421
|
// children: MixerHorizontalIcon as LucideIcon,
|
|
@@ -527,7 +527,7 @@
|
|
|
527
527
|
// // }),
|
|
528
528
|
// // command: {
|
|
529
529
|
// // key: 'ctrl+shift+down',
|
|
530
|
-
// // label: '
|
|
530
|
+
// // label: 'Ctrl+Shift+Down',
|
|
531
531
|
// // action: () =>
|
|
532
532
|
// // setPaginationState({
|
|
533
533
|
// // ...paginationState,
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
// // onClick: () => setPaginationState({ ...paginationState, activePage: 0 }),
|
|
547
547
|
// // command: {
|
|
548
548
|
// // key: 'ctrl+shift+left',
|
|
549
|
-
// // label: '
|
|
549
|
+
// // label: 'Ctrl+Shift+Left',
|
|
550
550
|
// // action: () => setPaginationState({ ...paginationState, activePage: 0 }),
|
|
551
551
|
// // },
|
|
552
552
|
// label: {
|
|
@@ -561,7 +561,7 @@
|
|
|
561
561
|
// // onClick: () => setPaginationState({ ...paginationState, activePage: resultArrays.length - 1 }),
|
|
562
562
|
// // command: {
|
|
563
563
|
// // key: 'ctrl+shift+right',
|
|
564
|
-
// // label: '
|
|
564
|
+
// // label: 'Ctrl+Shift+Right',
|
|
565
565
|
// // action: () => setPaginationState({ ...paginationState, activePage: resultArrays.length - 1 }),
|
|
566
566
|
// // },
|
|
567
567
|
// label: {
|
|
@@ -575,7 +575,7 @@
|
|
|
575
575
|
// right={{
|
|
576
576
|
// command: {
|
|
577
577
|
// key: 'ctrl+shift+up',
|
|
578
|
-
// label: '
|
|
578
|
+
// label: 'Ctrl+Shift+Up',
|
|
579
579
|
// // action: () =>
|
|
580
580
|
// // setPaginationState({
|
|
581
581
|
// // ...paginationState,
|
|
@@ -677,7 +677,7 @@
|
|
|
677
677
|
// className: 'w-[4.5rem] h-[32px] gap-0',
|
|
678
678
|
// command: {
|
|
679
679
|
// key: 'ctrl+shift+c',
|
|
680
|
-
// label: '
|
|
680
|
+
// label: 'Ctrl+Shift+C',
|
|
681
681
|
// },
|
|
682
682
|
// label: {
|
|
683
683
|
// children: 'Rows per page',
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated These upload components are deprecated and no longer maintained.
|
|
3
|
+
* Use the components from `@duck-ui/registry-ui/upload` instead.
|
|
4
|
+
* This module will be removed in a future release.
|
|
5
|
+
*/
|
|
1
6
|
export * from './upload'
|
|
7
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
2
8
|
export * from './upload.assets'
|
|
9
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
3
10
|
export * from './upload.constants'
|
|
11
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
4
12
|
export * from './upload.dto'
|
|
13
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
5
14
|
export * from './upload.lib'
|
|
15
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
6
16
|
export * from './upload.types'
|
|
17
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
7
18
|
export * from './upload-advanced'
|
|
19
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
8
20
|
export * from './upload-advanced-chunks'
|
|
21
|
+
/** @deprecated Use components from `@duck-ui/registry-ui/upload` instead. */
|
|
9
22
|
export * from './upload-sonner'
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// </p>
|
|
35
35
|
// <div className="flex items-center gap-2">
|
|
36
36
|
// {remainingTime && (
|
|
37
|
-
// <p className="text-foreground-light text-sm font-mono">{`${remainingTime && !isNaN(remainingTime) && isFinite(remainingTime) && remainingTime !== 0 ? `${formatTime(remainingTime)} remaining
|
|
37
|
+
// <p className="text-foreground-light text-sm font-mono">{`${remainingTime && !isNaN(remainingTime) && isFinite(remainingTime) && remainingTime !== 0 ? `${formatTime(remainingTime)} remaining -` : ''}`}</p>
|
|
38
38
|
// )}
|
|
39
39
|
// <p className="text-foreground-light text-sm font-mono">{`${progress}%`}</p>
|
|
40
40
|
// </div>
|
|
@@ -337,7 +337,19 @@ const AudioVisualizer: React.FC<AudioVisualizerProps> = ({
|
|
|
337
337
|
setLoading,
|
|
338
338
|
width,
|
|
339
339
|
})
|
|
340
|
-
}, [
|
|
340
|
+
}, [
|
|
341
|
+
blob,
|
|
342
|
+
barWidth,
|
|
343
|
+
currentColors.backgroundColor,
|
|
344
|
+
currentColors.barColor,
|
|
345
|
+
currentColors.barPlayedColor,
|
|
346
|
+
gap,
|
|
347
|
+
height,
|
|
348
|
+
minBarHeight,
|
|
349
|
+
process_audio,
|
|
350
|
+
setLoading,
|
|
351
|
+
width,
|
|
352
|
+
])
|
|
341
353
|
|
|
342
354
|
React.useEffect(() => {
|
|
343
355
|
if (!canvasRef.current) return
|
|
@@ -359,7 +371,19 @@ const AudioVisualizer: React.FC<AudioVisualizerProps> = ({
|
|
|
359
371
|
gap,
|
|
360
372
|
minBarHeight,
|
|
361
373
|
})
|
|
362
|
-
}, [
|
|
374
|
+
}, [
|
|
375
|
+
data,
|
|
376
|
+
width,
|
|
377
|
+
currentTime,
|
|
378
|
+
duration,
|
|
379
|
+
animationProgress,
|
|
380
|
+
barWidth,
|
|
381
|
+
currentColors.backgroundColor,
|
|
382
|
+
currentColors.barColor,
|
|
383
|
+
currentColors.barPlayedColor,
|
|
384
|
+
gap,
|
|
385
|
+
minBarHeight,
|
|
386
|
+
])
|
|
363
387
|
|
|
364
388
|
return (
|
|
365
389
|
<canvas
|
package/src/audio/audio.types.ts
CHANGED
|
@@ -6,8 +6,7 @@ export interface RecordingParams {
|
|
|
6
6
|
|
|
7
7
|
export interface StopRecordingHandlerParam {
|
|
8
8
|
setRecording: React.Dispatch<React.SetStateAction<boolean>>
|
|
9
|
-
|
|
10
|
-
intervalRef: React.RefObject<NodeJS.Timeout | null>
|
|
9
|
+
intervalRef: React.RefObject<ReturnType<typeof setInterval> | null>
|
|
11
10
|
mediaRecorderRef: React.RefObject<MediaRecorder | null>
|
|
12
11
|
durationRef: React.RefObject<number>
|
|
13
12
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
|
+
import { AnimationIcon, Button } from '../button'
|
|
5
|
+
import { buttonVariants } from '../button.constants'
|
|
6
|
+
|
|
7
|
+
describe('registry-ui button', () => {
|
|
8
|
+
test('buttonVariants returns the shared base styles and defaults', () => {
|
|
9
|
+
const classes = buttonVariants()
|
|
10
|
+
|
|
11
|
+
expect(classes).toContain('inline-flex')
|
|
12
|
+
expect(classes).toContain('bg-primary')
|
|
13
|
+
expect(classes).toContain('h-9')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('buttonVariants applies explicit variant and size overrides', () => {
|
|
17
|
+
const classes = buttonVariants({ size: 'sm', variant: 'ghost' })
|
|
18
|
+
|
|
19
|
+
expect(classes).toContain('h-8')
|
|
20
|
+
expect(classes).toContain('hover:bg-accent')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('button exports keep stable display names', () => {
|
|
24
|
+
expect(Button).toBeDefined()
|
|
25
|
+
expect(Button.displayName).toBe('Button')
|
|
26
|
+
expect(AnimationIcon.displayName).toBe('AnimationIcon')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('Button renders loading state as a busy disabled native button', () => {
|
|
30
|
+
const html = renderToStaticMarkup(<Button loading>Save</Button>)
|
|
31
|
+
|
|
32
|
+
expect(html).toContain('type="button"')
|
|
33
|
+
expect(html).toContain('aria-busy="true"')
|
|
34
|
+
expect(html).toContain('disabled=""')
|
|
35
|
+
expect(html).toContain('animate-spin')
|
|
36
|
+
expect(html).toContain('Save')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('Button preserves explicit disabled state even when loading is false', () => {
|
|
40
|
+
const html = renderToStaticMarkup(
|
|
41
|
+
<Button disabled loading={false}>
|
|
42
|
+
Save
|
|
43
|
+
</Button>,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(html).toContain('disabled=""')
|
|
47
|
+
expect(html).not.toContain('aria-busy="true"')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('Button collapses into icon-only mode and hides secondary content', () => {
|
|
51
|
+
const html = renderToStaticMarkup(
|
|
52
|
+
<Button icon={<span data-icon="left">L</span>} isCollapsed secondIcon={<span data-icon="right">R</span>}>
|
|
53
|
+
Save
|
|
54
|
+
</Button>,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
expect(html).toContain('data-icon="left"')
|
|
58
|
+
expect(html).toContain('size-9')
|
|
59
|
+
expect(html).not.toContain('Save')
|
|
60
|
+
expect(html).not.toContain('data-icon="right"')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('AnimationIcon renders left and right placements around children', () => {
|
|
64
|
+
const leftHtml = renderToStaticMarkup(
|
|
65
|
+
<AnimationIcon animationIcon={{ icon: <span data-icon="left">L</span>, iconPlacement: 'left' }}>
|
|
66
|
+
Label
|
|
67
|
+
</AnimationIcon>,
|
|
68
|
+
)
|
|
69
|
+
const rightHtml = renderToStaticMarkup(
|
|
70
|
+
<AnimationIcon animationIcon={{ icon: <span data-icon="right">R</span>, iconPlacement: 'right' }}>
|
|
71
|
+
Label
|
|
72
|
+
</AnimationIcon>,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(leftHtml).toContain('data-icon="left"')
|
|
76
|
+
expect(leftHtml.indexOf('data-icon="left"')).toBeLessThan(leftHtml.indexOf('Label'))
|
|
77
|
+
expect(rightHtml).toContain('data-icon="right"')
|
|
78
|
+
expect(rightHtml.indexOf('Label')).toBeLessThan(rightHtml.indexOf('data-icon="right"'))
|
|
79
|
+
})
|
|
80
|
+
})
|
package/src/button/button.tsx
CHANGED
|
@@ -42,7 +42,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
42
42
|
variant,
|
|
43
43
|
}),
|
|
44
44
|
)}
|
|
45
|
-
disabled={loading
|
|
45
|
+
disabled={Boolean(loading) || disabled}
|
|
46
46
|
ref={ref}
|
|
47
47
|
type={type}>
|
|
48
48
|
{loading ? <Loader aria-hidden="true" className="animate-spin" /> : icon}
|
|
@@ -12,6 +12,7 @@ const ButtonGroup = React.forwardRef<
|
|
|
12
12
|
>(({ className, orientation = 'horizontal', dir, ...props }, ref) => {
|
|
13
13
|
const direction = useDirection(dir as Direction)
|
|
14
14
|
return (
|
|
15
|
+
// biome-ignore lint/a11y/useSemanticElements: group role is semantically correct for button groups
|
|
15
16
|
<div
|
|
16
17
|
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
17
18
|
data-orientation={orientation}
|
|
@@ -100,6 +100,7 @@ const Carousel = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLDivEleme
|
|
|
100
100
|
scrollNext,
|
|
101
101
|
scrollPrev,
|
|
102
102
|
}}>
|
|
103
|
+
{/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: carousel is a custom widget needing roledescription */}
|
|
103
104
|
<section
|
|
104
105
|
aria-roledescription="carousel"
|
|
105
106
|
className={cn('relative', className)}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
|
+
import { Bar, BarChart, XAxis } from 'recharts'
|
|
5
|
+
import { ChartContainer } from '../chart'
|
|
6
|
+
|
|
7
|
+
const data = [{ name: 'alpha', value: 12 }]
|
|
8
|
+
|
|
9
|
+
describe('registry-ui chart', () => {
|
|
10
|
+
test('ChartContainer server render does not emit invalid size warnings', () => {
|
|
11
|
+
const originalWarn = console.warn
|
|
12
|
+
const warnings: string[] = []
|
|
13
|
+
|
|
14
|
+
console.warn = (...args: unknown[]) => {
|
|
15
|
+
warnings.push(args.map((value) => String(value)).join(' '))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const html = renderToStaticMarkup(
|
|
20
|
+
<ChartContainer
|
|
21
|
+
config={{
|
|
22
|
+
value: {
|
|
23
|
+
color: 'hsl(var(--chart-1))',
|
|
24
|
+
label: 'Value',
|
|
25
|
+
},
|
|
26
|
+
}}>
|
|
27
|
+
<BarChart accessibilityLayer data={data}>
|
|
28
|
+
<XAxis dataKey="name" hide />
|
|
29
|
+
<Bar dataKey="value" fill="var(--color-value)" radius={8} />
|
|
30
|
+
</BarChart>
|
|
31
|
+
</ChartContainer>,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
expect(html).toContain('data-slot="chart-container"')
|
|
35
|
+
expect(warnings.some((message) => message.includes('The width(') && message.includes('height('))).toBe(false)
|
|
36
|
+
} finally {
|
|
37
|
+
console.warn = originalWarn
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
})
|
package/src/chart/chart.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
|
|
16
16
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
|
17
17
|
export const THEMES = { dark: '.dark', light: '' } as const
|
|
18
|
+
const DEFAULT_CHART_INITIAL_DIMENSION = { width: 640, height: 360 } as const
|
|
18
19
|
|
|
19
20
|
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
|
20
21
|
|
|
@@ -46,7 +47,9 @@ const ChartContainer = ({ id, className, children, config, ref, dir, ...props }:
|
|
|
46
47
|
dir={direction}
|
|
47
48
|
ref={ref}>
|
|
48
49
|
<ChartStyle config={config} id={chartId} />
|
|
49
|
-
<RechartsPrimitive.ResponsiveContainer minWidth={0}>
|
|
50
|
+
<RechartsPrimitive.ResponsiveContainer initialDimension={DEFAULT_CHART_INITIAL_DIMENSION} minWidth={0}>
|
|
51
|
+
{children}
|
|
52
|
+
</RechartsPrimitive.ResponsiveContainer>
|
|
50
53
|
</div>
|
|
51
54
|
</ChartContext.Provider>
|
|
52
55
|
)
|
|
@@ -61,6 +64,7 @@ const ChartStyle = ({ id, config }: ChartStyleProps) => {
|
|
|
61
64
|
|
|
62
65
|
return (
|
|
63
66
|
<style
|
|
67
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: controlled CSS injection for chart color themes
|
|
64
68
|
dangerouslySetInnerHTML={{
|
|
65
69
|
__html: Object.entries(THEMES)
|
|
66
70
|
.map(
|
|
@@ -37,6 +37,7 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
37
37
|
onCheckedChange?.(next)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: changeCheckedState is stable and defined in render scope
|
|
40
41
|
React.useEffect(() => {
|
|
41
42
|
if (ref && typeof ref !== 'function' && checked === 'indeterminate' && ref.current) {
|
|
42
43
|
ref.current.indeterminate = true
|
|
@@ -44,6 +44,7 @@ const Collapsible = React.forwardRef<
|
|
|
44
44
|
onOpenChange?.(state)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: handleOpenChange and triggerRef are stable refs
|
|
47
48
|
React.useEffect(() => {
|
|
48
49
|
if (open) {
|
|
49
50
|
handleOpenChange(open)
|
|
@@ -56,7 +57,7 @@ const Collapsible = React.forwardRef<
|
|
|
56
57
|
|
|
57
58
|
triggerRef.current?.addEventListener('click', handleClick)
|
|
58
59
|
return () => triggerRef.current?.removeEventListener('click', handleClick)
|
|
59
|
-
}, [open])
|
|
60
|
+
}, [open, onOpenChange])
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
63
|
<CollapsibleContext.Provider
|
package/src/command/command.tsx
CHANGED
|
@@ -162,13 +162,19 @@ function CommandShortcut({
|
|
|
162
162
|
)
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
function CommandDialog({
|
|
165
|
+
function CommandDialog({
|
|
166
|
+
children,
|
|
167
|
+
shouldFilter,
|
|
168
|
+
...props
|
|
169
|
+
}: React.ComponentPropsWithRef<typeof Dialog> & { shouldFilter?: boolean }): React.JSX.Element {
|
|
166
170
|
return (
|
|
167
171
|
<Dialog {...props}>
|
|
168
172
|
<DialogContent className="h-125 max-w-full p-0 lg:w-[700px]">
|
|
169
173
|
<DialogTitle className="sr-only">Command palette</DialogTitle>
|
|
170
174
|
<DialogDescription className="sr-only">Search for commands and navigation items</DialogDescription>
|
|
171
|
-
<Command className="max-w-full"
|
|
175
|
+
<Command className="max-w-full" shouldFilter={shouldFilter}>
|
|
176
|
+
{children}
|
|
177
|
+
</Command>
|
|
172
178
|
</DialogContent>
|
|
173
179
|
</Dialog>
|
|
174
180
|
)
|
|
@@ -53,7 +53,9 @@ function DialogContentResponsive({
|
|
|
53
53
|
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
54
54
|
|
|
55
55
|
if (isDesktop) {
|
|
56
|
-
return
|
|
56
|
+
return (
|
|
57
|
+
<DialogContent {...(props as React.ComponentPropsWithoutRef<typeof DialogContent>)}>{children}</DialogContent>
|
|
58
|
+
)
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
return <DrawerContent {...(props as React.ComponentPropsWithoutRef<typeof DrawerContent>)}>{children}</DrawerContent>
|
package/src/field/field.tsx
CHANGED
|
@@ -58,6 +58,7 @@ function Field({
|
|
|
58
58
|
...props
|
|
59
59
|
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
|
|
60
60
|
return (
|
|
61
|
+
// biome-ignore lint/a11y/useSemanticElements: field group role is semantically correct for form field grouping
|
|
61
62
|
<div
|
|
62
63
|
className={cn(fieldVariants({ orientation }), className)}
|
|
63
64
|
data-orientation={orientation}
|
|
@@ -169,6 +170,7 @@ function FieldError({
|
|
|
169
170
|
|
|
170
171
|
return (
|
|
171
172
|
<ul className="ms-4 flex list-disc flex-col gap-1">
|
|
173
|
+
{/* biome-ignore lint/suspicious/noArrayIndexKey: error messages have no stable unique id */}
|
|
172
174
|
{errors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
|
173
175
|
</ul>
|
|
174
176
|
)
|
|
@@ -12,6 +12,7 @@ const InputGroup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutR
|
|
|
12
12
|
({ className, dir, children, ...props }, ref) => {
|
|
13
13
|
const direction = useDirection(dir as Direction)
|
|
14
14
|
return (
|
|
15
|
+
// biome-ignore lint/a11y/useSemanticElements: group role is semantically correct for input grouping
|
|
15
16
|
<div
|
|
16
17
|
className={cn(
|
|
17
18
|
'group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs outline-none transition-[color,box-shadow] dark:bg-input/30',
|
|
@@ -66,6 +67,8 @@ const InputGroupAddon = React.forwardRef<
|
|
|
66
67
|
React.ComponentPropsWithoutRef<'div'> & VariantProps<typeof inputGroupAddonVariants>
|
|
67
68
|
>(({ className, align = 'inline-start', ...props }, ref) => {
|
|
68
69
|
return (
|
|
70
|
+
// biome-ignore lint/a11y/useSemanticElements: group role is semantically correct for addon grouping
|
|
71
|
+
// biome-ignore lint/a11y/useKeyWithClickEvents: click handler delegates focus to input, not interactive itself
|
|
69
72
|
<div
|
|
70
73
|
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
71
74
|
data-align={align}
|
package/src/item/item.tsx
CHANGED
|
@@ -10,6 +10,7 @@ const ItemGroup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRe
|
|
|
10
10
|
({ className, dir, ...props }, ref) => {
|
|
11
11
|
const direction = useDirection(dir as Direction)
|
|
12
12
|
return (
|
|
13
|
+
// biome-ignore lint/a11y/useSemanticElements: list role on div is intentional for composed item patterns
|
|
13
14
|
<div
|
|
14
15
|
className={cn('group/item-group flex flex-col', className)}
|
|
15
16
|
dir={direction}
|
|
@@ -243,7 +243,7 @@ export function JsonTextareaField<TFieldValues extends FieldValues>(
|
|
|
243
243
|
|
|
244
244
|
const oneLine = committedText.replace(/\s+/g, ' ').trim()
|
|
245
245
|
return oneLine.length > 120 ? `${oneLine.slice(0, 117)}...` : oneLine
|
|
246
|
-
}, [committedText])
|
|
246
|
+
}, [committedText, t.nullPreview])
|
|
247
247
|
|
|
248
248
|
const inlineEditor = (
|
|
249
249
|
<div className="space-y-2" data-slot="json-editor-inline">
|
|
@@ -335,67 +335,65 @@ export function JsonTextareaField<TFieldValues extends FieldValues>(
|
|
|
335
335
|
{fieldState.error ? <FieldError errors={[fieldState.error]} /> : null}
|
|
336
336
|
|
|
337
337
|
{expandMode === 'sheet' ? (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<
|
|
350
|
-
<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
<
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
<div className="
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
</Button>
|
|
393
|
-
</div>
|
|
338
|
+
<Sheet
|
|
339
|
+
onOpenChange={(nextOpen) => {
|
|
340
|
+
if (nextOpen) {
|
|
341
|
+
openSheet()
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
requestCloseSheet()
|
|
346
|
+
}}
|
|
347
|
+
open={sheetOpen}>
|
|
348
|
+
<SheetContent className="w-full sm:max-w-3xl" dir={dir} side={sheetSide}>
|
|
349
|
+
<SheetHeader>
|
|
350
|
+
<SheetTitle>{sheetTitle}</SheetTitle>
|
|
351
|
+
</SheetHeader>
|
|
352
|
+
|
|
353
|
+
<div className="mt-4 space-y-3" data-slot="json-editor-sheet-content">
|
|
354
|
+
<JsonEditorView
|
|
355
|
+
dir={dir}
|
|
356
|
+
lang={lang}
|
|
357
|
+
lineHeightPx={lineHeightPx}
|
|
358
|
+
lineNumbers={lineNumbers}
|
|
359
|
+
onChange={(value) => {
|
|
360
|
+
setSheetDraft(value)
|
|
361
|
+
setSheetDirty(true)
|
|
362
|
+
}}
|
|
363
|
+
onKeyDown={sheetHotkeys}
|
|
364
|
+
onScroll={setSheetScrollTop}
|
|
365
|
+
placeholder={placeholder}
|
|
366
|
+
readOnly={!isEditable}
|
|
367
|
+
rows={24}
|
|
368
|
+
scrollTop={sheetScrollTop}
|
|
369
|
+
value={sheetDraft}
|
|
370
|
+
/>
|
|
371
|
+
|
|
372
|
+
<div className="flex items-center justify-between gap-2" data-slot="json-editor-sheet-actions">
|
|
373
|
+
<div className="text-muted-foreground text-xs">{t.sheetStatusHint}</div>
|
|
374
|
+
|
|
375
|
+
<div className="flex items-center gap-2">
|
|
376
|
+
<Button
|
|
377
|
+
disabled={!isEditable || !canFormatSheet}
|
|
378
|
+
onClick={formatSheet}
|
|
379
|
+
size="sm"
|
|
380
|
+
type="button"
|
|
381
|
+
variant="outline">
|
|
382
|
+
{t.format}
|
|
383
|
+
</Button>
|
|
384
|
+
|
|
385
|
+
<Button onClick={requestCloseSheet} size="sm" type="button" variant="outline">
|
|
386
|
+
{t.close}
|
|
387
|
+
</Button>
|
|
388
|
+
|
|
389
|
+
<Button disabled={!isEditable} onClick={saveSheet} size="sm" type="button">
|
|
390
|
+
{t.save}
|
|
391
|
+
</Button>
|
|
394
392
|
</div>
|
|
395
393
|
</div>
|
|
396
|
-
</
|
|
397
|
-
</
|
|
398
|
-
|
|
394
|
+
</div>
|
|
395
|
+
</SheetContent>
|
|
396
|
+
</Sheet>
|
|
399
397
|
) : null}
|
|
400
398
|
|
|
401
399
|
<Portal>
|
package/src/label/label.tsx
CHANGED
|
@@ -10,6 +10,7 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ className, htmlF
|
|
|
10
10
|
const direction = useDirection(dir as Direction)
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
|
+
// biome-ignore lint/a11y/noLabelWithoutControl: label is composed with form controls externally via htmlFor
|
|
13
14
|
<label
|
|
14
15
|
className={cn(
|
|
15
16
|
'text-balance font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
@@ -304,8 +304,8 @@ function PreviewPanel({
|
|
|
304
304
|
if (!el) return
|
|
305
305
|
|
|
306
306
|
const dist = (e: TouchEvent) => {
|
|
307
|
-
const a = e.touches[0]
|
|
308
|
-
const b = e.touches[1]
|
|
307
|
+
const a = e.touches[0] ?? { clientX: 0, clientY: 0 }
|
|
308
|
+
const b = e.touches[1] ?? { clientX: 0, clientY: 0 }
|
|
309
309
|
return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY)
|
|
310
310
|
}
|
|
311
311
|
|
|
@@ -17,7 +17,6 @@ const Separator = React.forwardRef<
|
|
|
17
17
|
aria-orientation={orientation}
|
|
18
18
|
className={cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-px w-full' : 'min-h-full w-px', className)}
|
|
19
19
|
dir={direction}
|
|
20
|
-
role="separator"
|
|
21
20
|
{...props}
|
|
22
21
|
data-slot="separator"
|
|
23
22
|
/>
|
package/src/sidebar/sidebar.tsx
CHANGED
|
@@ -61,7 +61,7 @@ function SidebarProvider({
|
|
|
61
61
|
// Helper to toggle the sidebar.
|
|
62
62
|
const toggleSidebar = React.useCallback(() => {
|
|
63
63
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
|
|
64
|
-
}, [isMobile, setOpen
|
|
64
|
+
}, [isMobile, setOpen])
|
|
65
65
|
|
|
66
66
|
// Adds a keyboard shortcut to toggle the sidebar.
|
|
67
67
|
React.useEffect(() => {
|
|
@@ -91,7 +91,7 @@ function SidebarProvider({
|
|
|
91
91
|
toggleSidebar,
|
|
92
92
|
dir: direction,
|
|
93
93
|
}),
|
|
94
|
-
[state, open, setOpen, isMobile, openMobile,
|
|
94
|
+
[state, open, setOpen, isMobile, openMobile, toggleSidebar, direction],
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
return (
|
package/src/slider/slider.tsx
CHANGED
|
@@ -46,6 +46,7 @@ function Slider({
|
|
|
46
46
|
<SliderPrimitive.Thumb
|
|
47
47
|
data-orientation={orientation}
|
|
48
48
|
data-slot="slider-thumb"
|
|
49
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional, index is the stable key
|
|
49
50
|
key={index}
|
|
50
51
|
className="relative block size-4 shrink-0 select-none rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] after:absolute after:-inset-2 hover:ring-3 focus-visible:outline-hidden focus-visible:ring-3 active:ring-3 disabled:pointer-events-none disabled:opacity-50"
|
|
51
52
|
/>
|
|
@@ -31,6 +31,7 @@ const SonnerUpload = ({
|
|
|
31
31
|
)}
|
|
32
32
|
/>
|
|
33
33
|
<div className="flex w-full flex-col gap-2">
|
|
34
|
+
{/* biome-ignore lint/a11y/useSemanticElements: status role on div is intentional for live region announcements */}
|
|
34
35
|
<div className="flex w-full justify-between" role="status">
|
|
35
36
|
<p className="text-foreground text-sm">
|
|
36
37
|
{progress >= 100
|
package/src/switch/switch.tsx
CHANGED
package/src/tabs/tabs.tsx
CHANGED
|
@@ -35,7 +35,7 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
|
|
|
35
35
|
|
|
36
36
|
React.useEffect(() => {
|
|
37
37
|
if (onValueChange) onValueChange(activeItem)
|
|
38
|
-
}, [activeItem])
|
|
38
|
+
}, [activeItem, onValueChange])
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<TabsContext.Provider value={{ activeItem, setActiveItem, tabsId }}>
|
|
@@ -74,7 +74,7 @@ const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
|
|
|
74
74
|
|
|
75
75
|
React.useEffect(() => {
|
|
76
76
|
if (defaultChecked) setActiveItem(value)
|
|
77
|
-
}, [defaultChecked])
|
|
77
|
+
}, [defaultChecked, setActiveItem, value])
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
80
|
<button
|
package/tsconfig.json
CHANGED
|
@@ -19,7 +19,17 @@
|
|
|
19
19
|
],
|
|
20
20
|
"rootDir": "./"
|
|
21
21
|
},
|
|
22
|
-
"exclude": [
|
|
22
|
+
"exclude": [
|
|
23
|
+
"node_modules",
|
|
24
|
+
"dist",
|
|
25
|
+
"./src/_old",
|
|
26
|
+
"**/__test__/**",
|
|
27
|
+
"**/__test__/**",
|
|
28
|
+
"**/*.test.ts",
|
|
29
|
+
"**/*.test.tsx",
|
|
30
|
+
"**/*.tsbuildinfo",
|
|
31
|
+
"tsconfig.tsbuildinfo"
|
|
32
|
+
],
|
|
23
33
|
"extends": "@gentleduck/typescript-config/base.json",
|
|
24
34
|
"include": ["./**/*.ts", "./**/*.tsx"]
|
|
25
35
|
}
|