@godxjp/ui 18.15.4 → 19.0.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 (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +25 -8
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. package/scripts/ui-audit.mjs +51 -2
@@ -64,7 +64,7 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
64
64
  "data-size": size,
65
65
  className: cn(
66
66
  controlTriggerClass,
67
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-full gap-2 whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
67
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-full whitespace-nowrap transition-[color,box-shadow] outline-none *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center",
68
68
  className
69
69
  ),
70
70
  ...props,
@@ -75,7 +75,7 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
75
75
  ChevronDown,
76
76
  {
77
77
  "data-slot": "select-chevron",
78
- className: "size-4 shrink-0 opacity-50",
78
+ className: "ui-select-chevron",
79
79
  "aria-hidden": "true"
80
80
  }
81
81
  ) }) : null
@@ -89,9 +89,9 @@ const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) =>
89
89
  {
90
90
  ref,
91
91
  "data-slot": "select-scroll-up-button",
92
- className: cn("flex cursor-default items-center justify-center py-1", className),
92
+ className: cn("ui-select-scroll-button", className),
93
93
  ...props,
94
- children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-4", "aria-hidden": "true" })
94
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "ui-select-scroll-icon", "aria-hidden": "true" })
95
95
  }
96
96
  ));
97
97
  SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
@@ -100,9 +100,9 @@ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) =
100
100
  {
101
101
  ref,
102
102
  "data-slot": "select-scroll-down-button",
103
- className: cn("flex cursor-default items-center justify-center py-1", className),
103
+ className: cn("ui-select-scroll-button", className),
104
104
  ...props,
105
- children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4", "aria-hidden": "true" })
105
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "ui-select-scroll-icon", "aria-hidden": "true" })
106
106
  }
107
107
  ));
108
108
  SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
@@ -112,7 +112,7 @@ const SelectContent = React.forwardRef(({ className, children, position = "poppe
112
112
  ref,
113
113
  "data-slot": "select-content",
114
114
  className: cn(
115
- "bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border shadow-md",
115
+ "ui-select-content data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
116
116
  position === "popper" && "translate-y-1",
117
117
  className
118
118
  ),
@@ -125,7 +125,7 @@ const SelectContent = React.forwardRef(({ className, children, position = "poppe
125
125
  {
126
126
  "data-slot": "select-viewport",
127
127
  className: cn(
128
- "p-1",
128
+ "ui-select-viewport",
129
129
  position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
130
130
  ),
131
131
  children
@@ -141,7 +141,7 @@ const SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
141
141
  {
142
142
  ref,
143
143
  "data-slot": "select-label",
144
- className: cn("px-2 py-1.5 text-sm font-medium", className),
144
+ className: cn("ui-select-label", className),
145
145
  ...props
146
146
  }
147
147
  ));
@@ -151,10 +151,7 @@ const SelectItem = React.forwardRef(({ className, children, ...props }, ref) =>
151
151
  {
152
152
  ref,
153
153
  "data-slot": "select-item",
154
- className: cn(
155
- "focus:bg-accent focus:text-accent-foreground data-[state=checked]:bg-accent data-[state=checked]:text-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[state=checked]:font-medium [&_svg]:pointer-events-none [&_svg]:shrink-0",
156
- className
157
- ),
154
+ className: cn("ui-select-item [&_svg:not([class*='text-'])]:text-muted-foreground", className),
158
155
  ...props,
159
156
  children: /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
160
157
  }
@@ -165,7 +162,7 @@ const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => /* @_
165
162
  {
166
163
  ref,
167
164
  "data-slot": "select-separator",
168
- className: cn("bg-border -mx-1 my-1 h-px", className),
165
+ className: cn("ui-select-separator", className),
169
166
  ...props
170
167
  }
171
168
  ));
@@ -293,7 +290,7 @@ function DataSelect({
293
290
  {
294
291
  id,
295
292
  "data-testid": dataTestId,
296
- className: cn(showClear && "pe-9", canClear ? void 0 : className),
293
+ className: cn(showClear && "ui-control-trigger-affixed", canClear ? void 0 : className),
297
294
  showIndicator: !showClear,
298
295
  ...ariaProps,
299
296
  children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
@@ -311,15 +308,15 @@ function DataSelect({
311
308
  if (!canClear) return select;
312
309
  return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
313
310
  select,
314
- showClear ? /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 end-2 flex items-center", children: /* @__PURE__ */ jsx(
311
+ showClear ? /* @__PURE__ */ jsx("div", { className: "ui-control-affix", children: /* @__PURE__ */ jsx(
315
312
  "button",
316
313
  {
317
314
  type: "button",
318
315
  "aria-label": clearLabel ?? t("dataEntry.searchSelect.clear"),
319
316
  "data-testid": dataTestId ? `${dataTestId}-clear` : void 0,
320
- className: "flex size-6 items-center justify-center rounded-sm opacity-50 hover:opacity-100 focus-visible:opacity-100",
317
+ className: "ui-control-affix-action",
321
318
  onClick: () => onValueChange?.("", void 0),
322
- children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
319
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
323
320
  }
324
321
  ) }) : null
325
322
  ] });
@@ -25,7 +25,13 @@ const Switch = React.forwardRef(
25
25
  checked: isChecked,
26
26
  onCheckedChange: handleCheckedChange,
27
27
  className: cn(
28
- "peer ui-switch focus-visible:border-ring focus-visible:ring-ring/50 shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
28
+ // `disabled:cursor-not-allowed disabled:opacity-50` DELETED, not moved (#319):
29
+ // `.ui-switch:disabled, .ui-switch[data-disabled]` in styles/control.css already
30
+ // declares both and reads --disabled-opacity. The utility was layered after
31
+ // components, so it silently outranked that token. Byte-identical: the token
32
+ // defaults to 0.5. (`shadow-xs` STAYS — .ui-switch declares --shadow-sm, so this
33
+ // utility is the switch's real resting elevation, not a duplicate.)
34
+ "peer ui-switch shadow-xs transition-all outline-none",
29
35
  className
30
36
  ),
31
37
  ...props,
@@ -4,10 +4,66 @@ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> &
4
4
  allowClear?: boolean;
5
5
  /** Called after the field is cleared via the inline ✕. */
6
6
  onClear?: () => void;
7
+ /**
8
+ * `ghost` strips the field's own border, background and focus ring, for a textarea embedded
9
+ * in a surface that already draws the box (a chat composer inside a Card). The surface then
10
+ * owns focus — give it `focus-within`. Default keeps the standalone control chrome.
11
+ */
12
+ variant?: "default" | "ghost";
13
+ /**
14
+ * Grow the control with its content instead of holding a fixed `rows` height (default false —
15
+ * the inert default, so every existing `<Textarea>` keeps the geometry it has today).
16
+ *
17
+ * The floor is `minRows` (or `rows`, when that is the only one given) and never undercuts the
18
+ * `--control-height` tier; past `maxRows` the control stops growing and scrolls internally.
19
+ * Sizing is done in CSS by a hidden replica of the text, so it follows a paste, a programmatic
20
+ * value change, a font swap and a container resize — not just typing — and it never writes
21
+ * `style.height` or reads `scrollHeight`. Works controlled and uncontrolled.
22
+ */
23
+ autoGrow?: boolean;
24
+ /**
25
+ * Floor in text rows while `autoGrow` (theme default `--textarea-autogrow-min-height-rows`, 1).
26
+ * Ignored when `autoGrow` is false.
27
+ */
28
+ minRows?: number;
29
+ /**
30
+ * Ceiling in text rows while `autoGrow` (theme default `--textarea-autogrow-max-height-rows`,
31
+ * 8); beyond it the control scrolls internally rather than pushing the page. Pass `0` for no
32
+ * ceiling — only correct inside an owning scroll container. Ignored when `autoGrow` is false.
33
+ */
34
+ maxRows?: number;
7
35
  };
8
36
  export declare const Textarea: React.ForwardRefExoticComponent<React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
9
37
  /** Show an inline ✕ (top-end) that clears the field while it holds text (default false). */
10
38
  allowClear?: boolean;
11
39
  /** Called after the field is cleared via the inline ✕. */
12
40
  onClear?: () => void;
41
+ /**
42
+ * `ghost` strips the field's own border, background and focus ring, for a textarea embedded
43
+ * in a surface that already draws the box (a chat composer inside a Card). The surface then
44
+ * owns focus — give it `focus-within`. Default keeps the standalone control chrome.
45
+ */
46
+ variant?: "default" | "ghost";
47
+ /**
48
+ * Grow the control with its content instead of holding a fixed `rows` height (default false —
49
+ * the inert default, so every existing `<Textarea>` keeps the geometry it has today).
50
+ *
51
+ * The floor is `minRows` (or `rows`, when that is the only one given) and never undercuts the
52
+ * `--control-height` tier; past `maxRows` the control stops growing and scrolls internally.
53
+ * Sizing is done in CSS by a hidden replica of the text, so it follows a paste, a programmatic
54
+ * value change, a font swap and a container resize — not just typing — and it never writes
55
+ * `style.height` or reads `scrollHeight`. Works controlled and uncontrolled.
56
+ */
57
+ autoGrow?: boolean;
58
+ /**
59
+ * Floor in text rows while `autoGrow` (theme default `--textarea-autogrow-min-height-rows`, 1).
60
+ * Ignored when `autoGrow` is false.
61
+ */
62
+ minRows?: number;
63
+ /**
64
+ * Ceiling in text rows while `autoGrow` (theme default `--textarea-autogrow-max-height-rows`,
65
+ * 8); beyond it the control scrolls internally rather than pushing the page. Pass `0` for no
66
+ * ceiling — only correct inside an owning scroll container. Ignored when `autoGrow` is false.
67
+ */
68
+ maxRows?: number;
13
69
  } & React.RefAttributes<HTMLTextAreaElement>>;
@@ -4,10 +4,28 @@ import * as React from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { controlMultilineClass } from "../../lib/control-styles.js";
7
+ import { controlMultilineClass, controlMultilineGhostClass } from "../../lib/control-styles.js";
8
+ const UNBOUNDED_ROWS = "infinity";
8
9
  const Textarea = React.forwardRef(
9
- ({ className, allowClear = false, onClear, value, defaultValue, onChange, ...props }, ref) => {
10
+ ({
11
+ className,
12
+ allowClear = false,
13
+ onClear,
14
+ variant = "default",
15
+ autoGrow = false,
16
+ minRows,
17
+ maxRows,
18
+ rows,
19
+ style,
20
+ value,
21
+ defaultValue,
22
+ onChange,
23
+ onCompositionStart,
24
+ onCompositionEnd,
25
+ ...props
26
+ }, ref) => {
10
27
  const { t } = useTranslation();
28
+ const base = variant === "ghost" ? controlMultilineGhostClass : controlMultilineClass;
11
29
  const innerRef = React.useRef(null);
12
30
  const setRefs = React.useCallback(
13
31
  (node) => {
@@ -23,10 +41,39 @@ const Textarea = React.forwardRef(
23
41
  React.useEffect(() => {
24
42
  if (value !== void 0) setHasText(String(value).length > 0);
25
43
  }, [value]);
44
+ const [mirror, setMirror] = React.useState(() => String(value ?? defaultValue ?? ""));
45
+ const composing = React.useRef(false);
46
+ const syncMirror = React.useCallback(() => {
47
+ const el = innerRef.current;
48
+ if (!el || composing.current) return;
49
+ setMirror(el.value);
50
+ }, []);
51
+ React.useLayoutEffect(() => {
52
+ if (autoGrow) syncMirror();
53
+ });
54
+ React.useEffect(() => {
55
+ const form = innerRef.current?.form;
56
+ if (!autoGrow || !form) return;
57
+ const onReset = () => {
58
+ queueMicrotask(syncMirror);
59
+ };
60
+ form.addEventListener("reset", onReset);
61
+ return () => form.removeEventListener("reset", onReset);
62
+ }, [autoGrow, syncMirror]);
26
63
  const handleChange = (event) => {
27
64
  if (value === void 0) setHasText(event.target.value.length > 0);
65
+ if (autoGrow && !composing.current) setMirror(event.target.value);
28
66
  onChange?.(event);
29
67
  };
68
+ const handleCompositionStart = (event) => {
69
+ composing.current = true;
70
+ onCompositionStart?.(event);
71
+ };
72
+ const handleCompositionEnd = (event) => {
73
+ composing.current = false;
74
+ if (autoGrow) setMirror(event.currentTarget.value);
75
+ onCompositionEnd?.(event);
76
+ };
30
77
  const clear = () => {
31
78
  const el = innerRef.current;
32
79
  if (el) {
@@ -39,46 +86,62 @@ const Textarea = React.forwardRef(
39
86
  el.focus();
40
87
  }
41
88
  setHasText(false);
89
+ if (autoGrow) setMirror("");
42
90
  onClear?.();
43
91
  };
44
- if (!allowClear) {
45
- return /* @__PURE__ */ jsx(
46
- "textarea",
47
- {
48
- ref: setRefs,
49
- value,
50
- defaultValue,
51
- onChange,
52
- className: cn(controlMultilineClass, className),
53
- ...props
54
- }
55
- );
56
- }
57
- const showClear = hasText && !props.disabled && !props.readOnly;
58
- return /* @__PURE__ */ jsxs("span", { "data-slot": "textarea-affix-wrapper", className: "relative block w-full", children: [
59
- /* @__PURE__ */ jsx(
60
- "textarea",
61
- {
62
- ref: setRefs,
63
- value,
64
- defaultValue,
65
- onChange: handleChange,
66
- className: cn(controlMultilineClass, showClear && "pe-9", className),
67
- ...props
68
- }
69
- ),
70
- showClear ? /* @__PURE__ */ jsx(
71
- "button",
72
- {
73
- type: "button",
74
- tabIndex: -1,
75
- "aria-label": t("common.clear") ?? "Clear",
76
- onClick: clear,
77
- className: "text-muted-foreground hover:text-foreground absolute end-2 top-2 inline-flex size-5 items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:opacity-100",
78
- children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
79
- }
80
- ) : null
81
- ] });
92
+ const floorRows = minRows ?? (typeof rows === "number" ? rows : void 0);
93
+ const autoGrowVars = autoGrow ? {
94
+ ...floorRows === void 0 ? null : { "--textarea-autogrow-min-height-rows": floorRows },
95
+ ...maxRows === void 0 ? null : {
96
+ "--textarea-autogrow-max-height-rows": maxRows === 0 ? UNBOUNDED_ROWS : maxRows
97
+ }
98
+ } : void 0;
99
+ const showClear = allowClear && hasText && !props.disabled && !props.readOnly;
100
+ const needsWrapper = allowClear || autoGrow;
101
+ const field = /* @__PURE__ */ jsx(
102
+ "textarea",
103
+ {
104
+ ref: setRefs,
105
+ value,
106
+ defaultValue,
107
+ rows: autoGrow ? 1 : rows,
108
+ onChange: needsWrapper ? handleChange : onChange,
109
+ onCompositionStart: handleCompositionStart,
110
+ onCompositionEnd: handleCompositionEnd,
111
+ style,
112
+ className: cn(base, showClear && "ui-input--trailing-affix", className),
113
+ ...props
114
+ }
115
+ );
116
+ if (!needsWrapper) return field;
117
+ return /* @__PURE__ */ jsxs(
118
+ "span",
119
+ {
120
+ "data-slot": "textarea-affix-wrapper",
121
+ "data-autogrow-value": autoGrow ? mirror : void 0,
122
+ style: autoGrowVars,
123
+ className: cn(
124
+ "relative w-full",
125
+ autoGrow ? "grid" : "block",
126
+ autoGrow && "ui-textarea-autogrow",
127
+ autoGrow && variant === "ghost" && "ui-textarea-autogrow--ghost"
128
+ ),
129
+ children: [
130
+ field,
131
+ showClear ? /* @__PURE__ */ jsx(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ tabIndex: -1,
136
+ "aria-label": t("common.clear") ?? "Clear",
137
+ onClick: clear,
138
+ className: "ui-control-inline-affix-action ui-textarea-clear",
139
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
140
+ }
141
+ ) : null
142
+ ]
143
+ }
144
+ );
82
145
  }
83
146
  );
84
147
  Textarea.displayName = "Textarea";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Clock } from "lucide-react";
4
+ import { Clock, X } from "lucide-react";
5
5
  import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
6
  import { isValidHhmm, normalizeHhmm } from "../../lib/datetime/index.js";
7
7
  import { pickFieldA11y } from "../../lib/field-a11y.js";
@@ -73,40 +73,28 @@ function TimeColumn({
73
73
  break;
74
74
  }
75
75
  };
76
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
77
- /* @__PURE__ */ jsx("div", { className: "text-muted-foreground border-b px-1 py-1.5 text-center text-xs font-medium", children: label }),
78
- /* @__PURE__ */ jsx(
79
- "div",
80
- {
81
- ref: listRef,
82
- role: "listbox",
83
- "aria-label": label,
84
- className: "h-52 [scrollbar-width:thin] [scrollbar-gutter:stable] overflow-y-scroll overscroll-contain p-1",
85
- children: items.map((item, index) => {
86
- const isSelected = item === selected;
87
- return /* @__PURE__ */ jsx(
88
- "button",
89
- {
90
- type: "button",
91
- role: "option",
92
- "aria-selected": isSelected,
93
- "data-selected": isSelected,
94
- tabIndex: isSelected ? 0 : -1,
95
- className: cn(
96
- "hover:bg-accent flex w-full items-center justify-center rounded-md py-1.5 text-sm tabular-nums transition-colors",
97
- isSelected && "bg-primary text-primary-foreground hover:bg-primary/90"
98
- ),
99
- onClick: () => {
100
- onSelect(item);
101
- },
102
- onKeyDown: (e) => onKeyDown(e, index),
103
- children: formatItem(item)
104
- },
105
- item
106
- );
107
- })
108
- }
109
- )
76
+ return /* @__PURE__ */ jsxs("div", { className: "ui-time-picker-column", children: [
77
+ /* @__PURE__ */ jsx("div", { className: "ui-time-picker-column-heading", children: label }),
78
+ /* @__PURE__ */ jsx("div", { ref: listRef, role: "listbox", "aria-label": label, className: "ui-time-picker-column-scroll", children: items.map((item, index) => {
79
+ const isSelected = item === selected;
80
+ return /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ role: "option",
85
+ "aria-selected": isSelected,
86
+ "data-selected": isSelected,
87
+ tabIndex: isSelected ? 0 : -1,
88
+ className: "ui-time-picker-option",
89
+ onClick: () => {
90
+ onSelect(item);
91
+ },
92
+ onKeyDown: (e) => onKeyDown(e, index),
93
+ children: formatItem(item)
94
+ },
95
+ item
96
+ );
97
+ }) })
110
98
  ] });
111
99
  }
112
100
  function TimePickerPanel({ value, minuteStep, use12h, onChange, onDone }) {
@@ -132,7 +120,7 @@ function TimePickerPanel({ value, minuteStep, use12h, onChange, onDone }) {
132
120
  };
133
121
  const hourItems = use12h ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
134
122
  const selectedHourItem = use12h ? to12h(hour) : hour;
135
- return /* @__PURE__ */ jsxs("div", { className: use12h ? "w-52" : "w-36", children: [
123
+ return /* @__PURE__ */ jsxs("div", { className: "ui-time-picker-panel", "data-hour-cycle": use12h ? "h12" : "h23", children: [
136
124
  /* @__PURE__ */ jsxs("div", { className: "divide-border flex divide-x", children: [
137
125
  /* @__PURE__ */ jsx(
138
126
  TimeColumn,
@@ -174,7 +162,7 @@ function TimePickerPanel({ value, minuteStep, use12h, onChange, onDone }) {
174
162
  }
175
163
  )
176
164
  ] }),
177
- /* @__PURE__ */ jsx("div", { className: "border-t p-2", children: /* @__PURE__ */ jsx(
165
+ /* @__PURE__ */ jsx("div", { className: "ui-time-picker-footer", children: /* @__PURE__ */ jsx(
178
166
  Input,
179
167
  {
180
168
  id: draftId,
@@ -237,6 +225,7 @@ function TimePicker({
237
225
  setValue("");
238
226
  setText("");
239
227
  };
228
+ const showClear = allowClear && text !== "" && !disabled;
240
229
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
241
230
  /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: /* @__PURE__ */ jsx(
242
231
  Input,
@@ -253,20 +242,31 @@ function TimePicker({
253
242
  "aria-haspopup": "dialog",
254
243
  "aria-controls": open ? dialogId : void 0,
255
244
  ...fieldA11y,
256
- className: "tabular-nums",
257
- allowClear,
258
- onClear: clear,
259
- trailingIcon: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
260
- "button",
261
- {
262
- type: "button",
263
- disabled,
264
- tabIndex: -1,
265
- "aria-label": t("dataEntry.timePicker.openPicker") ?? "Open time picker",
266
- className: "text-muted-foreground hover:text-foreground inline-flex size-5 items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100",
267
- children: /* @__PURE__ */ jsx(Clock, { className: "size-4", "aria-hidden": "true" })
268
- }
269
- ) }),
245
+ className: cn("tabular-nums", showClear && "ui-control-inline-affix-pair-affixed"),
246
+ trailingIcon: /* @__PURE__ */ jsxs("span", { className: "ui-time-picker-affix", children: [
247
+ showClear ? /* @__PURE__ */ jsx(
248
+ "button",
249
+ {
250
+ type: "button",
251
+ tabIndex: -1,
252
+ "aria-label": t("common.clear") ?? "Clear",
253
+ onClick: clear,
254
+ className: "ui-control-inline-affix-action",
255
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
256
+ }
257
+ ) : null,
258
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
259
+ "button",
260
+ {
261
+ type: "button",
262
+ disabled,
263
+ tabIndex: -1,
264
+ "aria-label": t("dataEntry.timePicker.openPicker") ?? "Open time picker",
265
+ className: "ui-control-inline-affix-action",
266
+ children: /* @__PURE__ */ jsx(Clock, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
267
+ }
268
+ ) })
269
+ ] }),
270
270
  onClick: () => {
271
271
  if (!disabled) setOpen(true);
272
272
  },
@@ -295,7 +295,7 @@ function TimePicker({
295
295
  id: dialogId,
296
296
  role: "dialog",
297
297
  "aria-label": t("dataEntry.timePicker.openPicker") ?? "Time picker",
298
- className: "w-auto p-0",
298
+ className: "ui-time-picker-popover",
299
299
  align: "end",
300
300
  onOpenAutoFocus: (event) => event.preventDefault(),
301
301
  children: /* @__PURE__ */ jsx(
@@ -1 +1,2 @@
1
1
  export { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group.js";
2
+ export type { ToggleGroupItemProp, ToggleGroupItemProps } from "../ui/toggle-group.js";
@@ -1,2 +1,2 @@
1
1
  export { Toggle } from "../ui/toggle.js";
2
- export type { ToggleProps } from "../ui/toggle.js";
2
+ export type { ToggleProp, ToggleProps } from "../ui/toggle.js";
@@ -40,9 +40,9 @@ function TransferPanel({
40
40
  const toggleKey = (key, checked) => {
41
41
  onSelectChange(checked ? [...selectedKeys, key] : selectedKeys.filter((k) => k !== key));
42
42
  };
43
- return /* @__PURE__ */ jsxs("div", { className: "bg-background flex min-h-[14rem] flex-1 flex-col rounded-md border", children: [
44
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2 text-sm", children: [
45
- /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 font-medium", children: [
43
+ return /* @__PURE__ */ jsxs("div", { className: "ui-transfer-pane", children: [
44
+ /* @__PURE__ */ jsxs("div", { className: "ui-transfer-pane-header", children: [
45
+ /* @__PURE__ */ jsxs("label", { className: "ui-transfer-pane-check", children: [
46
46
  /* @__PURE__ */ jsx(
47
47
  Checkbox,
48
48
  {
@@ -60,21 +60,20 @@ function TransferPanel({
60
60
  filtered.length
61
61
  ] })
62
62
  ] }),
63
- showSearch && /* @__PURE__ */ jsx("div", { className: "border-b p-2", children: /* @__PURE__ */ jsx(
63
+ showSearch && /* @__PURE__ */ jsx("div", { className: "ui-transfer-search", "data-disabled": disabled ? "" : void 0, children: /* @__PURE__ */ jsx(
64
64
  SearchInput,
65
65
  {
66
66
  onSearch: setQuery,
67
67
  placeholder: searchPlaceholder,
68
68
  ariaLabel: searchPlaceholder,
69
- debounce: 0,
70
- className: disabled ? "pointer-events-none opacity-50" : void 0
69
+ debounce: 0
71
70
  }
72
71
  ) }),
73
- /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("ul", { className: "p-1", "aria-labelledby": titleId, children: filtered.length === 0 ? /* @__PURE__ */ jsx("li", { className: "text-muted-foreground py-8 text-center text-sm", children: emptyText }) : filtered.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
72
+ /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("ul", { className: "ui-transfer-list", "aria-labelledby": titleId, children: filtered.length === 0 ? /* @__PURE__ */ jsx("li", { className: "ui-transfer-empty", children: emptyText }) : filtered.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
74
73
  "label",
75
74
  {
76
75
  className: cn(
77
- "flex cursor-pointer items-start gap-2 rounded-sm px-2 py-2 text-sm",
76
+ "ui-transfer-row",
78
77
  "hover:bg-accent hover:text-accent-foreground",
79
78
  item.disabled && "bg-muted/40 text-muted-foreground pointer-events-none"
80
79
  ),
@@ -85,12 +84,12 @@ function TransferPanel({
85
84
  checked: selectedKeys.includes(item.key),
86
85
  disabled: Boolean(disabled) || Boolean(item.disabled),
87
86
  onCheckedChange: (v) => toggleKey(item.key, v === true),
88
- className: "mt-0.5"
87
+ className: "ui-transfer-row-check"
89
88
  }
90
89
  ),
91
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
90
+ /* @__PURE__ */ jsxs("span", { className: "ui-transfer-row-body", children: [
92
91
  /* @__PURE__ */ jsx("span", { className: "block truncate font-medium", children: item.title }),
93
- item.description && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground block truncate text-xs", children: item.description })
92
+ item.description && /* @__PURE__ */ jsx("span", { className: "ui-transfer-row-description text-muted-foreground block truncate", children: item.description })
94
93
  ] })
95
94
  ]
96
95
  }
@@ -141,7 +140,7 @@ function Transfer({
141
140
  id,
142
141
  ...groupA11y,
143
142
  "aria-disabled": disabled ? true : void 0,
144
- className: cn("flex flex-wrap items-stretch gap-3", className),
143
+ className: cn("ui-transfer", className),
145
144
  children: [
146
145
  /* @__PURE__ */ jsx(
147
146
  TransferPanel,
@@ -158,7 +157,7 @@ function Transfer({
158
157
  emptyText: t("dataEntry.transfer.empty")
159
158
  }
160
159
  ),
161
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center gap-2", children: [
160
+ /* @__PURE__ */ jsxs("div", { className: "ui-transfer-actions", children: [
162
161
  /* @__PURE__ */ jsx(
163
162
  Button,
164
163
  {
@@ -168,7 +167,7 @@ function Transfer({
168
167
  disabled: Boolean(disabled) || selected[0].length === 0,
169
168
  "aria-label": t("dataEntry.transfer.moveRight"),
170
169
  onClick: () => move("right"),
171
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
170
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "ui-transfer-action-icon", "aria-hidden": "true" })
172
171
  }
173
172
  ),
174
173
  !oneWay && /* @__PURE__ */ jsx(
@@ -180,7 +179,7 @@ function Transfer({
180
179
  disabled: Boolean(disabled) || selected[1].length === 0,
181
180
  "aria-label": t("dataEntry.transfer.moveLeft"),
182
181
  onClick: () => move("left"),
183
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
182
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ui-transfer-action-icon", "aria-hidden": "true" })
184
183
  }
185
184
  )
186
185
  ] }),