@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.
- package/dist/app/app-provider.d.ts +1 -1
- package/dist/app/app-provider.js +3 -0
- package/dist/app/date-format-labels.d.ts +1 -1
- package/dist/app/time-format-labels.d.ts +1 -1
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/card.d.ts +4 -13
- package/dist/components/data-display/card.js +2 -15
- package/dist/components/data-display/data-table.d.ts +3 -8
- package/dist/components/data-display/data-table.js +106 -68
- package/dist/components/data-display/descriptions.js +34 -5
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/permission-matrix.js +7 -7
- package/dist/components/data-display/popover.js +2 -9
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/scroll-area.d.ts +3 -1
- package/dist/components/data-display/scroll-area.js +150 -16
- package/dist/components/data-display/table.js +12 -3
- package/dist/components/data-entry/branch-scope-picker.js +4 -4
- package/dist/components/data-entry/calendar.js +15 -24
- package/dist/components/data-entry/cascader.js +70 -72
- package/dist/components/data-entry/checkbox-group.js +12 -5
- package/dist/components/data-entry/checkbox.js +30 -13
- package/dist/components/data-entry/command.d.ts +6 -6
- package/dist/components/data-entry/date-picker.js +34 -17
- package/dist/components/data-entry/date-range-picker.js +19 -13
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +22 -5
- package/dist/components/data-entry/index.d.ts +2 -1
- package/dist/components/data-entry/input.js +18 -22
- package/dist/components/data-entry/label.js +4 -2
- package/dist/components/data-entry/month-picker.js +20 -17
- package/dist/components/data-entry/month-range-picker.js +26 -19
- package/dist/components/data-entry/radio.js +25 -6
- package/dist/components/data-entry/search-select.d.ts +1 -1
- package/dist/components/data-entry/search-select.js +37 -31
- package/dist/components/data-entry/select.d.ts +8 -0
- package/dist/components/data-entry/select.js +68 -26
- package/dist/components/data-entry/switch.js +16 -2
- package/dist/components/data-entry/textarea.d.ts +56 -0
- package/dist/components/data-entry/textarea.js +110 -40
- package/dist/components/data-entry/time-picker.js +52 -52
- package/dist/components/data-entry/toggle-group.d.ts +1 -0
- package/dist/components/data-entry/toggle.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +14 -15
- package/dist/components/data-entry/tree-select.js +19 -23
- package/dist/components/data-entry/upload-crop-dialog.js +4 -4
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +46 -46
- package/dist/components/feedback/alert.js +7 -14
- package/dist/components/feedback/dialog.d.ts +23 -32
- package/dist/components/feedback/dialog.js +16 -13
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +14 -14
- package/dist/components/feedback/skeleton.js +13 -10
- package/dist/components/feedback/sonner.d.ts +1 -1
- package/dist/components/feedback/sonner.js +17 -8
- package/dist/components/feedback/tooltip.js +1 -1
- package/dist/components/general/activity.d.ts +44 -0
- package/dist/components/general/activity.js +65 -0
- package/dist/components/general/button.d.ts +2 -2
- package/dist/components/general/button.js +11 -24
- package/dist/components/general/index.d.ts +2 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/layout/app-shell.js +5 -3
- package/dist/components/layout/auth-divider.d.ts +9 -2
- package/dist/components/layout/auth-divider.js +7 -11
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -20
- package/dist/components/layout/resizable.d.ts +12 -12
- package/dist/components/layout/separator.d.ts +1 -0
- package/dist/components/layout/service-role-panel.js +47 -42
- package/dist/components/layout/sidebar.js +13 -1
- package/dist/components/layout/split-pane.d.ts +12 -1
- package/dist/components/layout/split-pane.js +13 -4
- package/dist/components/navigation/app-setting-picker.js +5 -14
- package/dist/components/navigation/dropdown-menu.js +10 -16
- package/dist/components/navigation/menubar.d.ts +3 -6
- package/dist/components/navigation/pagination-utils.d.ts +15 -2
- package/dist/components/navigation/pagination-utils.js +27 -21
- package/dist/components/navigation/pagination.d.ts +4 -4
- package/dist/components/navigation/pagination.js +2 -2
- package/dist/components/navigation/steps.js +31 -67
- package/dist/components/navigation/tabs.js +17 -5
- package/dist/components/query/infinite-query-state.js +2 -2
- package/dist/components/ui/accordion.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +29 -10
- package/dist/components/ui/input-otp.d.ts +8 -6
- package/dist/components/ui/rating.js +1 -1
- package/dist/components/ui/separator.d.ts +27 -2
- package/dist/components/ui/separator.js +42 -11
- package/dist/components/ui/toggle-group.d.ts +15 -5
- package/dist/components/ui/toggle-group.js +43 -18
- package/dist/components/ui/toggle.d.ts +66 -5
- package/dist/components/ui/toggle.js +64 -9
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +14 -0
- package/dist/lib/control-styles.js +2 -0
- package/dist/lib/field-a11y.d.ts +52 -0
- package/dist/lib/field-a11y.js +17 -2
- package/dist/props/components/app.prop.d.ts +15 -0
- package/dist/props/components/data-display.prop.d.ts +85 -3
- package/dist/props/components/data-entry.prop.d.ts +35 -2
- package/dist/props/components/general.prop.d.ts +52 -1
- package/dist/props/components/layout.prop.d.ts +137 -2
- package/dist/props/registry.d.ts +116 -5
- package/dist/props/registry.js +165 -4
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
- package/dist/props/vocabulary/layout.prop.d.ts +8 -0
- package/dist/styles/alert-layout.css +69 -3
- package/dist/styles/badge-layout.css +2 -2
- package/dist/styles/base.css +6 -0
- package/dist/styles/card-layout.css +25 -8
- package/dist/styles/control.css +1150 -29
- package/dist/styles/data-display-layout.css +185 -11
- package/dist/styles/data-entry-layout.css +352 -1
- package/dist/styles/density.css +2 -2
- package/dist/styles/dialog-layout.css +203 -4
- package/dist/styles/focus-ring.css +155 -0
- package/dist/styles/form-layout.css +50 -5
- package/dist/styles/index.css +1 -0
- package/dist/styles/layout.css +412 -23
- package/dist/styles/motion.css +178 -0
- package/dist/styles/navigation-layout.css +398 -22
- package/dist/styles/shell-layout.css +137 -39
- package/dist/styles/table-layout.css +153 -22
- package/dist/styles/toggle.css +59 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +64 -0
- package/dist/tokens/components/badge.css +7 -0
- package/dist/tokens/components/card.css +14 -10
- package/dist/tokens/components/chart.css +1 -1
- package/dist/tokens/components/control.css +358 -8
- package/dist/tokens/components/data-display.css +75 -0
- package/dist/tokens/components/data-entry.css +25 -0
- package/dist/tokens/components/descriptions.css +22 -0
- package/dist/tokens/components/feedback.css +143 -4
- package/dist/tokens/components/form.css +16 -0
- package/dist/tokens/components/legal-document.css +5 -1
- package/dist/tokens/components/navigation.css +109 -0
- package/dist/tokens/components/scroll-area.css +21 -0
- package/dist/tokens/components/separator.css +52 -0
- package/dist/tokens/components/shell.css +132 -20
- package/dist/tokens/components/table.css +52 -3
- package/dist/tokens/components/toggle.css +82 -0
- package/dist/tokens/components/upload.css +107 -0
- package/dist/tokens/foundation.css +171 -10
- package/dist/tokens/semantic/layout.css +120 -1
- package/package.json +85 -68
- package/scripts/ui-audit.mjs +51 -2
|
@@ -7,6 +7,7 @@ import { cn } from "../../lib/utils.js";
|
|
|
7
7
|
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
8
8
|
import {
|
|
9
9
|
mergeAriaIds,
|
|
10
|
+
useFieldIdentity,
|
|
10
11
|
omitFieldA11y,
|
|
11
12
|
pickFieldA11y,
|
|
12
13
|
useFieldNameFallback
|
|
@@ -26,7 +27,36 @@ function Select(props) {
|
|
|
26
27
|
function CompoundSelect({ id, ...props }) {
|
|
27
28
|
const fieldA11y = pickFieldA11y(props);
|
|
28
29
|
const rootProps = omitFieldA11y(props);
|
|
29
|
-
|
|
30
|
+
const [uncontrolled, setUncontrolled] = React.useState(props.defaultValue);
|
|
31
|
+
const value = props.value ?? uncontrolled;
|
|
32
|
+
const identity = useFieldIdentity({
|
|
33
|
+
id,
|
|
34
|
+
name: props.name,
|
|
35
|
+
"data-field": fieldA11y["data-field"]
|
|
36
|
+
});
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
SelectFieldA11yContext.Provider,
|
|
39
|
+
{
|
|
40
|
+
value: {
|
|
41
|
+
...fieldA11y,
|
|
42
|
+
"data-field": identity["data-field"] ?? fieldA11y["data-field"],
|
|
43
|
+
id,
|
|
44
|
+
value
|
|
45
|
+
},
|
|
46
|
+
children: /* @__PURE__ */ jsx(
|
|
47
|
+
SelectPrimitive.Root,
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "select",
|
|
50
|
+
...rootProps,
|
|
51
|
+
name: props.name ?? identity.name,
|
|
52
|
+
onValueChange: (next) => {
|
|
53
|
+
setUncontrolled(next);
|
|
54
|
+
props.onValueChange?.(next);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
);
|
|
30
60
|
}
|
|
31
61
|
function SelectGroup(props) {
|
|
32
62
|
return /* @__PURE__ */ jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
@@ -54,8 +84,10 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
|
|
|
54
84
|
field?.["aria-errormessage"]
|
|
55
85
|
),
|
|
56
86
|
"aria-required": props["aria-required"] ?? field?.["aria-required"],
|
|
57
|
-
"aria-invalid": props["aria-invalid"] ?? field?.["aria-invalid"]
|
|
87
|
+
"aria-invalid": props["aria-invalid"] ?? field?.["aria-invalid"],
|
|
88
|
+
"data-field": props["data-field"] ?? field?.["data-field"]
|
|
58
89
|
} : void 0;
|
|
90
|
+
const dataValue = (props["data-value"] ?? field?.value) || void 0;
|
|
59
91
|
return /* @__PURE__ */ jsxs(
|
|
60
92
|
SelectPrimitive.Trigger,
|
|
61
93
|
{
|
|
@@ -64,18 +96,19 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
|
|
|
64
96
|
"data-size": size,
|
|
65
97
|
className: cn(
|
|
66
98
|
controlTriggerClass,
|
|
67
|
-
"
|
|
99
|
+
"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
100
|
className
|
|
69
101
|
),
|
|
70
102
|
...props,
|
|
71
103
|
...fieldA11y,
|
|
104
|
+
"data-value": dataValue,
|
|
72
105
|
children: [
|
|
73
106
|
children,
|
|
74
107
|
showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
75
108
|
ChevronDown,
|
|
76
109
|
{
|
|
77
110
|
"data-slot": "select-chevron",
|
|
78
|
-
className: "
|
|
111
|
+
className: "ui-select-chevron",
|
|
79
112
|
"aria-hidden": "true"
|
|
80
113
|
}
|
|
81
114
|
) }) : null
|
|
@@ -89,9 +122,9 @@ const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) =>
|
|
|
89
122
|
{
|
|
90
123
|
ref,
|
|
91
124
|
"data-slot": "select-scroll-up-button",
|
|
92
|
-
className: cn("
|
|
125
|
+
className: cn("ui-select-scroll-button", className),
|
|
93
126
|
...props,
|
|
94
|
-
children: /* @__PURE__ */ jsx(ChevronUp, { className: "
|
|
127
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "ui-select-scroll-icon", "aria-hidden": "true" })
|
|
95
128
|
}
|
|
96
129
|
));
|
|
97
130
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
@@ -100,9 +133,9 @@ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) =
|
|
|
100
133
|
{
|
|
101
134
|
ref,
|
|
102
135
|
"data-slot": "select-scroll-down-button",
|
|
103
|
-
className: cn("
|
|
136
|
+
className: cn("ui-select-scroll-button", className),
|
|
104
137
|
...props,
|
|
105
|
-
children: /* @__PURE__ */ jsx(ChevronDown, { className: "
|
|
138
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "ui-select-scroll-icon", "aria-hidden": "true" })
|
|
106
139
|
}
|
|
107
140
|
));
|
|
108
141
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
@@ -112,7 +145,7 @@ const SelectContent = React.forwardRef(({ className, children, position = "poppe
|
|
|
112
145
|
ref,
|
|
113
146
|
"data-slot": "select-content",
|
|
114
147
|
className: cn(
|
|
115
|
-
"
|
|
148
|
+
"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
149
|
position === "popper" && "translate-y-1",
|
|
117
150
|
className
|
|
118
151
|
),
|
|
@@ -125,7 +158,7 @@ const SelectContent = React.forwardRef(({ className, children, position = "poppe
|
|
|
125
158
|
{
|
|
126
159
|
"data-slot": "select-viewport",
|
|
127
160
|
className: cn(
|
|
128
|
-
"
|
|
161
|
+
"ui-select-viewport",
|
|
129
162
|
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
130
163
|
),
|
|
131
164
|
children
|
|
@@ -141,7 +174,7 @@ const SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
141
174
|
{
|
|
142
175
|
ref,
|
|
143
176
|
"data-slot": "select-label",
|
|
144
|
-
className: cn("
|
|
177
|
+
className: cn("ui-select-label", className),
|
|
145
178
|
...props
|
|
146
179
|
}
|
|
147
180
|
));
|
|
@@ -151,10 +184,7 @@ const SelectItem = React.forwardRef(({ className, children, ...props }, ref) =>
|
|
|
151
184
|
{
|
|
152
185
|
ref,
|
|
153
186
|
"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
|
-
),
|
|
187
|
+
className: cn("ui-select-item [&_svg:not([class*='text-'])]:text-muted-foreground", className),
|
|
158
188
|
...props,
|
|
159
189
|
children: /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
|
|
160
190
|
}
|
|
@@ -165,7 +195,7 @@ const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => /* @_
|
|
|
165
195
|
{
|
|
166
196
|
ref,
|
|
167
197
|
"data-slot": "select-separator",
|
|
168
|
-
className: cn("
|
|
198
|
+
className: cn("ui-select-separator", className),
|
|
169
199
|
...props
|
|
170
200
|
}
|
|
171
201
|
));
|
|
@@ -215,12 +245,18 @@ function DataSelect({
|
|
|
215
245
|
id,
|
|
216
246
|
className,
|
|
217
247
|
"data-testid": dataTestId,
|
|
248
|
+
"data-field": dataField,
|
|
218
249
|
...rest
|
|
219
250
|
}) {
|
|
220
251
|
const ariaProps = Object.fromEntries(
|
|
221
252
|
Object.entries(rest).filter(([key]) => key.startsWith("aria-"))
|
|
222
253
|
);
|
|
223
254
|
const { t } = useTranslation();
|
|
255
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
256
|
+
const currentValue = value ?? uncontrolledValue;
|
|
257
|
+
const identity = useFieldIdentity({ id, name, "data-field": dataField });
|
|
258
|
+
const resolvedName = name ?? identity.name;
|
|
259
|
+
const resolvedField = dataField ?? identity["data-field"];
|
|
224
260
|
const searchable = showSearch ?? Boolean(loadOptions);
|
|
225
261
|
const hasOptions = options.length > 0;
|
|
226
262
|
if (searchable) {
|
|
@@ -253,10 +289,11 @@ function DataSelect({
|
|
|
253
289
|
filterOption,
|
|
254
290
|
renderError,
|
|
255
291
|
renderLoadMore,
|
|
256
|
-
name,
|
|
292
|
+
name: resolvedName,
|
|
257
293
|
id,
|
|
258
294
|
className,
|
|
259
295
|
"data-testid": dataTestId,
|
|
296
|
+
"data-field": resolvedField,
|
|
260
297
|
...ariaProps
|
|
261
298
|
}
|
|
262
299
|
);
|
|
@@ -281,19 +318,24 @@ function DataSelect({
|
|
|
281
318
|
"data-slot": "select",
|
|
282
319
|
value: isControlled ? value : void 0,
|
|
283
320
|
defaultValue: isControlled ? void 0 : defaultValue || void 0,
|
|
284
|
-
onValueChange: (next) =>
|
|
285
|
-
next
|
|
286
|
-
|
|
287
|
-
|
|
321
|
+
onValueChange: (next) => {
|
|
322
|
+
setUncontrolledValue(next);
|
|
323
|
+
onValueChange?.(
|
|
324
|
+
next,
|
|
325
|
+
options.find((option) => option.value === next)
|
|
326
|
+
);
|
|
327
|
+
},
|
|
288
328
|
disabled: disabled || !hasOptions,
|
|
289
|
-
name,
|
|
329
|
+
name: resolvedName,
|
|
290
330
|
children: [
|
|
291
331
|
/* @__PURE__ */ jsx(
|
|
292
332
|
SelectTrigger,
|
|
293
333
|
{
|
|
294
334
|
id,
|
|
295
335
|
"data-testid": dataTestId,
|
|
296
|
-
|
|
336
|
+
"data-field": resolvedField,
|
|
337
|
+
"data-value": currentValue || void 0,
|
|
338
|
+
className: cn(showClear && "ui-control-trigger-affixed", canClear ? void 0 : className),
|
|
297
339
|
showIndicator: !showClear,
|
|
298
340
|
...ariaProps,
|
|
299
341
|
children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
|
|
@@ -311,15 +353,15 @@ function DataSelect({
|
|
|
311
353
|
if (!canClear) return select;
|
|
312
354
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
313
355
|
select,
|
|
314
|
-
showClear ? /* @__PURE__ */ jsx("div", { className: "
|
|
356
|
+
showClear ? /* @__PURE__ */ jsx("div", { className: "ui-control-affix", children: /* @__PURE__ */ jsx(
|
|
315
357
|
"button",
|
|
316
358
|
{
|
|
317
359
|
type: "button",
|
|
318
360
|
"aria-label": clearLabel ?? t("dataEntry.searchSelect.clear"),
|
|
319
361
|
"data-testid": dataTestId ? `${dataTestId}-clear` : void 0,
|
|
320
|
-
className: "
|
|
362
|
+
className: "ui-control-affix-action",
|
|
321
363
|
onClick: () => onValueChange?.("", void 0),
|
|
322
|
-
children: /* @__PURE__ */ jsx(X, { className: "
|
|
364
|
+
children: /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
|
|
323
365
|
}
|
|
324
366
|
) }) : null
|
|
325
367
|
] });
|
|
@@ -3,8 +3,15 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
6
7
|
const Switch = React.forwardRef(
|
|
7
8
|
({ className, size = "md", name, checked, defaultChecked = false, onCheckedChange, ...props }, ref) => {
|
|
9
|
+
const identity = useFieldIdentity({
|
|
10
|
+
id: props.id,
|
|
11
|
+
name,
|
|
12
|
+
"data-field": props["data-field"]
|
|
13
|
+
});
|
|
14
|
+
const resolvedName = name ?? identity.name;
|
|
8
15
|
const [internalChecked, setInternalChecked] = React.useState(defaultChecked);
|
|
9
16
|
const isControlled = checked !== void 0;
|
|
10
17
|
const isChecked = isControlled ? checked : internalChecked;
|
|
@@ -15,7 +22,7 @@ const Switch = React.forwardRef(
|
|
|
15
22
|
onCheckedChange?.(next);
|
|
16
23
|
};
|
|
17
24
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
|
-
|
|
25
|
+
resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: isChecked ? "1" : "0", readOnly: true }) : null,
|
|
19
26
|
/* @__PURE__ */ jsx(
|
|
20
27
|
SwitchPrimitive.Root,
|
|
21
28
|
{
|
|
@@ -25,10 +32,17 @@ const Switch = React.forwardRef(
|
|
|
25
32
|
checked: isChecked,
|
|
26
33
|
onCheckedChange: handleCheckedChange,
|
|
27
34
|
className: cn(
|
|
28
|
-
|
|
35
|
+
// `disabled:cursor-not-allowed disabled:opacity-50` DELETED, not moved (#319):
|
|
36
|
+
// `.ui-switch:disabled, .ui-switch[data-disabled]` in styles/control.css already
|
|
37
|
+
// declares both and reads --disabled-opacity. The utility was layered after
|
|
38
|
+
// components, so it silently outranked that token. Byte-identical: the token
|
|
39
|
+
// defaults to 0.5. (`shadow-xs` STAYS — .ui-switch declares --shadow-sm, so this
|
|
40
|
+
// utility is the switch's real resting elevation, not a duplicate.)
|
|
41
|
+
"peer ui-switch shadow-xs transition-all outline-none",
|
|
29
42
|
className
|
|
30
43
|
),
|
|
31
44
|
...props,
|
|
45
|
+
"data-field": identity["data-field"] ?? props["data-field"],
|
|
32
46
|
children: /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "ui-switch-thumb" })
|
|
33
47
|
}
|
|
34
48
|
)
|
|
@@ -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,34 @@ 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 {
|
|
7
|
+
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
|
+
import { controlMultilineClass, controlMultilineGhostClass } from "../../lib/control-styles.js";
|
|
9
|
+
const UNBOUNDED_ROWS = "infinity";
|
|
8
10
|
const Textarea = React.forwardRef(
|
|
9
|
-
({
|
|
11
|
+
({
|
|
12
|
+
className,
|
|
13
|
+
allowClear = false,
|
|
14
|
+
onClear,
|
|
15
|
+
variant = "default",
|
|
16
|
+
autoGrow = false,
|
|
17
|
+
minRows,
|
|
18
|
+
maxRows,
|
|
19
|
+
rows,
|
|
20
|
+
style,
|
|
21
|
+
value,
|
|
22
|
+
defaultValue,
|
|
23
|
+
onChange,
|
|
24
|
+
onCompositionStart,
|
|
25
|
+
onCompositionEnd,
|
|
26
|
+
...props
|
|
27
|
+
}, ref) => {
|
|
10
28
|
const { t } = useTranslation();
|
|
29
|
+
const base = variant === "ghost" ? controlMultilineGhostClass : controlMultilineClass;
|
|
30
|
+
const identity = useFieldIdentity({
|
|
31
|
+
id: props.id,
|
|
32
|
+
name: props.name,
|
|
33
|
+
"data-field": props["data-field"]
|
|
34
|
+
});
|
|
11
35
|
const innerRef = React.useRef(null);
|
|
12
36
|
const setRefs = React.useCallback(
|
|
13
37
|
(node) => {
|
|
@@ -23,10 +47,39 @@ const Textarea = React.forwardRef(
|
|
|
23
47
|
React.useEffect(() => {
|
|
24
48
|
if (value !== void 0) setHasText(String(value).length > 0);
|
|
25
49
|
}, [value]);
|
|
50
|
+
const [mirror, setMirror] = React.useState(() => String(value ?? defaultValue ?? ""));
|
|
51
|
+
const composing = React.useRef(false);
|
|
52
|
+
const syncMirror = React.useCallback(() => {
|
|
53
|
+
const el = innerRef.current;
|
|
54
|
+
if (!el || composing.current) return;
|
|
55
|
+
setMirror(el.value);
|
|
56
|
+
}, []);
|
|
57
|
+
React.useLayoutEffect(() => {
|
|
58
|
+
if (autoGrow) syncMirror();
|
|
59
|
+
});
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
const form = innerRef.current?.form;
|
|
62
|
+
if (!autoGrow || !form) return;
|
|
63
|
+
const onReset = () => {
|
|
64
|
+
queueMicrotask(syncMirror);
|
|
65
|
+
};
|
|
66
|
+
form.addEventListener("reset", onReset);
|
|
67
|
+
return () => form.removeEventListener("reset", onReset);
|
|
68
|
+
}, [autoGrow, syncMirror]);
|
|
26
69
|
const handleChange = (event) => {
|
|
27
70
|
if (value === void 0) setHasText(event.target.value.length > 0);
|
|
71
|
+
if (autoGrow && !composing.current) setMirror(event.target.value);
|
|
28
72
|
onChange?.(event);
|
|
29
73
|
};
|
|
74
|
+
const handleCompositionStart = (event) => {
|
|
75
|
+
composing.current = true;
|
|
76
|
+
onCompositionStart?.(event);
|
|
77
|
+
};
|
|
78
|
+
const handleCompositionEnd = (event) => {
|
|
79
|
+
composing.current = false;
|
|
80
|
+
if (autoGrow) setMirror(event.currentTarget.value);
|
|
81
|
+
onCompositionEnd?.(event);
|
|
82
|
+
};
|
|
30
83
|
const clear = () => {
|
|
31
84
|
const el = innerRef.current;
|
|
32
85
|
if (el) {
|
|
@@ -39,46 +92,63 @@ const Textarea = React.forwardRef(
|
|
|
39
92
|
el.focus();
|
|
40
93
|
}
|
|
41
94
|
setHasText(false);
|
|
95
|
+
if (autoGrow) setMirror("");
|
|
42
96
|
onClear?.();
|
|
43
97
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
const floorRows = minRows ?? (typeof rows === "number" ? rows : void 0);
|
|
99
|
+
const autoGrowVars = autoGrow ? {
|
|
100
|
+
...floorRows === void 0 ? null : { "--textarea-autogrow-min-height-rows": floorRows },
|
|
101
|
+
...maxRows === void 0 ? null : {
|
|
102
|
+
"--textarea-autogrow-max-height-rows": maxRows === 0 ? UNBOUNDED_ROWS : maxRows
|
|
103
|
+
}
|
|
104
|
+
} : void 0;
|
|
105
|
+
const showClear = allowClear && hasText && !props.disabled && !props.readOnly;
|
|
106
|
+
const needsWrapper = allowClear || autoGrow;
|
|
107
|
+
const field = /* @__PURE__ */ jsx(
|
|
108
|
+
"textarea",
|
|
109
|
+
{
|
|
110
|
+
ref: setRefs,
|
|
111
|
+
value,
|
|
112
|
+
defaultValue,
|
|
113
|
+
rows: autoGrow ? 1 : rows,
|
|
114
|
+
onChange: needsWrapper ? handleChange : onChange,
|
|
115
|
+
onCompositionStart: handleCompositionStart,
|
|
116
|
+
onCompositionEnd: handleCompositionEnd,
|
|
117
|
+
style,
|
|
118
|
+
className: cn(base, showClear && "ui-input--trailing-affix", className),
|
|
119
|
+
...props,
|
|
120
|
+
...identity
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
if (!needsWrapper) return field;
|
|
124
|
+
return /* @__PURE__ */ jsxs(
|
|
125
|
+
"span",
|
|
126
|
+
{
|
|
127
|
+
"data-slot": "textarea-affix-wrapper",
|
|
128
|
+
"data-autogrow-value": autoGrow ? mirror : void 0,
|
|
129
|
+
style: autoGrowVars,
|
|
130
|
+
className: cn(
|
|
131
|
+
"relative w-full",
|
|
132
|
+
autoGrow ? "grid" : "block",
|
|
133
|
+
autoGrow && "ui-textarea-autogrow",
|
|
134
|
+
autoGrow && variant === "ghost" && "ui-textarea-autogrow--ghost"
|
|
135
|
+
),
|
|
136
|
+
children: [
|
|
137
|
+
field,
|
|
138
|
+
showClear ? /* @__PURE__ */ jsx(
|
|
139
|
+
"button",
|
|
140
|
+
{
|
|
141
|
+
type: "button",
|
|
142
|
+
tabIndex: -1,
|
|
143
|
+
"aria-label": t("common.clear") ?? "Clear",
|
|
144
|
+
onClick: clear,
|
|
145
|
+
className: "ui-control-inline-affix-action ui-textarea-clear",
|
|
146
|
+
children: /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
|
|
147
|
+
}
|
|
148
|
+
) : null
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
);
|
|
82
152
|
}
|
|
83
153
|
);
|
|
84
154
|
Textarea.displayName = "Textarea";
|