@dashforge/tw 0.3.0-beta → 0.5.0-beta
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/CHANGELOG.md +246 -0
- package/PARITY.md +483 -0
- package/PERFORMANCE.md +169 -0
- package/dist/index.esm.js +1055 -2
- package/dist/src/components/Accordion/Accordion.d.ts +21 -0
- package/dist/src/components/Accordion/Accordion.d.ts.map +1 -0
- package/dist/src/components/Accordion/accordion.types.d.ts +74 -0
- package/dist/src/components/Accordion/accordion.types.d.ts.map +1 -0
- package/dist/src/components/Accordion/accordion.variants.d.ts +58 -0
- package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -0
- package/dist/src/components/Dialog/Dialog.d.ts +17 -0
- package/dist/src/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.types.d.ts +75 -0
- package/dist/src/components/Dialog/dialog.types.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts +80 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -0
- package/dist/src/components/Pagination/Pagination.d.ts +32 -0
- package/dist/src/components/Pagination/Pagination.d.ts.map +1 -0
- package/dist/src/components/Pagination/pagination.helpers.d.ts +23 -0
- package/dist/src/components/Pagination/pagination.helpers.d.ts.map +1 -0
- package/dist/src/components/Pagination/pagination.types.d.ts +120 -0
- package/dist/src/components/Pagination/pagination.types.d.ts.map +1 -0
- package/dist/src/components/Pagination/pagination.variants.d.ts +150 -0
- package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -0
- package/dist/src/components/Popover/Popover.d.ts +15 -0
- package/dist/src/components/Popover/Popover.d.ts.map +1 -0
- package/dist/src/components/Popover/popover.types.d.ts +45 -0
- package/dist/src/components/Popover/popover.types.d.ts.map +1 -0
- package/dist/src/components/Popover/popover.variants.d.ts +34 -0
- package/dist/src/components/Popover/popover.variants.d.ts.map +1 -0
- package/dist/src/components/Skeleton/Skeleton.d.ts +23 -0
- package/dist/src/components/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/src/components/Skeleton/skeleton.types.d.ts +56 -0
- package/dist/src/components/Skeleton/skeleton.types.d.ts.map +1 -0
- package/dist/src/components/Skeleton/skeleton.variants.d.ts +110 -0
- package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -0
- package/dist/src/components/Tabs/Tabs.d.ts +15 -0
- package/dist/src/components/Tabs/Tabs.d.ts.map +1 -0
- package/dist/src/components/Tabs/tabs.types.d.ts +55 -0
- package/dist/src/components/Tabs/tabs.types.d.ts.map +1 -0
- package/dist/src/components/Tabs/tabs.variants.d.ts +82 -0
- package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -0
- package/dist/src/components/Tooltip/Tooltip.d.ts +29 -0
- package/dist/src/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/src/components/Tooltip/tooltip.types.d.ts +47 -0
- package/dist/src/components/Tooltip/tooltip.types.d.ts.map +1 -0
- package/dist/src/components/Tooltip/tooltip.variants.d.ts +34 -0
- package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -0
- package/dist/src/index.d.ts +22 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/components/Accordion/Accordion.test.tsx +95 -0
- package/src/components/Accordion/Accordion.tsx +97 -0
- package/src/components/Accordion/accordion.types.ts +66 -0
- package/src/components/Accordion/accordion.variants.ts +30 -0
- package/src/components/Dialog/Dialog.test.tsx +96 -0
- package/src/components/Dialog/Dialog.tsx +93 -0
- package/src/components/Dialog/dialog.types.ts +62 -0
- package/src/components/Dialog/dialog.variants.ts +62 -0
- package/src/components/Pagination/Pagination.test.tsx +314 -0
- package/src/components/Pagination/Pagination.tsx +275 -0
- package/src/components/Pagination/pagination.helpers.ts +102 -0
- package/src/components/Pagination/pagination.types.ts +117 -0
- package/src/components/Pagination/pagination.variants.ts +111 -0
- package/src/components/Popover/Popover.test.tsx +72 -0
- package/src/components/Popover/Popover.tsx +55 -0
- package/src/components/Popover/popover.types.ts +42 -0
- package/src/components/Popover/popover.variants.ts +18 -0
- package/src/components/Skeleton/Skeleton.test.tsx +127 -0
- package/src/components/Skeleton/Skeleton.tsx +49 -0
- package/src/components/Skeleton/skeleton.types.ts +59 -0
- package/src/components/Skeleton/skeleton.variants.ts +86 -0
- package/src/components/Tabs/Tabs.test.tsx +74 -0
- package/src/components/Tabs/Tabs.tsx +66 -0
- package/src/components/Tabs/tabs.types.ts +49 -0
- package/src/components/Tabs/tabs.variants.ts +58 -0
- package/src/components/Tooltip/Tooltip.test.tsx +73 -0
- package/src/components/Tooltip/Tooltip.tsx +70 -0
- package/src/components/Tooltip/tooltip.types.ts +44 -0
- package/src/components/Tooltip/tooltip.variants.ts +18 -0
- package/src/index.ts +57 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface TooltipSlotProps {
|
|
3
|
+
content?: {
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
arrow?: {
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Props for `<Tooltip>`.
|
|
12
|
+
*
|
|
13
|
+
* Built on `@radix-ui/react-tooltip`. Wrap any element with a tooltip
|
|
14
|
+
* that follows APG semantics (`role="tooltip"`, `aria-describedby`
|
|
15
|
+
* wired to the trigger).
|
|
16
|
+
*
|
|
17
|
+
* Renders an arrow by default. Three side options
|
|
18
|
+
* (top / right / bottom / left) with 'top' as default. Delay
|
|
19
|
+
* configurable via `delayDuration` (forwarded to Radix Provider).
|
|
20
|
+
*/
|
|
21
|
+
export interface TooltipProps {
|
|
22
|
+
/** The element that, when hovered/focused, shows the tooltip. */
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
/** Tooltip body content. */
|
|
25
|
+
content: ReactNode;
|
|
26
|
+
/** Side of the trigger to show the tooltip on. @default 'top' */
|
|
27
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
28
|
+
/** Alignment along the perpendicular axis. @default 'center' */
|
|
29
|
+
align?: 'start' | 'center' | 'end';
|
|
30
|
+
/** Hide the arrow indicator. @default false (arrow shown) */
|
|
31
|
+
hideArrow?: boolean;
|
|
32
|
+
/** Delay before the tooltip shows on hover (ms). @default 200 */
|
|
33
|
+
delayDuration?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Controlled open state. Use when the tooltip should be programmatic
|
|
36
|
+
* (e.g. revealed on a manual button press). Omit for default
|
|
37
|
+
* hover / focus behavior.
|
|
38
|
+
*/
|
|
39
|
+
open?: boolean;
|
|
40
|
+
/** Called when the tooltip's open state changes. */
|
|
41
|
+
onOpenChange?: (open: boolean) => void;
|
|
42
|
+
/** Side offset in pixels from the trigger. @default 4 */
|
|
43
|
+
sideOffset?: number;
|
|
44
|
+
/** Per-slot overrides. */
|
|
45
|
+
slotProps?: TooltipSlotProps;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=tooltip.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.types.d.ts","sourceRoot":"","sources":["../../../../src/components/Tooltip/tooltip.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,KAAK,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,QAAQ,EAAE,SAAS,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,SAAS,CAAC;IACnB,iEAAiE;IACjE,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type VariantProps } from 'tailwind-variants';
|
|
2
|
+
export declare const tooltipVariants: import("tailwind-variants").TVReturnType<{
|
|
3
|
+
[key: string]: {
|
|
4
|
+
[key: string]: import("tailwind-merge").ClassNameValue | {
|
|
5
|
+
content?: import("tailwind-merge").ClassNameValue;
|
|
6
|
+
arrow?: import("tailwind-merge").ClassNameValue;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
} | {
|
|
10
|
+
[x: string]: {
|
|
11
|
+
[x: string]: import("tailwind-merge").ClassNameValue | {
|
|
12
|
+
content?: import("tailwind-merge").ClassNameValue;
|
|
13
|
+
arrow?: import("tailwind-merge").ClassNameValue;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
} | {}, {
|
|
17
|
+
content: string[];
|
|
18
|
+
arrow: string;
|
|
19
|
+
}, undefined, {
|
|
20
|
+
[key: string]: {
|
|
21
|
+
[key: string]: import("tailwind-merge").ClassNameValue | {
|
|
22
|
+
content?: import("tailwind-merge").ClassNameValue;
|
|
23
|
+
arrow?: import("tailwind-merge").ClassNameValue;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
} | {}, {
|
|
27
|
+
content: string[];
|
|
28
|
+
arrow: string;
|
|
29
|
+
}, import("tailwind-variants").TVReturnType<unknown, {
|
|
30
|
+
content: string[];
|
|
31
|
+
arrow: string;
|
|
32
|
+
}, undefined, unknown, unknown, undefined>>;
|
|
33
|
+
export type TooltipVariants = VariantProps<typeof tooltipVariants>;
|
|
34
|
+
//# sourceMappingURL=tooltip.variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.variants.d.ts","sourceRoot":"","sources":["../../../../src/components/Tooltip/tooltip.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAa1B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -93,6 +93,27 @@ export { AspectRatio } from './components/AspectRatio/AspectRatio.js';
|
|
|
93
93
|
export type { AspectRatioProps } from './components/AspectRatio/aspectRatio.types.js';
|
|
94
94
|
export { VisuallyHidden } from './components/VisuallyHidden/VisuallyHidden.js';
|
|
95
95
|
export type { VisuallyHiddenProps } from './components/VisuallyHidden/visuallyHidden.types.js';
|
|
96
|
+
export { Dialog } from './components/Dialog/Dialog.js';
|
|
97
|
+
export type { DialogProps, DialogSlotProps, } from './components/Dialog/dialog.types.js';
|
|
98
|
+
export { dialogVariants } from './components/Dialog/dialog.variants.js';
|
|
99
|
+
export { Tabs } from './components/Tabs/Tabs.js';
|
|
100
|
+
export type { TabsProps, TabsSlotProps, TabItem, } from './components/Tabs/tabs.types.js';
|
|
101
|
+
export { tabsVariants } from './components/Tabs/tabs.variants.js';
|
|
102
|
+
export { Tooltip } from './components/Tooltip/Tooltip.js';
|
|
103
|
+
export type { TooltipProps, TooltipSlotProps, } from './components/Tooltip/tooltip.types.js';
|
|
104
|
+
export { tooltipVariants } from './components/Tooltip/tooltip.variants.js';
|
|
105
|
+
export { Popover } from './components/Popover/Popover.js';
|
|
106
|
+
export type { PopoverProps, PopoverSlotProps, } from './components/Popover/popover.types.js';
|
|
107
|
+
export { popoverVariants } from './components/Popover/popover.variants.js';
|
|
108
|
+
export { Accordion } from './components/Accordion/Accordion.js';
|
|
109
|
+
export type { AccordionProps, AccordionSlotProps, AccordionItem, } from './components/Accordion/accordion.types.js';
|
|
110
|
+
export { accordionVariants } from './components/Accordion/accordion.variants.js';
|
|
111
|
+
export { Skeleton } from './components/Skeleton/Skeleton.js';
|
|
112
|
+
export type { SkeletonProps, SkeletonSlotProps, } from './components/Skeleton/skeleton.types.js';
|
|
113
|
+
export { skeletonVariants } from './components/Skeleton/skeleton.variants.js';
|
|
114
|
+
export { Pagination } from './components/Pagination/Pagination.js';
|
|
115
|
+
export type { PaginationProps, PaginationSlotProps, PaginationLabels, } from './components/Pagination/pagination.types.js';
|
|
116
|
+
export { paginationVariants } from './components/Pagination/pagination.variants.js';
|
|
96
117
|
export { useAccessState } from './hooks/useAccessState.js';
|
|
97
118
|
export { cn } from './utils/cn.js';
|
|
98
119
|
export { tv } from 'tailwind-variants';
|
|
@@ -100,5 +121,5 @@ export type { VariantProps } from 'tailwind-variants';
|
|
|
100
121
|
/**
|
|
101
122
|
* Package version (synced with `package.json` at publish time).
|
|
102
123
|
*/
|
|
103
|
-
export declare const VERSION = "0.
|
|
124
|
+
export declare const VERSION = "0.5.0-beta";
|
|
104
125
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashforge/tw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-beta",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|
|
@@ -16,18 +16,23 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@radix-ui/react-accordion": "^1.2.0",
|
|
19
20
|
"@radix-ui/react-checkbox": "^1.1.0",
|
|
21
|
+
"@radix-ui/react-dialog": "^1.1.0",
|
|
22
|
+
"@radix-ui/react-popover": "^1.1.0",
|
|
20
23
|
"@radix-ui/react-radio-group": "^1.3.8",
|
|
21
24
|
"@radix-ui/react-slot": "^1.1.0",
|
|
22
25
|
"@radix-ui/react-switch": "^1.1.0",
|
|
26
|
+
"@radix-ui/react-tabs": "^1.1.0",
|
|
27
|
+
"@radix-ui/react-tooltip": "^1.1.0",
|
|
23
28
|
"@vitejs/plugin-react": "*",
|
|
24
29
|
"clsx": "^2.1.0",
|
|
25
30
|
"tailwind-merge": "^2.5.0",
|
|
26
31
|
"tailwind-variants": "^3.1.1",
|
|
27
32
|
"vitest": "*",
|
|
28
33
|
"@dashforge/forms": "0.2.3-beta",
|
|
29
|
-
"@dashforge/
|
|
30
|
-
"@dashforge/
|
|
34
|
+
"@dashforge/rbac": "0.2.3-beta",
|
|
35
|
+
"@dashforge/ui-core": "0.2.3-beta"
|
|
31
36
|
},
|
|
32
37
|
"peerDependencies": {
|
|
33
38
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
4
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
5
|
+
import { Accordion } from './Accordion.js';
|
|
6
|
+
|
|
7
|
+
void React;
|
|
8
|
+
afterEach(() => cleanup());
|
|
9
|
+
|
|
10
|
+
const items = [
|
|
11
|
+
{ value: 'one', header: 'Section one', content: <p>One body</p> },
|
|
12
|
+
{ value: 'two', header: 'Section two', content: <p>Two body</p> },
|
|
13
|
+
{ value: 'three', header: 'Section three', content: <p>Three body</p> },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function regionIsOpen(bodyText: string): boolean {
|
|
17
|
+
const node = screen.queryByText(bodyText);
|
|
18
|
+
if (!node) return false;
|
|
19
|
+
const region = node.closest('[role="region"]');
|
|
20
|
+
if (!region) return false;
|
|
21
|
+
// Radix sets `hidden` attr on closed regions in jsdom.
|
|
22
|
+
return region.getAttribute('hidden') === null && !(region as HTMLElement).hidden;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('Accordion — single mode (default)', () => {
|
|
26
|
+
it('renders all triggers', () => {
|
|
27
|
+
render(<Accordion items={items} />);
|
|
28
|
+
expect(screen.queryByText('Section one')).not.toBeNull();
|
|
29
|
+
expect(screen.queryByText('Section two')).not.toBeNull();
|
|
30
|
+
expect(screen.queryByText('Section three')).not.toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('opens a panel on trigger click', () => {
|
|
34
|
+
render(<Accordion items={items} />);
|
|
35
|
+
expect(regionIsOpen('Two body')).toBe(false);
|
|
36
|
+
fireEvent.click(screen.getByText('Section two'));
|
|
37
|
+
expect(regionIsOpen('Two body')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('closes the open panel when clicked again (collapsible default)', () => {
|
|
41
|
+
render(<Accordion items={items} />);
|
|
42
|
+
const trigger = screen.getByText('Section two');
|
|
43
|
+
fireEvent.click(trigger);
|
|
44
|
+
expect(regionIsOpen('Two body')).toBe(true);
|
|
45
|
+
fireEvent.click(trigger);
|
|
46
|
+
expect(regionIsOpen('Two body')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('respects defaultValue', () => {
|
|
50
|
+
render(<Accordion items={items} defaultValue="three" />);
|
|
51
|
+
expect(regionIsOpen('Three body')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('emits onValueChange', () => {
|
|
55
|
+
let last: string | null = null;
|
|
56
|
+
render(<Accordion items={items} onValueChange={(v) => (last = v)} />);
|
|
57
|
+
fireEvent.click(screen.getByText('Section one'));
|
|
58
|
+
expect(last).toBe('one');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Accordion — multiple mode', () => {
|
|
63
|
+
it('allows multiple panels open simultaneously', () => {
|
|
64
|
+
render(<Accordion type="multiple" items={items} defaultValue={['one', 'three']} />);
|
|
65
|
+
expect(regionIsOpen('One body')).toBe(true);
|
|
66
|
+
expect(regionIsOpen('Three body')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('emits onValueChange with the updated array', () => {
|
|
70
|
+
let last: string[] = [];
|
|
71
|
+
render(
|
|
72
|
+
<Accordion
|
|
73
|
+
type="multiple"
|
|
74
|
+
items={items}
|
|
75
|
+
defaultValue={['one']}
|
|
76
|
+
onValueChange={(v) => (last = v)}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
fireEvent.click(screen.getByText('Section two'));
|
|
80
|
+
expect(last).toEqual(expect.arrayContaining(['one', 'two']));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('Accordion — disabled items', () => {
|
|
85
|
+
it('does not open a disabled section', () => {
|
|
86
|
+
const disabledItems = [
|
|
87
|
+
...items.slice(0, 2),
|
|
88
|
+
{ ...items[2]!, disabled: true },
|
|
89
|
+
];
|
|
90
|
+
render(<Accordion items={disabledItems} />);
|
|
91
|
+
const trigger = screen.getByText('Section three');
|
|
92
|
+
fireEvent.click(trigger);
|
|
93
|
+
expect(regionIsOpen('Three body')).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as RadixAccordion from '@radix-ui/react-accordion';
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
import { accordionVariants } from './accordion.variants.js';
|
|
4
|
+
import type {
|
|
5
|
+
AccordionProps,
|
|
6
|
+
AccordionItem,
|
|
7
|
+
AccordionSlotProps,
|
|
8
|
+
} from './accordion.types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dashforge TW `<Accordion>` — collapsible section list.
|
|
12
|
+
*
|
|
13
|
+
* Built on `@radix-ui/react-accordion`:
|
|
14
|
+
* - APG accordion pattern: `role="region"` on content, header
|
|
15
|
+
* button with `aria-expanded` and `aria-controls` linked to
|
|
16
|
+
* the panel.
|
|
17
|
+
* - Keyboard support: Space / Enter toggle, ArrowDown / ArrowUp
|
|
18
|
+
* move focus between triggers, Home / End to extremes.
|
|
19
|
+
* - Two modes:
|
|
20
|
+
* - `type="single"` — at most one section open at a time;
|
|
21
|
+
* `collapsible` allows clicking the open section to close it
|
|
22
|
+
* (default true).
|
|
23
|
+
* - `type="multiple"` — multiple sections open simultaneously.
|
|
24
|
+
*
|
|
25
|
+
* The trigger renders a chevron that flips 180° via CSS
|
|
26
|
+
* (`data-state=open` selector) — no React state, no JS animation.
|
|
27
|
+
*/
|
|
28
|
+
export function Accordion(props: AccordionProps) {
|
|
29
|
+
const v = accordionVariants();
|
|
30
|
+
const { items, sx, slotProps } = props;
|
|
31
|
+
|
|
32
|
+
const renderItems = (items: AccordionItem[], slotProps?: AccordionSlotProps) =>
|
|
33
|
+
items.map((item) => (
|
|
34
|
+
<RadixAccordion.Item
|
|
35
|
+
key={item.value}
|
|
36
|
+
value={item.value}
|
|
37
|
+
disabled={item.disabled}
|
|
38
|
+
className={cn(v.item(), slotProps?.item?.className)}
|
|
39
|
+
>
|
|
40
|
+
<RadixAccordion.Header
|
|
41
|
+
className={cn(v.header(), slotProps?.header?.className)}
|
|
42
|
+
>
|
|
43
|
+
<RadixAccordion.Trigger
|
|
44
|
+
className={cn(v.trigger(), slotProps?.trigger?.className)}
|
|
45
|
+
>
|
|
46
|
+
{item.header}
|
|
47
|
+
<svg
|
|
48
|
+
className={cn(v.chevron(), slotProps?.chevron?.className)}
|
|
49
|
+
width="1em"
|
|
50
|
+
height="1em"
|
|
51
|
+
viewBox="0 0 16 16"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth="1.5"
|
|
55
|
+
strokeLinecap="round"
|
|
56
|
+
strokeLinejoin="round"
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
>
|
|
59
|
+
<path d="M4 6l4 4 4-4" />
|
|
60
|
+
</svg>
|
|
61
|
+
</RadixAccordion.Trigger>
|
|
62
|
+
</RadixAccordion.Header>
|
|
63
|
+
<RadixAccordion.Content
|
|
64
|
+
className={cn(v.content(), slotProps?.content?.className)}
|
|
65
|
+
>
|
|
66
|
+
<div className="pb-3 pt-0">{item.content}</div>
|
|
67
|
+
</RadixAccordion.Content>
|
|
68
|
+
</RadixAccordion.Item>
|
|
69
|
+
));
|
|
70
|
+
|
|
71
|
+
if (props.type === 'multiple') {
|
|
72
|
+
return (
|
|
73
|
+
<RadixAccordion.Root
|
|
74
|
+
type="multiple"
|
|
75
|
+
value={props.value}
|
|
76
|
+
defaultValue={props.defaultValue}
|
|
77
|
+
onValueChange={props.onValueChange}
|
|
78
|
+
className={cn(v.root(), sx, slotProps?.root?.className)}
|
|
79
|
+
>
|
|
80
|
+
{renderItems(items, slotProps)}
|
|
81
|
+
</RadixAccordion.Root>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<RadixAccordion.Root
|
|
87
|
+
type="single"
|
|
88
|
+
collapsible={props.collapsible ?? true}
|
|
89
|
+
value={props.value}
|
|
90
|
+
defaultValue={props.defaultValue}
|
|
91
|
+
onValueChange={props.onValueChange}
|
|
92
|
+
className={cn(v.root(), sx, slotProps?.root?.className)}
|
|
93
|
+
>
|
|
94
|
+
{renderItems(items, slotProps)}
|
|
95
|
+
</RadixAccordion.Root>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface AccordionSlotProps {
|
|
4
|
+
root?: { className?: string };
|
|
5
|
+
item?: { className?: string };
|
|
6
|
+
header?: { className?: string };
|
|
7
|
+
trigger?: { className?: string };
|
|
8
|
+
content?: { className?: string };
|
|
9
|
+
chevron?: { className?: string };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Single accordion section — paired `{ value, header, content }`.
|
|
14
|
+
* The `value` is the canonical id (used in `value` / `defaultValue`
|
|
15
|
+
* to control which sections are expanded).
|
|
16
|
+
*/
|
|
17
|
+
export interface AccordionItem {
|
|
18
|
+
value: string;
|
|
19
|
+
/** Trigger label (clickable to expand / collapse). */
|
|
20
|
+
header: ReactNode;
|
|
21
|
+
/** Section body, revealed when this item is expanded. */
|
|
22
|
+
content: ReactNode;
|
|
23
|
+
/** Per-item disabled flag. */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AccordionBaseProps {
|
|
28
|
+
items: AccordionItem[];
|
|
29
|
+
/** Root className shortcut. */
|
|
30
|
+
sx?: string;
|
|
31
|
+
/** Per-slot overrides. */
|
|
32
|
+
slotProps?: AccordionSlotProps;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Single-mode props — at most one section open at a time.
|
|
37
|
+
* `collapsible: true` (default) lets the user close the open
|
|
38
|
+
* section by clicking it again.
|
|
39
|
+
*/
|
|
40
|
+
export interface AccordionSingleProps extends AccordionBaseProps {
|
|
41
|
+
type?: 'single';
|
|
42
|
+
value?: string;
|
|
43
|
+
defaultValue?: string;
|
|
44
|
+
onValueChange?: (value: string) => void;
|
|
45
|
+
/** @default true */
|
|
46
|
+
collapsible?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Multiple-mode props — any number of sections open simultaneously.
|
|
51
|
+
*/
|
|
52
|
+
export interface AccordionMultipleProps extends AccordionBaseProps {
|
|
53
|
+
type: 'multiple';
|
|
54
|
+
value?: string[];
|
|
55
|
+
defaultValue?: string[];
|
|
56
|
+
onValueChange?: (value: string[]) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Props for `<Accordion>`.
|
|
61
|
+
*
|
|
62
|
+
* Built on `@radix-ui/react-accordion`. Discriminated by `type`:
|
|
63
|
+
* - `type="single"` (default) — `value` is `string`.
|
|
64
|
+
* - `type="multiple"` — `value` is `string[]`.
|
|
65
|
+
*/
|
|
66
|
+
export type AccordionProps = AccordionSingleProps | AccordionMultipleProps;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
2
|
+
|
|
3
|
+
export const accordionVariants = tv({
|
|
4
|
+
slots: {
|
|
5
|
+
root: 'w-full',
|
|
6
|
+
item: 'border-b border-neutral-200 dark:border-neutral-800',
|
|
7
|
+
header: 'flex',
|
|
8
|
+
trigger: [
|
|
9
|
+
'flex flex-1 items-center justify-between py-3 text-left',
|
|
10
|
+
'text-sm font-medium text-neutral-900 dark:text-neutral-50',
|
|
11
|
+
'transition-all hover:underline',
|
|
12
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:rounded',
|
|
13
|
+
'[&[data-state=open]>svg]:rotate-180',
|
|
14
|
+
'disabled:opacity-50 disabled:pointer-events-none',
|
|
15
|
+
'motion-reduce:transition-none',
|
|
16
|
+
],
|
|
17
|
+
content: [
|
|
18
|
+
'overflow-hidden text-sm text-neutral-700 dark:text-neutral-300',
|
|
19
|
+
'data-[state=open]:animate-accordion-down',
|
|
20
|
+
'data-[state=closed]:animate-accordion-up',
|
|
21
|
+
'motion-reduce:animate-none',
|
|
22
|
+
],
|
|
23
|
+
chevron: [
|
|
24
|
+
'h-4 w-4 shrink-0 text-neutral-500 transition-transform duration-200',
|
|
25
|
+
'motion-reduce:transition-none motion-reduce:duration-0',
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type AccordionVariants = VariantProps<typeof accordionVariants>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
4
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
5
|
+
import { Dialog } from './Dialog.js';
|
|
6
|
+
|
|
7
|
+
void React;
|
|
8
|
+
afterEach(() => cleanup());
|
|
9
|
+
|
|
10
|
+
describe('Dialog — rendering', () => {
|
|
11
|
+
it('renders nothing when open=false', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Dialog open={false} onOpenChange={() => {}}>
|
|
14
|
+
<p>body</p>
|
|
15
|
+
</Dialog>
|
|
16
|
+
);
|
|
17
|
+
expect(screen.queryByText('body')).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders title + body + close button when open', () => {
|
|
21
|
+
render(
|
|
22
|
+
<Dialog open onOpenChange={() => {}} title="Confirm action">
|
|
23
|
+
<p>body content</p>
|
|
24
|
+
</Dialog>
|
|
25
|
+
);
|
|
26
|
+
expect(screen.queryByText('Confirm action')).not.toBeNull();
|
|
27
|
+
expect(screen.queryByText('body content')).not.toBeNull();
|
|
28
|
+
expect(screen.queryByLabelText('Close')).not.toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('honors showCloseButton={false}', () => {
|
|
32
|
+
render(
|
|
33
|
+
<Dialog open onOpenChange={() => {}} showCloseButton={false}>
|
|
34
|
+
<p>body</p>
|
|
35
|
+
</Dialog>
|
|
36
|
+
);
|
|
37
|
+
expect(screen.queryByLabelText('Close')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('emits onOpenChange(false) when close button is clicked', () => {
|
|
41
|
+
let lastOpen: boolean | null = null;
|
|
42
|
+
render(
|
|
43
|
+
<Dialog open onOpenChange={(o) => (lastOpen = o)}>
|
|
44
|
+
<p>body</p>
|
|
45
|
+
</Dialog>
|
|
46
|
+
);
|
|
47
|
+
fireEvent.click(screen.getByLabelText('Close'));
|
|
48
|
+
expect(lastOpen).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders description when provided', () => {
|
|
52
|
+
render(
|
|
53
|
+
<Dialog open onOpenChange={() => {}} title="Title" description="Hint text">
|
|
54
|
+
<p>body</p>
|
|
55
|
+
</Dialog>
|
|
56
|
+
);
|
|
57
|
+
expect(screen.queryByText('Hint text')).not.toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Dialog — size variants', () => {
|
|
62
|
+
it.each(['sm', 'md', 'lg'] as const)('renders size=%s without crashing', (size) => {
|
|
63
|
+
render(
|
|
64
|
+
<Dialog open onOpenChange={() => {}} size={size}>
|
|
65
|
+
<p>body</p>
|
|
66
|
+
</Dialog>
|
|
67
|
+
);
|
|
68
|
+
expect(screen.queryByText('body')).not.toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Dialog — sx + slotProps', () => {
|
|
73
|
+
it('appends sx to the content element', () => {
|
|
74
|
+
render(
|
|
75
|
+
<Dialog open onOpenChange={() => {}} sx="custom-sx-class">
|
|
76
|
+
<p>body</p>
|
|
77
|
+
</Dialog>
|
|
78
|
+
);
|
|
79
|
+
const content = screen.getByText('body').closest('[role="dialog"]');
|
|
80
|
+
expect(content?.className).toContain('custom-sx-class');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('applies slotProps.title.className', () => {
|
|
84
|
+
render(
|
|
85
|
+
<Dialog
|
|
86
|
+
open
|
|
87
|
+
onOpenChange={() => {}}
|
|
88
|
+
title="T"
|
|
89
|
+
slotProps={{ title: { className: 'tw-title-override' } }}
|
|
90
|
+
>
|
|
91
|
+
<p>body</p>
|
|
92
|
+
</Dialog>
|
|
93
|
+
);
|
|
94
|
+
expect(screen.getByText('T').className).toContain('tw-title-override');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
import { dialogVariants } from './dialog.variants.js';
|
|
4
|
+
import type { DialogProps } from './dialog.types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dashforge TW `<Dialog>` — declarative modal dialog.
|
|
8
|
+
*
|
|
9
|
+
* Built on `@radix-ui/react-dialog`:
|
|
10
|
+
* - APG dialog pattern out of the box (focus trap, restore focus on
|
|
11
|
+
* close, Esc-to-close, `aria-modal`, `aria-labelledby`).
|
|
12
|
+
* - Renders into a portal (no z-index hell).
|
|
13
|
+
* - Scroll lock on `<body>` while open.
|
|
14
|
+
*
|
|
15
|
+
* Use the imperative `useDialog()` (export from this same module) if
|
|
16
|
+
* you want a `confirm()`-style return value pattern (similar to
|
|
17
|
+
* `useConfirm()` from `ConfirmDialog`). Use `<Dialog>` when you want
|
|
18
|
+
* the control flow living in your component tree.
|
|
19
|
+
*/
|
|
20
|
+
export function Dialog(props: DialogProps) {
|
|
21
|
+
const {
|
|
22
|
+
open,
|
|
23
|
+
onOpenChange,
|
|
24
|
+
children,
|
|
25
|
+
title,
|
|
26
|
+
description,
|
|
27
|
+
showCloseButton = true,
|
|
28
|
+
disableBackdropClose = false,
|
|
29
|
+
disableEscapeClose = false,
|
|
30
|
+
size = 'md',
|
|
31
|
+
sx,
|
|
32
|
+
slotProps,
|
|
33
|
+
} = props;
|
|
34
|
+
|
|
35
|
+
const v = dialogVariants({ size });
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<RadixDialog.Root open={open} onOpenChange={onOpenChange}>
|
|
39
|
+
<RadixDialog.Portal>
|
|
40
|
+
<RadixDialog.Overlay
|
|
41
|
+
className={cn(v.overlay(), slotProps?.overlay?.className)}
|
|
42
|
+
/>
|
|
43
|
+
<RadixDialog.Content
|
|
44
|
+
className={cn(v.content(), sx, slotProps?.content?.className)}
|
|
45
|
+
onPointerDownOutside={(event) => {
|
|
46
|
+
if (disableBackdropClose) event.preventDefault();
|
|
47
|
+
}}
|
|
48
|
+
onEscapeKeyDown={(event) => {
|
|
49
|
+
if (disableEscapeClose) event.preventDefault();
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{title != null && (
|
|
53
|
+
<RadixDialog.Title
|
|
54
|
+
className={cn(v.title(), slotProps?.title?.className)}
|
|
55
|
+
>
|
|
56
|
+
{title}
|
|
57
|
+
</RadixDialog.Title>
|
|
58
|
+
)}
|
|
59
|
+
{description != null && (
|
|
60
|
+
<RadixDialog.Description
|
|
61
|
+
className={cn(v.description(), slotProps?.description?.className)}
|
|
62
|
+
>
|
|
63
|
+
{description}
|
|
64
|
+
</RadixDialog.Description>
|
|
65
|
+
)}
|
|
66
|
+
<div className={cn(v.body(), slotProps?.body?.className)}>
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
{showCloseButton && (
|
|
70
|
+
<RadixDialog.Close
|
|
71
|
+
aria-label="Close"
|
|
72
|
+
className={cn(v.closeButton(), slotProps?.closeButton?.className)}
|
|
73
|
+
>
|
|
74
|
+
<svg
|
|
75
|
+
width="1em"
|
|
76
|
+
height="1em"
|
|
77
|
+
viewBox="0 0 16 16"
|
|
78
|
+
fill="none"
|
|
79
|
+
stroke="currentColor"
|
|
80
|
+
strokeWidth="1.5"
|
|
81
|
+
strokeLinecap="round"
|
|
82
|
+
strokeLinejoin="round"
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
>
|
|
85
|
+
<path d="M4 4l8 8M12 4l-8 8" />
|
|
86
|
+
</svg>
|
|
87
|
+
</RadixDialog.Close>
|
|
88
|
+
)}
|
|
89
|
+
</RadixDialog.Content>
|
|
90
|
+
</RadixDialog.Portal>
|
|
91
|
+
</RadixDialog.Root>
|
|
92
|
+
);
|
|
93
|
+
}
|