@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/PARITY.md +483 -0
  3. package/PERFORMANCE.md +169 -0
  4. package/dist/index.esm.js +1055 -2
  5. package/dist/src/components/Accordion/Accordion.d.ts +21 -0
  6. package/dist/src/components/Accordion/Accordion.d.ts.map +1 -0
  7. package/dist/src/components/Accordion/accordion.types.d.ts +74 -0
  8. package/dist/src/components/Accordion/accordion.types.d.ts.map +1 -0
  9. package/dist/src/components/Accordion/accordion.variants.d.ts +58 -0
  10. package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -0
  11. package/dist/src/components/Dialog/Dialog.d.ts +17 -0
  12. package/dist/src/components/Dialog/Dialog.d.ts.map +1 -0
  13. package/dist/src/components/Dialog/dialog.types.d.ts +75 -0
  14. package/dist/src/components/Dialog/dialog.types.d.ts.map +1 -0
  15. package/dist/src/components/Dialog/dialog.variants.d.ts +80 -0
  16. package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -0
  17. package/dist/src/components/Pagination/Pagination.d.ts +32 -0
  18. package/dist/src/components/Pagination/Pagination.d.ts.map +1 -0
  19. package/dist/src/components/Pagination/pagination.helpers.d.ts +23 -0
  20. package/dist/src/components/Pagination/pagination.helpers.d.ts.map +1 -0
  21. package/dist/src/components/Pagination/pagination.types.d.ts +120 -0
  22. package/dist/src/components/Pagination/pagination.types.d.ts.map +1 -0
  23. package/dist/src/components/Pagination/pagination.variants.d.ts +150 -0
  24. package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -0
  25. package/dist/src/components/Popover/Popover.d.ts +15 -0
  26. package/dist/src/components/Popover/Popover.d.ts.map +1 -0
  27. package/dist/src/components/Popover/popover.types.d.ts +45 -0
  28. package/dist/src/components/Popover/popover.types.d.ts.map +1 -0
  29. package/dist/src/components/Popover/popover.variants.d.ts +34 -0
  30. package/dist/src/components/Popover/popover.variants.d.ts.map +1 -0
  31. package/dist/src/components/Skeleton/Skeleton.d.ts +23 -0
  32. package/dist/src/components/Skeleton/Skeleton.d.ts.map +1 -0
  33. package/dist/src/components/Skeleton/skeleton.types.d.ts +56 -0
  34. package/dist/src/components/Skeleton/skeleton.types.d.ts.map +1 -0
  35. package/dist/src/components/Skeleton/skeleton.variants.d.ts +110 -0
  36. package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -0
  37. package/dist/src/components/Tabs/Tabs.d.ts +15 -0
  38. package/dist/src/components/Tabs/Tabs.d.ts.map +1 -0
  39. package/dist/src/components/Tabs/tabs.types.d.ts +55 -0
  40. package/dist/src/components/Tabs/tabs.types.d.ts.map +1 -0
  41. package/dist/src/components/Tabs/tabs.variants.d.ts +82 -0
  42. package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -0
  43. package/dist/src/components/Tooltip/Tooltip.d.ts +29 -0
  44. package/dist/src/components/Tooltip/Tooltip.d.ts.map +1 -0
  45. package/dist/src/components/Tooltip/tooltip.types.d.ts +47 -0
  46. package/dist/src/components/Tooltip/tooltip.types.d.ts.map +1 -0
  47. package/dist/src/components/Tooltip/tooltip.variants.d.ts +34 -0
  48. package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -0
  49. package/dist/src/index.d.ts +22 -1
  50. package/dist/src/index.d.ts.map +1 -1
  51. package/package.json +8 -3
  52. package/src/components/Accordion/Accordion.test.tsx +95 -0
  53. package/src/components/Accordion/Accordion.tsx +97 -0
  54. package/src/components/Accordion/accordion.types.ts +66 -0
  55. package/src/components/Accordion/accordion.variants.ts +30 -0
  56. package/src/components/Dialog/Dialog.test.tsx +96 -0
  57. package/src/components/Dialog/Dialog.tsx +93 -0
  58. package/src/components/Dialog/dialog.types.ts +62 -0
  59. package/src/components/Dialog/dialog.variants.ts +62 -0
  60. package/src/components/Pagination/Pagination.test.tsx +314 -0
  61. package/src/components/Pagination/Pagination.tsx +275 -0
  62. package/src/components/Pagination/pagination.helpers.ts +102 -0
  63. package/src/components/Pagination/pagination.types.ts +117 -0
  64. package/src/components/Pagination/pagination.variants.ts +111 -0
  65. package/src/components/Popover/Popover.test.tsx +72 -0
  66. package/src/components/Popover/Popover.tsx +55 -0
  67. package/src/components/Popover/popover.types.ts +42 -0
  68. package/src/components/Popover/popover.variants.ts +18 -0
  69. package/src/components/Skeleton/Skeleton.test.tsx +127 -0
  70. package/src/components/Skeleton/Skeleton.tsx +49 -0
  71. package/src/components/Skeleton/skeleton.types.ts +59 -0
  72. package/src/components/Skeleton/skeleton.variants.ts +86 -0
  73. package/src/components/Tabs/Tabs.test.tsx +74 -0
  74. package/src/components/Tabs/Tabs.tsx +66 -0
  75. package/src/components/Tabs/tabs.types.ts +49 -0
  76. package/src/components/Tabs/tabs.variants.ts +58 -0
  77. package/src/components/Tooltip/Tooltip.test.tsx +73 -0
  78. package/src/components/Tooltip/Tooltip.tsx +70 -0
  79. package/src/components/Tooltip/tooltip.types.ts +44 -0
  80. package/src/components/Tooltip/tooltip.variants.ts +18 -0
  81. 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"}
@@ -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.3.0-beta";
124
+ export declare const VERSION = "0.5.0-beta";
104
125
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAExE,OAAO,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAChE,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAE9E,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,YAAY,EACV,WAAW,EACX,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAIxE,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AACnE,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gDAAgD,CAAC;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAI9E,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AACtE,YAAY,EACV,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AAEvF,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAI9E,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AACzE,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAC;AAI1F,OAAO,EACL,cAAc,EACd,eAAe,GAChB,MAAM,+CAA+C,CAAC;AACvD,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAC;AAIhG,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AACtE,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AAEvF,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAC1D,YAAY,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAE3E,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,YAAY,EACV,WAAW,EACX,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAI9E,OAAO,EACL,qBAAqB,EACrB,UAAU,GACX,MAAM,6CAA6C,CAAC;AACrD,YAAY,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAE7F,OAAO,EACL,gBAAgB,EAChB,WAAW,GACZ,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAO9E,OAAO,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gDAAgD,CAAC;AAEpF,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAC9C,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAErE,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACjD,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,aAAa,EACb,OAAO,EACP,eAAe,GAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAOlE,OAAO,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAEjF,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAEtF,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,YAAY,EAAE,mBAAmB,EAAE,MAAM,qDAAqD,CAAC;AAG/F,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAInC,OAAO,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,OAAO,eAAe,CAAC"}
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.0-beta",
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/ui-core": "0.2.3-beta",
30
- "@dashforge/rbac": "0.2.3-beta"
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
+ }