@godxjp/ui 18.15.4 → 19.1.0

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 (154) hide show
  1. package/dist/app/app-provider.d.ts +1 -1
  2. package/dist/app/app-provider.js +3 -0
  3. package/dist/app/date-format-labels.d.ts +1 -1
  4. package/dist/app/time-format-labels.d.ts +1 -1
  5. package/dist/components/data-display/badge.d.ts +1 -1
  6. package/dist/components/data-display/card.d.ts +4 -13
  7. package/dist/components/data-display/card.js +2 -15
  8. package/dist/components/data-display/data-table.d.ts +3 -8
  9. package/dist/components/data-display/data-table.js +106 -68
  10. package/dist/components/data-display/descriptions.js +34 -5
  11. package/dist/components/data-display/empty-state.js +1 -1
  12. package/dist/components/data-display/permission-matrix.js +7 -7
  13. package/dist/components/data-display/popover.js +2 -9
  14. package/dist/components/data-display/qr-code.d.ts +1 -1
  15. package/dist/components/data-display/scroll-area.d.ts +3 -1
  16. package/dist/components/data-display/scroll-area.js +150 -16
  17. package/dist/components/data-display/table.js +12 -3
  18. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  19. package/dist/components/data-entry/calendar.js +15 -24
  20. package/dist/components/data-entry/cascader.js +70 -72
  21. package/dist/components/data-entry/checkbox-group.js +12 -5
  22. package/dist/components/data-entry/checkbox.js +30 -13
  23. package/dist/components/data-entry/command.d.ts +6 -6
  24. package/dist/components/data-entry/date-picker.js +34 -17
  25. package/dist/components/data-entry/date-range-picker.js +19 -13
  26. package/dist/components/data-entry/form-field.d.ts +1 -1
  27. package/dist/components/data-entry/form-field.js +22 -5
  28. package/dist/components/data-entry/index.d.ts +2 -1
  29. package/dist/components/data-entry/input.js +18 -22
  30. package/dist/components/data-entry/label.js +4 -2
  31. package/dist/components/data-entry/month-picker.js +20 -17
  32. package/dist/components/data-entry/month-range-picker.js +26 -19
  33. package/dist/components/data-entry/radio.js +25 -6
  34. package/dist/components/data-entry/search-select.d.ts +1 -1
  35. package/dist/components/data-entry/search-select.js +37 -31
  36. package/dist/components/data-entry/select.d.ts +8 -0
  37. package/dist/components/data-entry/select.js +68 -26
  38. package/dist/components/data-entry/switch.js +16 -2
  39. package/dist/components/data-entry/textarea.d.ts +56 -0
  40. package/dist/components/data-entry/textarea.js +110 -40
  41. package/dist/components/data-entry/time-picker.js +52 -52
  42. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  43. package/dist/components/data-entry/toggle.d.ts +1 -1
  44. package/dist/components/data-entry/transfer.js +14 -15
  45. package/dist/components/data-entry/tree-select.js +19 -23
  46. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  47. package/dist/components/data-entry/upload.d.ts +1 -1
  48. package/dist/components/data-entry/upload.js +46 -46
  49. package/dist/components/feedback/alert.js +7 -14
  50. package/dist/components/feedback/dialog.d.ts +23 -32
  51. package/dist/components/feedback/dialog.js +16 -13
  52. package/dist/components/feedback/sheet.d.ts +1 -1
  53. package/dist/components/feedback/sheet.js +14 -14
  54. package/dist/components/feedback/skeleton.js +13 -10
  55. package/dist/components/feedback/sonner.d.ts +1 -1
  56. package/dist/components/feedback/sonner.js +17 -8
  57. package/dist/components/feedback/tooltip.js +1 -1
  58. package/dist/components/general/activity.d.ts +44 -0
  59. package/dist/components/general/activity.js +65 -0
  60. package/dist/components/general/button.d.ts +2 -2
  61. package/dist/components/general/button.js +11 -24
  62. package/dist/components/general/index.d.ts +2 -0
  63. package/dist/components/general/index.js +2 -0
  64. package/dist/components/layout/app-shell.js +5 -3
  65. package/dist/components/layout/auth-divider.d.ts +9 -2
  66. package/dist/components/layout/auth-divider.js +7 -11
  67. package/dist/components/layout/index.d.ts +1 -0
  68. package/dist/components/layout/page-container.d.ts +1 -1
  69. package/dist/components/layout/page-container.js +32 -20
  70. package/dist/components/layout/resizable.d.ts +12 -12
  71. package/dist/components/layout/separator.d.ts +1 -0
  72. package/dist/components/layout/service-role-panel.js +47 -42
  73. package/dist/components/layout/sidebar.js +13 -1
  74. package/dist/components/layout/split-pane.d.ts +12 -1
  75. package/dist/components/layout/split-pane.js +13 -4
  76. package/dist/components/navigation/app-setting-picker.js +5 -14
  77. package/dist/components/navigation/dropdown-menu.js +10 -16
  78. package/dist/components/navigation/menubar.d.ts +3 -6
  79. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  80. package/dist/components/navigation/pagination-utils.js +27 -21
  81. package/dist/components/navigation/pagination.d.ts +4 -4
  82. package/dist/components/navigation/pagination.js +2 -2
  83. package/dist/components/navigation/steps.js +31 -67
  84. package/dist/components/navigation/tabs.js +17 -5
  85. package/dist/components/query/infinite-query-state.js +2 -2
  86. package/dist/components/ui/accordion.d.ts +1 -1
  87. package/dist/components/ui/avatar.d.ts +1 -1
  88. package/dist/components/ui/avatar.js +29 -10
  89. package/dist/components/ui/input-otp.d.ts +8 -6
  90. package/dist/components/ui/rating.js +1 -1
  91. package/dist/components/ui/separator.d.ts +27 -2
  92. package/dist/components/ui/separator.js +42 -11
  93. package/dist/components/ui/toggle-group.d.ts +15 -5
  94. package/dist/components/ui/toggle-group.js +43 -18
  95. package/dist/components/ui/toggle.d.ts +66 -5
  96. package/dist/components/ui/toggle.js +64 -9
  97. package/dist/i18n/messages/en.json +8 -0
  98. package/dist/i18n/messages/ja.json +8 -0
  99. package/dist/i18n/messages/vi.json +8 -0
  100. package/dist/lib/control-styles.d.ts +14 -0
  101. package/dist/lib/control-styles.js +2 -0
  102. package/dist/lib/field-a11y.d.ts +52 -0
  103. package/dist/lib/field-a11y.js +17 -2
  104. package/dist/props/components/app.prop.d.ts +15 -0
  105. package/dist/props/components/data-display.prop.d.ts +85 -3
  106. package/dist/props/components/data-entry.prop.d.ts +35 -2
  107. package/dist/props/components/general.prop.d.ts +52 -1
  108. package/dist/props/components/layout.prop.d.ts +137 -2
  109. package/dist/props/registry.d.ts +116 -5
  110. package/dist/props/registry.js +165 -4
  111. package/dist/props/vocabulary/index.d.ts +2 -2
  112. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  113. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  114. package/dist/styles/alert-layout.css +69 -3
  115. package/dist/styles/badge-layout.css +2 -2
  116. package/dist/styles/base.css +6 -0
  117. package/dist/styles/card-layout.css +25 -8
  118. package/dist/styles/control.css +1150 -29
  119. package/dist/styles/data-display-layout.css +185 -11
  120. package/dist/styles/data-entry-layout.css +352 -1
  121. package/dist/styles/density.css +2 -2
  122. package/dist/styles/dialog-layout.css +203 -4
  123. package/dist/styles/focus-ring.css +155 -0
  124. package/dist/styles/form-layout.css +50 -5
  125. package/dist/styles/index.css +1 -0
  126. package/dist/styles/layout.css +412 -23
  127. package/dist/styles/motion.css +178 -0
  128. package/dist/styles/navigation-layout.css +398 -22
  129. package/dist/styles/shell-layout.css +137 -39
  130. package/dist/styles/table-layout.css +153 -22
  131. package/dist/styles/toggle.css +59 -0
  132. package/dist/tokens/base.css +5 -0
  133. package/dist/tokens/components/activity.css +64 -0
  134. package/dist/tokens/components/badge.css +7 -0
  135. package/dist/tokens/components/card.css +14 -10
  136. package/dist/tokens/components/chart.css +1 -1
  137. package/dist/tokens/components/control.css +358 -8
  138. package/dist/tokens/components/data-display.css +75 -0
  139. package/dist/tokens/components/data-entry.css +25 -0
  140. package/dist/tokens/components/descriptions.css +22 -0
  141. package/dist/tokens/components/feedback.css +143 -4
  142. package/dist/tokens/components/form.css +16 -0
  143. package/dist/tokens/components/legal-document.css +5 -1
  144. package/dist/tokens/components/navigation.css +109 -0
  145. package/dist/tokens/components/scroll-area.css +21 -0
  146. package/dist/tokens/components/separator.css +52 -0
  147. package/dist/tokens/components/shell.css +132 -20
  148. package/dist/tokens/components/table.css +52 -3
  149. package/dist/tokens/components/toggle.css +82 -0
  150. package/dist/tokens/components/upload.css +107 -0
  151. package/dist/tokens/foundation.css +171 -10
  152. package/dist/tokens/semantic/layout.css +120 -1
  153. package/package.json +85 -68
  154. package/scripts/ui-audit.mjs +51 -2
@@ -20,7 +20,7 @@ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffs
20
20
  align,
21
21
  sideOffset,
22
22
  className: cn(
23
- "bg-popover text-popover-foreground z-50 w-72 origin-[var(--radix-popover-content-transform-origin)] rounded-md border p-4 shadow-md outline-none",
23
+ "ui-popover-content origin-[var(--radix-popover-content-transform-origin)]",
24
24
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
25
25
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
26
26
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
@@ -31,14 +31,7 @@ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffs
31
31
  }
32
32
  ) }));
33
33
  PopoverContent.displayName = PopoverPrimitive.Content.displayName;
34
- const PopoverHeader = ({ className, ...props }) => /* @__PURE__ */ jsx(
35
- "div",
36
- {
37
- "data-slot": "popover-header",
38
- className: cn("flex flex-col gap-1 text-sm", className),
39
- ...props
40
- }
41
- );
34
+ const PopoverHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { "data-slot": "popover-header", className: cn("ui-popover-header", className), ...props });
42
35
  const PopoverTitle = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { "data-slot": "popover-title", className: cn("font-medium", className), ...props });
43
36
  const PopoverDescription = ({
44
37
  className,
@@ -5,4 +5,4 @@ export type QrCodeProps = QrCodeProp & Omit<React.ComponentPropsWithoutRef<"svg"
5
5
  * Renders a QR code entirely in-process. The encoded value is never sent to a network service,
6
6
  * written to an element attribute, or included in accessible text.
7
7
  */
8
- export declare const QrCode: React.ForwardRefExoticComponent<QrCodeProp & Omit<Omit<React.SVGProps<SVGSVGElement>, "ref">, "style" | "role" | "children" | "type" | keyof QrCodeProp> & React.RefAttributes<SVGSVGElement>>;
8
+ export declare const QrCode: React.ForwardRefExoticComponent<QrCodeProp & Omit<Omit<React.SVGProps<SVGSVGElement>, "ref">, "children" | "role" | "style" | "type" | keyof QrCodeProp> & React.RefAttributes<SVGSVGElement>>;
@@ -1,4 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
3
- export declare const ScrollArea: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
3
+ import type { ScrollAreaProp } from "../../props/components/data-display.prop.js";
4
+ export type ScrollAreaProps = ScrollAreaProp & Omit<React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>, keyof ScrollAreaProp>;
5
+ export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref">, keyof ScrollAreaProp> & React.RefAttributes<HTMLDivElement>>;
4
6
  export declare const ScrollBar: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -3,19 +3,153 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
5
5
  import { cn } from "../../lib/utils.js";
6
- const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
7
- ScrollAreaPrimitive.Root,
8
- {
9
- ref,
10
- className: cn("relative overflow-hidden", className),
11
- ...props,
12
- children: [
13
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, { tabIndex: 0, className: "size-full rounded-[inherit]", children }),
14
- /* @__PURE__ */ jsx(ScrollBar, {}),
15
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
16
- ]
6
+ const ANCHOR_OFFSET_TOKEN = "--scroll-area-anchor-offset";
7
+ const ANCHOR_OFFSET_FALLBACK_PX = 48;
8
+ const ROOT_FONT_SIZE_FALLBACK_PX = 16;
9
+ function cssLengthToPx(value, rootFontSize) {
10
+ const match = /^(-?\d*\.?\d+)(px|rem|em)?$/.exec(value.trim());
11
+ if (match == null) return void 0;
12
+ const amount = Number(match[1]);
13
+ if (!Number.isFinite(amount)) return void 0;
14
+ return match[2] === "rem" || match[2] === "em" ? amount * rootFontSize : amount;
15
+ }
16
+ function readAnchorOffset(element) {
17
+ if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
18
+ return ANCHOR_OFFSET_FALLBACK_PX;
17
19
  }
18
- ));
20
+ const rootFontSize = cssLengthToPx(
21
+ window.getComputedStyle(element.ownerDocument.documentElement).fontSize || "",
22
+ ROOT_FONT_SIZE_FALLBACK_PX
23
+ ) ?? ROOT_FONT_SIZE_FALLBACK_PX;
24
+ const declared = window.getComputedStyle(element).getPropertyValue(ANCHOR_OFFSET_TOKEN);
25
+ return cssLengthToPx(declared, rootFontSize) ?? ANCHOR_OFFSET_FALLBACK_PX;
26
+ }
27
+ function resolveRowContainer(root) {
28
+ let container = root;
29
+ while (container.children.length === 1 && container.firstElementChild) {
30
+ container = container.firstElementChild;
31
+ }
32
+ return container;
33
+ }
34
+ function findAnchorChild(root, scrollTop) {
35
+ const children = resolveRowContainer(root).children;
36
+ let low = 0;
37
+ let high = children.length - 1;
38
+ let found = null;
39
+ while (low <= high) {
40
+ const mid = Math.floor((low + high) / 2);
41
+ const child = children[mid];
42
+ if (!(child instanceof HTMLElement)) break;
43
+ if (child.offsetTop + child.offsetHeight > scrollTop) {
44
+ found = child;
45
+ high = mid - 1;
46
+ } else {
47
+ low = mid + 1;
48
+ }
49
+ }
50
+ return found;
51
+ }
52
+ function setScrollOffset(element, top) {
53
+ if (typeof element.scrollTo === "function") element.scrollTo({ top, behavior: "instant" });
54
+ else element.scrollTop = top;
55
+ }
56
+ function useBottomAnchor(viewport, enabled, offset, onAnchoredChange) {
57
+ const onAnchoredChangeRef = React.useRef(onAnchoredChange);
58
+ React.useEffect(() => {
59
+ onAnchoredChangeRef.current = onAnchoredChange;
60
+ }, [onAnchoredChange]);
61
+ React.useEffect(() => {
62
+ if (!viewport || !enabled) return;
63
+ const content = viewport.firstElementChild ?? viewport;
64
+ const band = offset ?? readAnchorOffset(viewport);
65
+ let anchored = true;
66
+ let record = null;
67
+ const distanceFromBottom = () => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop;
68
+ const setAnchored = (next) => {
69
+ if (anchored === next) return;
70
+ anchored = next;
71
+ onAnchoredChangeRef.current?.(next);
72
+ };
73
+ const remember = () => {
74
+ const node = findAnchorChild(content, viewport.scrollTop);
75
+ record = node ? { node, top: node.offsetTop - viewport.scrollTop } : null;
76
+ };
77
+ const pinToBottom = () => {
78
+ setScrollOffset(viewport, Math.max(0, viewport.scrollHeight - viewport.clientHeight));
79
+ };
80
+ const handleScroll = () => {
81
+ setAnchored(distanceFromBottom() <= band);
82
+ remember();
83
+ };
84
+ const handleContentChange = () => {
85
+ if (anchored) {
86
+ pinToBottom();
87
+ } else if (record && viewport.contains(record.node)) {
88
+ const target = Math.max(0, record.node.offsetTop - record.top);
89
+ if (target !== viewport.scrollTop) setScrollOffset(viewport, target);
90
+ setAnchored(distanceFromBottom() <= band);
91
+ }
92
+ remember();
93
+ };
94
+ pinToBottom();
95
+ remember();
96
+ viewport.addEventListener("scroll", handleScroll, { passive: true });
97
+ const observers = [];
98
+ if (typeof MutationObserver !== "undefined") {
99
+ const mutationObserver = new MutationObserver(handleContentChange);
100
+ mutationObserver.observe(content, { childList: true, subtree: true, characterData: true });
101
+ observers.push(mutationObserver);
102
+ }
103
+ if (typeof ResizeObserver !== "undefined") {
104
+ const resizeObserver = new ResizeObserver(handleContentChange);
105
+ resizeObserver.observe(viewport);
106
+ resizeObserver.observe(content);
107
+ observers.push(resizeObserver);
108
+ }
109
+ return () => {
110
+ viewport.removeEventListener("scroll", handleScroll);
111
+ for (const observer of observers) observer.disconnect();
112
+ };
113
+ }, [viewport, enabled, offset]);
114
+ }
115
+ const ScrollArea = React.forwardRef(
116
+ ({ className, children, viewportRef, anchor = "none", anchorOffset, onAnchoredChange, ...props }, ref) => {
117
+ const [viewport, setViewport] = React.useState(null);
118
+ const attachViewport = React.useCallback(
119
+ (node) => {
120
+ setViewport(node);
121
+ if (typeof viewportRef === "function") viewportRef(node);
122
+ else if (viewportRef)
123
+ viewportRef.current = node;
124
+ },
125
+ [viewportRef]
126
+ );
127
+ useBottomAnchor(viewport, anchor === "bottom", anchorOffset, onAnchoredChange);
128
+ return /* @__PURE__ */ jsxs(
129
+ ScrollAreaPrimitive.Root,
130
+ {
131
+ ref,
132
+ className: cn("relative overflow-hidden", className),
133
+ ...props,
134
+ children: [
135
+ /* @__PURE__ */ jsx(
136
+ ScrollAreaPrimitive.Viewport,
137
+ {
138
+ ref: attachViewport,
139
+ tabIndex: 0,
140
+ "data-slot": "scroll-area-viewport",
141
+ "data-anchor": anchor,
142
+ className: "size-full rounded-[inherit]",
143
+ children
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx(ScrollBar, {}),
147
+ /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
148
+ ]
149
+ }
150
+ );
151
+ }
152
+ );
19
153
  ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
20
154
  const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(
21
155
  ScrollAreaPrimitive.ScrollAreaScrollbar,
@@ -23,13 +157,13 @@ const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...pr
23
157
  ref,
24
158
  orientation,
25
159
  className: cn(
26
- "flex touch-none transition-colors select-none",
27
- orientation === "vertical" && "h-full w-2.5 border-s border-s-transparent p-px",
28
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-px",
160
+ "ui-scroll-area-bar flex touch-none transition-colors select-none",
161
+ orientation === "vertical" && "ui-scroll-area-bar--vertical h-full border-s border-s-transparent",
162
+ orientation === "horizontal" && "ui-scroll-area-bar--horizontal flex-col border-t border-t-transparent",
29
163
  className
30
164
  ),
31
165
  ...props,
32
- children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "bg-border relative flex-1 rounded-full" })
166
+ children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "ui-scroll-area-thumb bg-border relative flex-1" })
33
167
  }
34
168
  ));
35
169
  ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
@@ -34,7 +34,7 @@ const Table = React.forwardRef(
34
34
  {
35
35
  ref,
36
36
  "data-slot": "table",
37
- className: cn("w-full caption-bottom text-sm", bordered && "ui-table-bordered", className),
37
+ className: cn("w-full caption-bottom", bordered && "ui-table-bordered", className),
38
38
  ...props
39
39
  }
40
40
  )
@@ -45,14 +45,23 @@ const Table = React.forwardRef(
45
45
  Table.displayName = "Table";
46
46
  const TableHeader = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
47
47
  TableHeader.displayName = "TableHeader";
48
- const TableBody = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props }));
48
+ const TableBody = React.forwardRef(({ className, ...props }, ref) => (
49
+ // "The last row draws no rule" is an idiom, not a service-tunable constant, so it is a CSS
50
+ // rule (`[data-slot="table-body"] .ui-table-row:last-child`) rather than a utility. That only
51
+ // works because TableRow's own bottom rule moved to `.ui-table-row` in the same layer: while
52
+ // the row carried a `border-b` UTILITY, no `components` rule could ever zero it (gh#412).
53
+ /* @__PURE__ */ jsx("tbody", { ref, "data-slot": "table-body", className: cn(className), ...props })
54
+ ));
49
55
  TableBody.displayName = "TableBody";
50
56
  const TableRow = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
51
57
  "tr",
52
58
  {
53
59
  ref,
54
60
  className: cn(
55
- "ui-table-row hover:bg-accent/70 data-[state=selected]:bg-primary/[0.06] border-b transition-colors",
61
+ // The row rule itself is `.ui-table-row` in table-layout.css (--table-row-border-width),
62
+ // NOT a `border-b` utility — a utility sits in `@layer utilities` and would outrank the
63
+ // `components` rule that has to zero it on the last row (gh#319).
64
+ "ui-table-row hover:bg-accent/70 data-[state=selected]:bg-primary/[0.06] transition-colors",
56
65
  className
57
66
  ),
58
67
  ...props
@@ -66,7 +66,7 @@ const BranchScopePicker = React.forwardRef(
66
66
  id: rootId,
67
67
  "data-slot": "branch-scope-picker",
68
68
  "data-readonly": readOnly || void 0,
69
- className: cn("ui-branch-scope-picker flex flex-col gap-3", className),
69
+ className: cn("ui-branch-scope-picker", className),
70
70
  children
71
71
  }
72
72
  );
@@ -113,7 +113,7 @@ const BranchScopePicker = React.forwardRef(
113
113
  return surface(
114
114
  /* @__PURE__ */ jsxs(Fragment, { children: [
115
115
  /* @__PURE__ */ jsx(Text, { size: "sm", weight: "medium", children: value.mode === "all" ? resolvedAllLabel : resolvedSelectedLabel }),
116
- value.mode === "selected" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedBranches.map((branch) => /* @__PURE__ */ jsx(Badge, { variant: "outline", children: branch.name }, branch.id)) })
116
+ value.mode === "selected" && /* @__PURE__ */ jsx("div", { className: "ui-branch-scope-picker-badges", children: selectedBranches.map((branch) => /* @__PURE__ */ jsx(Badge, { variant: "outline", children: branch.name }, branch.id)) })
117
117
  ] })
118
118
  );
119
119
  }
@@ -145,7 +145,7 @@ const BranchScopePicker = React.forwardRef(
145
145
  "aria-errormessage": invalid ? mergeAriaIds(errorId, fieldA11y["aria-errormessage"]) : void 0
146
146
  }
147
147
  ),
148
- value.mode === "selected" && /* @__PURE__ */ jsxs("div", { className: "border-border flex flex-col gap-2 border-s-2 ps-4", children: [
148
+ value.mode === "selected" && /* @__PURE__ */ jsxs("div", { className: "ui-branch-scope-picker-subset", children: [
149
149
  searchable && branches.length > 0 && /* @__PURE__ */ jsx(
150
150
  SearchInput,
151
151
  {
@@ -169,7 +169,7 @@ const BranchScopePicker = React.forwardRef(
169
169
  role: "group",
170
170
  "aria-label": t("dataEntry.branchScope.listLabel"),
171
171
  tabIndex: 0,
172
- className: "max-h-64 overflow-y-auto",
172
+ className: "ui-branch-scope-picker-list",
173
173
  children: /* @__PURE__ */ jsx(
174
174
  CheckboxGroup,
175
175
  {
@@ -23,50 +23,41 @@ function Calendar({
23
23
  ...labels,
24
24
  labelNav: labels?.labelNav ?? (() => `${ariaLabel ?? "Calendar"} navigation`)
25
25
  },
26
- className: cn("relative w-fit p-3", className),
26
+ className: cn("ui-calendar", className),
27
27
  classNames: {
28
- months: cn("flex flex-col gap-4 sm:flex-row", classNames?.months),
29
- month: cn("relative flex flex-col gap-4", classNames?.month),
30
- month_caption: cn(
31
- "flex h-[length:var(--control-height)] w-full items-center justify-center px-8",
32
- classNames?.month_caption
33
- ),
34
- caption_label: cn("text-sm font-medium", classNames?.caption_label),
28
+ months: cn("ui-calendar-months", classNames?.months),
29
+ month: cn("ui-calendar-month", classNames?.month),
30
+ month_caption: cn("ui-calendar-caption", classNames?.month_caption),
31
+ caption_label: cn("ui-calendar-caption-label", classNames?.caption_label),
35
32
  // z-10 is load-bearing: `month` below is position:relative and later in the DOM, so
36
33
  // without a z-index it paints OVER these buttons — they render but never receive the
37
34
  // click (the month caption swallows it) and the calendar looks frozen.
38
- nav: cn(
39
- "absolute inset-x-0 top-3 z-10 flex items-center justify-between px-1",
40
- classNames?.nav
41
- ),
35
+ nav: cn("ui-calendar-nav", classNames?.nav),
42
36
  button_previous: cn(
43
37
  buttonVariants({ variant: "outline" }),
44
38
  controlIconSmClass,
45
- "bg-transparent p-0 opacity-70 hover:opacity-100",
39
+ "ui-calendar-nav-button",
46
40
  classNames?.button_previous
47
41
  ),
48
42
  button_next: cn(
49
43
  buttonVariants({ variant: "outline" }),
50
44
  controlIconSmClass,
51
- "bg-transparent p-0 opacity-70 hover:opacity-100",
45
+ "ui-calendar-nav-button",
52
46
  classNames?.button_next
53
47
  ),
54
- month_grid: cn("mt-4 w-full border-collapse", classNames?.month_grid),
48
+ month_grid: cn("ui-calendar-grid", classNames?.month_grid),
55
49
  weekdays: cn("flex", classNames?.weekdays),
56
- weekday: cn(
57
- "w-[length:var(--control-height)] rounded-md text-xs font-normal text-muted-foreground",
58
- classNames?.weekday
59
- ),
60
- week: cn("mt-2 flex w-full", classNames?.week),
50
+ weekday: cn("ui-calendar-weekday", classNames?.weekday),
51
+ week: cn("ui-calendar-week", classNames?.week),
61
52
  day: cn(
62
- "relative p-0 text-center text-sm focus-within:relative focus-within:z-20",
53
+ "ui-calendar-day",
63
54
  "[&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50",
64
55
  "[&:has([aria-selected].day-range-end)]:rounded-e-md",
65
56
  classNames?.day
66
57
  ),
67
58
  day_button: cn(
68
59
  buttonVariants({ variant: "ghost" }),
69
- "size-[length:var(--control-height)] p-0 font-normal aria-selected:opacity-100",
60
+ "ui-calendar-day-button",
70
61
  classNames?.day_button
71
62
  ),
72
63
  range_start: cn("day-range-start rounded-s-md", classNames?.range_start),
@@ -88,14 +79,14 @@ function Calendar({
88
79
  "day-outside text-muted-foreground aria-selected:text-muted-foreground",
89
80
  classNames?.outside
90
81
  ),
91
- disabled: cn("text-muted-foreground opacity-50", classNames?.disabled),
82
+ disabled: cn("ui-calendar-day-disabled", classNames?.disabled),
92
83
  hidden: cn("invisible", classNames?.hidden),
93
84
  ...classNames
94
85
  },
95
86
  components: {
96
87
  Chevron: ({ orientation, className: chevronClassName }) => {
97
88
  const Icon = orientation === "left" ? ChevronLeft : ChevronRight;
98
- return /* @__PURE__ */ jsx(Icon, { className: cn("size-4", chevronClassName), "aria-hidden": "true" });
89
+ return /* @__PURE__ */ jsx(Icon, { className: cn("ui-calendar-chevron", chevronClassName), "aria-hidden": "true" });
99
90
  }
100
91
  },
101
92
  ...props,
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { Check, ChevronRight, ChevronsUpDown, Minus, X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { pickFieldA11y } from "../../lib/field-a11y.js";
7
+ import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
8
8
  import { controlOpenRingClass } from "../../lib/control-styles.js";
9
9
  import { Button } from "../general/button.js";
10
10
  import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
@@ -38,12 +38,12 @@ function CheckboxVisual({
38
38
  "aria-hidden": "true",
39
39
  "data-slot": "checkbox",
40
40
  "data-state": state,
41
+ "data-disabled": disabled ? "" : void 0,
41
42
  className: cn(
42
43
  "ui-checkbox inline-flex items-center justify-center",
43
44
  // The shared CSS fills the box for [data-state=checked]; mirror that fill for the
44
45
  // indeterminate ("some children selected") state so a partial parent reads as partial.
45
46
  "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
46
- disabled && "cursor-not-allowed opacity-50",
47
47
  className
48
48
  ),
49
49
  children: indeterminate ? /* @__PURE__ */ jsx(Minus, { className: "ui-checkbox-icon", "aria-hidden": "true" }) : checked ? /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" }) : null
@@ -86,6 +86,7 @@ function Cascader({
86
86
  }) {
87
87
  const { t } = useTranslation();
88
88
  const fieldA11y = pickFieldA11y(ariaProps);
89
+ const identity = useFieldIdentity({ id, "data-field": fieldA11y["data-field"] });
89
90
  const reactId = React.useId();
90
91
  const panelId = `${id ?? reactId}-panel`;
91
92
  const options = React.useMemo(
@@ -175,13 +176,13 @@ function Cascader({
175
176
  else commitSingle([]);
176
177
  };
177
178
  const renderCascadeColumns = () => /* @__PURE__ */ jsxs(ScrollArea, { className: "w-full", children: [
178
- /* @__PURE__ */ jsx("div", { className: "flex max-h-[min(280px,50vh)]", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
179
+ /* @__PURE__ */ jsx("div", { className: "ui-cascader-columns", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
179
180
  "ul",
180
181
  {
181
182
  role: "listbox",
182
183
  "aria-orientation": "vertical",
183
184
  "aria-multiselectable": multiple ? true : void 0,
184
- className: "min-w-[9rem] border-e last:border-e-0",
185
+ className: "ui-cascader-column",
185
186
  children: col.map((node) => {
186
187
  const path = [...activePath.slice(0, colIndex), node.value];
187
188
  const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
@@ -197,11 +198,11 @@ function Cascader({
197
198
  "aria-haspopup": hasChildren ? "menu" : void 0,
198
199
  "aria-expanded": hasChildren ? active : void 0,
199
200
  disabled: node.disabled,
201
+ "data-disabled": node.disabled ? "" : void 0,
200
202
  className: cn(
201
- "flex w-full items-center gap-1 px-3 py-2 text-sm outline-none",
203
+ "ui-cascader-option",
202
204
  "hover:bg-accent hover:text-accent-foreground",
203
- active && "bg-accent/70 font-medium",
204
- node.disabled && "pointer-events-none opacity-50"
205
+ active && "bg-accent/70 font-medium"
205
206
  ),
206
207
  onMouseEnter: (
207
208
  // Hover-expand: a parent opens its children column; a leaf collapses any
@@ -221,9 +222,9 @@ function Cascader({
221
222
  className: "me-1"
222
223
  }
223
224
  ),
224
- !multiple && selected && /* @__PURE__ */ jsx(Check, { className: "me-1 size-4 shrink-0", "aria-hidden": "true" }),
225
+ !multiple && selected && /* @__PURE__ */ jsx(Check, { className: "ui-cascader-option-icon", "aria-hidden": "true" }),
225
226
  /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-start", children: node.label }),
226
- hasChildren && /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 shrink-0 opacity-50", "aria-hidden": "true" })
227
+ hasChildren && /* @__PURE__ */ jsx(ChevronRight, { className: "ui-cascader-option-chevron", "aria-hidden": "true" })
227
228
  ]
228
229
  }
229
230
  ) }, node.value);
@@ -240,6 +241,7 @@ function Cascader({
240
241
  Button,
241
242
  {
242
243
  id,
244
+ "data-field": fieldA11y["data-field"] ?? identity["data-field"],
243
245
  type: "button",
244
246
  variant: "outline",
245
247
  role: "combobox",
@@ -252,81 +254,77 @@ function Cascader({
252
254
  "w-full justify-start font-normal",
253
255
  controlOpenRingClass,
254
256
  // Reserve trailing room for the single clear-or-chevron overlay rendered below.
255
- "pe-9",
257
+ "ui-control-trigger-affixed",
256
258
  !displayLabel && "text-muted-foreground"
257
259
  ),
258
260
  children: /* @__PURE__ */ jsx("span", { className: "truncate", children: displayLabel ?? resolvedPlaceholder })
259
261
  }
260
262
  ) }),
261
- /* @__PURE__ */ jsxs(
262
- PopoverContent,
263
- {
264
- id: panelId,
265
- className: "w-auto min-w-[var(--radix-popover-trigger-width)] p-0",
266
- align: "start",
267
- children: [
268
- showSearch && /* @__PURE__ */ jsx(Command, { shouldFilter: false, children: /* @__PURE__ */ jsx(
269
- CommandInput,
270
- {
271
- placeholder: t("dataEntry.cascader.searchPlaceholder"),
272
- value: search,
273
- onValueChange: setSearch
274
- }
275
- ) }),
276
- isSearching ? /* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[min(300px,50vh)]", children: /* @__PURE__ */ jsx(
277
- "div",
278
- {
279
- className: "p-1",
280
- role: "listbox",
281
- "aria-multiselectable": multiple ? true : void 0,
282
- children: searchResults.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground py-6 text-center text-sm", children: t("dataEntry.cascader.empty") }) : searchResults.map(({ path, labels }) => {
283
- const label = labels.join(" / ");
284
- const selected = multiple ? pathInValues(path, multiValue) : pathsEqual(path, singleValue);
285
- return /* @__PURE__ */ jsxs(
286
- "button",
287
- {
288
- type: "button",
289
- role: "option",
290
- "aria-selected": selected,
291
- className: cn(
292
- "flex w-full items-center rounded-sm px-2 py-1.5 text-sm outline-none",
293
- "hover:bg-accent hover:text-accent-foreground",
294
- selected && "bg-accent/60"
295
- ),
296
- onClick: () => handleSelectNode({ value: path.at(-1), label }, path),
297
- children: [
298
- multiple ? /* @__PURE__ */ jsx(CheckboxVisual, { checked: selected, className: "me-2" }) : /* @__PURE__ */ jsx(
299
- Check,
300
- {
301
- className: cn(
302
- "me-2 size-4 shrink-0",
303
- selected ? "opacity-100" : "opacity-0"
304
- ),
305
- "aria-hidden": "true"
306
- }
307
- ),
308
- /* @__PURE__ */ jsx("span", { className: "truncate text-start", children: label })
309
- ]
310
- },
311
- pathKey(path)
312
- );
313
- })
314
- }
315
- ) }) : renderCascadeColumns()
316
- ]
317
- }
318
- )
263
+ /* @__PURE__ */ jsxs(PopoverContent, { id: panelId, className: "ui-cascader-popover", align: "start", children: [
264
+ showSearch && /* @__PURE__ */ jsx(Command, { shouldFilter: false, children: /* @__PURE__ */ jsx(
265
+ CommandInput,
266
+ {
267
+ placeholder: t("dataEntry.cascader.searchPlaceholder"),
268
+ value: search,
269
+ onValueChange: setSearch
270
+ }
271
+ ) }),
272
+ isSearching ? /* @__PURE__ */ jsx(ScrollArea, { className: "ui-cascader-list", children: /* @__PURE__ */ jsx(
273
+ "div",
274
+ {
275
+ className: "ui-cascader-panel",
276
+ role: "listbox",
277
+ "aria-multiselectable": multiple ? true : void 0,
278
+ children: searchResults.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ui-cascader-empty", children: t("dataEntry.cascader.empty") }) : searchResults.map(({ path, labels }) => {
279
+ const label = labels.join(" / ");
280
+ const selected = multiple ? pathInValues(path, multiValue) : pathsEqual(path, singleValue);
281
+ return /* @__PURE__ */ jsxs(
282
+ "button",
283
+ {
284
+ type: "button",
285
+ role: "option",
286
+ "aria-selected": selected,
287
+ className: cn(
288
+ "ui-cascader-result",
289
+ "hover:bg-accent hover:text-accent-foreground",
290
+ selected && "bg-accent/60"
291
+ ),
292
+ onClick: () => handleSelectNode({ value: path.at(-1), label }, path),
293
+ children: [
294
+ multiple ? /* @__PURE__ */ jsx(CheckboxVisual, { checked: selected, className: "me-2" }) : /* @__PURE__ */ jsx(
295
+ Check,
296
+ {
297
+ className: "ui-cascader-result-icon",
298
+ "data-selected": selected ? "true" : "false",
299
+ "aria-hidden": "true"
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsx("span", { className: "truncate text-start", children: label })
303
+ ]
304
+ },
305
+ pathKey(path)
306
+ );
307
+ })
308
+ }
309
+ ) }) : renderCascadeColumns()
310
+ ] })
319
311
  ] }),
320
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 end-3 flex items-center", children: showClear ? /* @__PURE__ */ jsx(
312
+ /* @__PURE__ */ jsx("div", { className: "ui-control-affix ui-cascader-affix", children: showClear ? /* @__PURE__ */ jsx(
321
313
  "button",
322
314
  {
323
315
  type: "button",
324
316
  "aria-label": t("dataEntry.cascader.clear"),
325
- className: "pointer-events-auto flex size-4 items-center justify-center rounded-sm opacity-50 hover:opacity-100 focus-visible:opacity-100",
317
+ className: "ui-control-affix-action",
326
318
  onClick: clearValue,
327
- children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
319
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
320
+ }
321
+ ) : /* @__PURE__ */ jsx(
322
+ ChevronsUpDown,
323
+ {
324
+ className: "ui-control-affix-icon ui-control-affix-indicator",
325
+ "aria-hidden": "true"
328
326
  }
329
- ) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "size-4 shrink-0 opacity-50", "aria-hidden": "true" }) })
327
+ ) })
330
328
  ] });
331
329
  }
332
330
  export {