@godxjp/ui 17.0.1 → 18.0.1
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/components/data-display/card.d.ts +37 -1
- package/dist/components/data-display/card.js +7 -1
- package/dist/components/data-display/data-table.js +19 -4
- package/dist/components/data-display/empty-state.d.ts +1 -1
- package/dist/components/data-display/empty-state.js +5 -1
- package/dist/components/data-display/scroll-area.js +1 -1
- package/dist/components/data-display/table.d.ts +20 -1
- package/dist/components/data-display/table.js +23 -9
- package/dist/components/data-entry/calendar.js +4 -0
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +10 -1
- package/dist/components/data-entry/checkbox-group.d.ts +1 -1
- package/dist/components/data-entry/checkbox-group.js +13 -4
- package/dist/components/data-entry/color-picker.d.ts +1 -1
- package/dist/components/data-entry/color-picker.js +5 -2
- package/dist/components/data-entry/command.d.ts +2 -2
- package/dist/components/data-entry/date-picker.d.ts +1 -1
- package/dist/components/data-entry/date-picker.js +11 -1
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.js +11 -3
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-picker.js +13 -1
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.js +11 -3
- package/dist/components/data-entry/number-input.d.ts +19 -2
- package/dist/components/data-entry/number-input.js +5 -7
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/radio.js +11 -3
- package/dist/components/data-entry/search-input.d.ts +3 -2
- package/dist/components/data-entry/search-input.js +5 -2
- package/dist/components/data-entry/search-select.d.ts +1 -1
- package/dist/components/data-entry/search-select.js +51 -10
- package/dist/components/data-entry/select.d.ts +7 -0
- package/dist/components/data-entry/select.js +21 -3
- package/dist/components/data-entry/time-picker.d.ts +1 -1
- package/dist/components/data-entry/time-picker.js +11 -1
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +73 -59
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +10 -1
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +7 -2
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +42 -2
- package/dist/components/layout/breadcrumb.d.ts +7 -1
- package/dist/components/layout/breadcrumb.js +6 -2
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -22
- package/dist/components/layout/responsive-grid.js +1 -1
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +44 -11
- package/dist/components/layout/split-pane.js +2 -2
- package/dist/components/navigation/app-setting-picker.js +15 -7
- package/dist/components/navigation/pagination.d.ts +1 -1
- package/dist/components/navigation/pagination.js +84 -95
- package/dist/components/navigation/tabs.js +21 -4
- package/dist/components/ui/tag-input.js +1 -0
- package/dist/i18n/messages/en.json +3 -1
- package/dist/i18n/messages/ja.json +3 -1
- package/dist/i18n/messages/vi.json +3 -1
- package/dist/lib/field-a11y.d.ts +66 -0
- package/dist/lib/field-a11y.js +44 -0
- package/dist/props/components/app.prop.d.ts +9 -5
- package/dist/props/components/data-display.prop.d.ts +16 -1
- package/dist/props/components/data-entry.prop.d.ts +69 -19
- package/dist/props/components/layout.prop.d.ts +42 -0
- package/dist/props/components/navigation.prop.d.ts +16 -0
- package/dist/props/registry.d.ts +8 -3
- package/dist/props/registry.js +21 -3
- package/dist/props/vocabulary/data.prop.d.ts +9 -0
- package/dist/styles/layout.css +46 -9
- package/dist/styles/shell-layout.css +19 -0
- package/package.json +8 -4
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ArrowRight, CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
7
|
+
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { cn } from "../../lib/utils.js";
|
|
8
9
|
import { Button } from "../general/button.js";
|
|
9
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -28,14 +29,17 @@ function MonthRangePicker({
|
|
|
28
29
|
name,
|
|
29
30
|
fromYear,
|
|
30
31
|
toYear,
|
|
31
|
-
allowClear = true
|
|
32
|
+
allowClear = true,
|
|
33
|
+
...ariaProps
|
|
32
34
|
}) {
|
|
33
35
|
const { t } = useTranslation();
|
|
34
36
|
const { locale } = usePickerLocales();
|
|
35
37
|
const [open, setOpen] = React.useState(false);
|
|
38
|
+
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
36
39
|
const autoId = React.useId();
|
|
37
|
-
const
|
|
38
|
-
const
|
|
40
|
+
const groupId = id ?? autoId;
|
|
41
|
+
const fromId = `${groupId}-from`;
|
|
42
|
+
const toId = `${groupId}-to`;
|
|
39
43
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
40
44
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
41
45
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -100,6 +104,10 @@ function MonthRangePicker({
|
|
|
100
104
|
return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
101
105
|
"div",
|
|
102
106
|
{
|
|
107
|
+
role: "group",
|
|
108
|
+
id: groupId,
|
|
109
|
+
...groupA11y,
|
|
110
|
+
"aria-disabled": disabled ? true : void 0,
|
|
103
111
|
className: cn(
|
|
104
112
|
"ui-control border-input bg-background flex w-full min-w-0 items-center gap-2 rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
|
|
105
113
|
"focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { NumberInputProp } from "../../props/components/data-entry.prop.js";
|
|
3
2
|
export type { NumberInputProp, NumberInputProp as NumberInputProps, } from "../../props/components/data-entry.prop.js";
|
|
4
3
|
/**
|
|
5
4
|
* NumberInput — WAI-ARIA spinbutton: a localized numeric `<Input>` with increment/decrement steps.
|
|
@@ -9,4 +8,22 @@ export type { NumberInputProp, NumberInputProp as NumberInputProps, } from "../.
|
|
|
9
8
|
* (Shift = ×10) step, and the value commits clamped + rounded to `precision` on blur/Enter/step.
|
|
10
9
|
* Display formats via `Intl.NumberFormat` at rest, but stays raw and typeable while focused.
|
|
11
10
|
*/
|
|
12
|
-
export declare const NumberInput: React.ForwardRefExoticComponent<
|
|
11
|
+
export declare const NumberInput: React.ForwardRefExoticComponent<import("../../lib/field-a11y.js").FieldA11yProps & {
|
|
12
|
+
value?: import("../../props/index.js").ValueProp<number | null>;
|
|
13
|
+
defaultValue?: import("../../props/index.js").DefaultValueProp<number | null>;
|
|
14
|
+
onValueChange?: import("../../props/index.js").OnValueChangeProp<number | null>;
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
step?: number;
|
|
18
|
+
precision?: number;
|
|
19
|
+
disabled?: import("../../props/index.js").DisabledProp;
|
|
20
|
+
readOnly?: boolean;
|
|
21
|
+
size?: import("../../props/index.js").SizeProp;
|
|
22
|
+
placeholder?: import("../../props/index.js").PlaceholderProp;
|
|
23
|
+
prefix?: React.ReactNode;
|
|
24
|
+
suffix?: React.ReactNode;
|
|
25
|
+
name?: import("../../props/index.js").NameProp;
|
|
26
|
+
id?: import("../../props/index.js").IdProp;
|
|
27
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
28
|
+
"data-testid"?: string;
|
|
29
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ChevronDown, ChevronUp } 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
8
|
import { Button } from "../general/button.js";
|
|
8
9
|
import { Input } from "./input.js";
|
|
9
10
|
function decimalsOf(n) {
|
|
@@ -41,12 +42,11 @@ const NumberInput = React.forwardRef(
|
|
|
41
42
|
name,
|
|
42
43
|
id,
|
|
43
44
|
className,
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"aria-describedby": ariaDescribedby,
|
|
47
|
-
"data-testid": dataTestId
|
|
45
|
+
"data-testid": dataTestId,
|
|
46
|
+
...ariaProps
|
|
48
47
|
}, ref) => {
|
|
49
48
|
const { t, locale } = useTranslation();
|
|
49
|
+
const fieldA11y = pickFieldA11y(ariaProps);
|
|
50
50
|
const isControlled = controlledValue !== void 0;
|
|
51
51
|
const [internal, setInternal] = React.useState(defaultValue);
|
|
52
52
|
const numericValue = isControlled ? controlledValue ?? null : internal;
|
|
@@ -159,9 +159,7 @@ const NumberInput = React.forwardRef(
|
|
|
159
159
|
placeholder,
|
|
160
160
|
disabled,
|
|
161
161
|
readOnly,
|
|
162
|
-
|
|
163
|
-
"aria-labelledby": ariaLabelledby,
|
|
164
|
-
"aria-describedby": ariaDescribedby,
|
|
162
|
+
...fieldA11y,
|
|
165
163
|
"aria-valuenow": numericValue ?? void 0,
|
|
166
164
|
"aria-valuemin": min,
|
|
167
165
|
"aria-valuemax": max,
|
|
@@ -4,7 +4,7 @@ import type { RadioGroupProp } from "../../props/components/data-entry.prop.js";
|
|
|
4
4
|
export type { RadioGroupProp, RadioGroupProp as RadioGroupProps, } from "../../props/components/data-entry.prop.js";
|
|
5
5
|
declare const RadioGroupRoot: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
6
6
|
declare const RadioItem: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
7
|
-
declare function RadioGroupOptions({ value, defaultValue, onValueChange, options, orientation, disabled, name, className, children, }: RadioGroupProp): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare function RadioGroupOptions({ value, defaultValue, onValueChange, options, orientation, disabled, name, id, className, children, ...ariaProps }: RadioGroupProp): import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
/** Single radio — use inside `Radio.Group` / `RadioGroupRoot`, or via `options` API. */
|
|
9
9
|
export declare const Radio: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>> & {
|
|
10
10
|
Root: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
5
5
|
import { Circle } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { Field } from "./field.js";
|
|
8
9
|
import { choiceGroupClassName } from "./choice-option.js";
|
|
9
10
|
const RadioGroupRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
@@ -45,10 +46,13 @@ function RadioGroupOptions({
|
|
|
45
46
|
orientation = "vertical",
|
|
46
47
|
disabled,
|
|
47
48
|
name,
|
|
49
|
+
id,
|
|
48
50
|
className,
|
|
49
|
-
children
|
|
51
|
+
children,
|
|
52
|
+
...ariaProps
|
|
50
53
|
}) {
|
|
51
54
|
const reactId = React.useId();
|
|
55
|
+
const groupA11y = pickFieldA11y(ariaProps);
|
|
52
56
|
if (options && options.length > 0) {
|
|
53
57
|
return /* @__PURE__ */ jsx(
|
|
54
58
|
RadioGroupRoot,
|
|
@@ -58,11 +62,13 @@ function RadioGroupOptions({
|
|
|
58
62
|
onValueChange,
|
|
59
63
|
disabled,
|
|
60
64
|
name,
|
|
65
|
+
id,
|
|
66
|
+
...groupA11y,
|
|
61
67
|
"data-orientation": orientation,
|
|
62
68
|
className: choiceGroupClassName(orientation, className),
|
|
63
69
|
children: options.map((opt, index) => {
|
|
64
|
-
const
|
|
65
|
-
return /* @__PURE__ */ jsx(Field, { id, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(RadioItem, { id, value: opt.value, disabled: opt.disabled }) }, opt.value);
|
|
70
|
+
const id2 = `${reactId}-${opt.value}-${index}`;
|
|
71
|
+
return /* @__PURE__ */ jsx(Field, { id: id2, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(RadioItem, { id: id2, value: opt.value, disabled: opt.disabled }) }, opt.value);
|
|
66
72
|
})
|
|
67
73
|
}
|
|
68
74
|
);
|
|
@@ -75,6 +81,8 @@ function RadioGroupOptions({
|
|
|
75
81
|
onValueChange,
|
|
76
82
|
disabled,
|
|
77
83
|
name,
|
|
84
|
+
id,
|
|
85
|
+
...groupA11y,
|
|
78
86
|
"data-orientation": orientation,
|
|
79
87
|
className: choiceGroupClassName(orientation, className),
|
|
80
88
|
children
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
2
|
+
import { type FieldA11yProps } from "../../lib/field-a11y.js";
|
|
3
|
+
interface SearchInputProps extends FieldA11yProps {
|
|
3
4
|
value?: string;
|
|
4
5
|
defaultValue?: string;
|
|
5
6
|
placeholder?: string;
|
|
@@ -15,5 +16,5 @@ interface SearchInputProps {
|
|
|
15
16
|
id?: string;
|
|
16
17
|
disabled?: boolean;
|
|
17
18
|
}
|
|
18
|
-
export declare function SearchInput({ value: controlledValue, defaultValue, placeholder, debounce, onValueChange, onSearch, label, ariaLabel, className, inputClassName, id, disabled, }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function SearchInput({ value: controlledValue, defaultValue, placeholder, debounce, onValueChange, onSearch, label, ariaLabel, className, inputClassName, id, disabled, ...ariaProps }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
|
|
19
20
|
export {};
|
|
@@ -6,6 +6,7 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
6
6
|
import { Input } from "../data-entry/input.js";
|
|
7
7
|
import { Label } from "../data-entry/label.js";
|
|
8
8
|
import { cn } from "../../lib/utils.js";
|
|
9
|
+
import { resolveFieldA11y } from "../../lib/field-a11y.js";
|
|
9
10
|
import { useDebouncedValue } from "../../lib/hooks.js";
|
|
10
11
|
function SearchInput({
|
|
11
12
|
value: controlledValue,
|
|
@@ -19,7 +20,8 @@ function SearchInput({
|
|
|
19
20
|
className,
|
|
20
21
|
inputClassName,
|
|
21
22
|
id,
|
|
22
|
-
disabled = false
|
|
23
|
+
disabled = false,
|
|
24
|
+
...ariaProps
|
|
23
25
|
}) {
|
|
24
26
|
const { t } = useTranslation();
|
|
25
27
|
const isControlled = controlledValue !== void 0;
|
|
@@ -30,6 +32,7 @@ function SearchInput({
|
|
|
30
32
|
const inputId = id ?? `search-${reactId}`;
|
|
31
33
|
const resolvedPlaceholder = placeholder ?? t("dataEntry.searchInput.placeholder");
|
|
32
34
|
const resolvedAriaLabel = ariaLabel ?? t("common.search");
|
|
35
|
+
const fieldA11y = resolveFieldA11y(ariaProps, resolvedAriaLabel);
|
|
33
36
|
const onSearchRef = React.useRef(onSearch);
|
|
34
37
|
React.useEffect(() => {
|
|
35
38
|
onSearchRef.current = onSearch;
|
|
@@ -56,7 +59,7 @@ function SearchInput({
|
|
|
56
59
|
setValue(e.target.value);
|
|
57
60
|
},
|
|
58
61
|
placeholder: resolvedPlaceholder,
|
|
59
|
-
|
|
62
|
+
...fieldA11y,
|
|
60
63
|
className: cn(
|
|
61
64
|
"ui-search-input-control !pr-[var(--search-input-end-padding)] !pl-[var(--search-input-start-padding)]",
|
|
62
65
|
inputClassName
|
|
@@ -8,4 +8,4 @@ export type { SearchSelectProp, SearchSelectProp as SearchSelectProps, SearchSel
|
|
|
8
8
|
* Custom per-option rendering via `renderOption` (Ant-Design style). Form-submittable via
|
|
9
9
|
* `name`; e2e-testable by the trigger's `data-testid` + each option's `${data-testid}-option-${value}`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -28,6 +28,15 @@ function SearchSelect({
|
|
|
28
28
|
clearLabel,
|
|
29
29
|
clearable = true,
|
|
30
30
|
disabled = false,
|
|
31
|
+
readOnly = false,
|
|
32
|
+
size,
|
|
33
|
+
open: openProp,
|
|
34
|
+
onOpenChange,
|
|
35
|
+
search: searchProp,
|
|
36
|
+
onSearchChange,
|
|
37
|
+
filterOption,
|
|
38
|
+
renderError,
|
|
39
|
+
renderLoadMore,
|
|
31
40
|
name,
|
|
32
41
|
id,
|
|
33
42
|
className,
|
|
@@ -43,8 +52,26 @@ function SearchSelect({
|
|
|
43
52
|
const reactId = React.useId();
|
|
44
53
|
const listId = `${reactId}-listbox`;
|
|
45
54
|
const optionDomId = (optionValue) => `${reactId}-opt-${optionValue}`;
|
|
46
|
-
const [
|
|
47
|
-
const
|
|
55
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
56
|
+
const isOpenControlled = openProp !== void 0;
|
|
57
|
+
const open = isOpenControlled ? openProp : internalOpen;
|
|
58
|
+
const setOpen = React.useCallback(
|
|
59
|
+
(next) => {
|
|
60
|
+
if (!isOpenControlled) setInternalOpen(next);
|
|
61
|
+
onOpenChange?.(next);
|
|
62
|
+
},
|
|
63
|
+
[isOpenControlled, onOpenChange]
|
|
64
|
+
);
|
|
65
|
+
const [internalQuery, setInternalQuery] = React.useState("");
|
|
66
|
+
const isSearchControlled = searchProp !== void 0;
|
|
67
|
+
const query = isSearchControlled ? searchProp : internalQuery;
|
|
68
|
+
const setQuery = React.useCallback(
|
|
69
|
+
(next) => {
|
|
70
|
+
if (!isSearchControlled) setInternalQuery(next);
|
|
71
|
+
onSearchChange?.(next);
|
|
72
|
+
},
|
|
73
|
+
[isSearchControlled, onSearchChange]
|
|
74
|
+
);
|
|
48
75
|
const [debouncedQuery, setDebouncedQuery] = React.useState("");
|
|
49
76
|
const [loaded, setLoaded] = React.useState([]);
|
|
50
77
|
const [page, setPage] = React.useState(1);
|
|
@@ -59,16 +86,15 @@ function SearchSelect({
|
|
|
59
86
|
const reqId = React.useRef(0);
|
|
60
87
|
const resolvedLoad = React.useMemo(
|
|
61
88
|
() => loadOptions ?? (async ({ query: search }) => {
|
|
62
|
-
const needle = search.trim()
|
|
89
|
+
const needle = search.trim();
|
|
63
90
|
const list = staticOptions ?? [];
|
|
91
|
+
const matches = (option) => filterOption ? filterOption(option, needle) : option.label.toLowerCase().includes(needle.toLowerCase()) || option.value.toLowerCase().includes(needle.toLowerCase());
|
|
64
92
|
return {
|
|
65
|
-
options: needle ? list.filter(
|
|
66
|
-
(option) => option.label.toLowerCase().includes(needle) || option.value.toLowerCase().includes(needle)
|
|
67
|
-
) : list,
|
|
93
|
+
options: needle ? list.filter(matches) : list,
|
|
68
94
|
hasMore: false
|
|
69
95
|
};
|
|
70
96
|
}),
|
|
71
|
-
[loadOptions, staticOptions]
|
|
97
|
+
[loadOptions, staticOptions, filterOption]
|
|
72
98
|
);
|
|
73
99
|
React.useEffect(() => {
|
|
74
100
|
const handle = window.setTimeout(() => setDebouncedQuery(query.trim()), DEBOUNCE_MS);
|
|
@@ -190,13 +216,14 @@ function SearchSelect({
|
|
|
190
216
|
const optionTestId = (optionValue) => dataTestId ? `${dataTestId}-option-${optionValue}` : void 0;
|
|
191
217
|
const activeOption = flatOrdered[activeIndex];
|
|
192
218
|
const activeOptionId = activeOption ? optionDomId(activeOption.value) : void 0;
|
|
193
|
-
const showClear = clearable && Boolean(value) && !disabled;
|
|
219
|
+
const showClear = clearable && Boolean(value) && !disabled && !readOnly;
|
|
194
220
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
195
221
|
/* @__PURE__ */ jsxs(
|
|
196
222
|
Popover,
|
|
197
223
|
{
|
|
198
224
|
open,
|
|
199
225
|
onOpenChange: (next) => {
|
|
226
|
+
if (readOnly && next) return;
|
|
200
227
|
setOpen(next);
|
|
201
228
|
if (!next) setQuery("");
|
|
202
229
|
},
|
|
@@ -208,6 +235,7 @@ function SearchSelect({
|
|
|
208
235
|
type: "button",
|
|
209
236
|
variant: "outline",
|
|
210
237
|
role: "combobox",
|
|
238
|
+
size,
|
|
211
239
|
"aria-expanded": open,
|
|
212
240
|
"aria-controls": open ? listId : void 0,
|
|
213
241
|
"aria-label": ariaLabel,
|
|
@@ -216,6 +244,7 @@ function SearchSelect({
|
|
|
216
244
|
"aria-errormessage": ariaErrorMessage,
|
|
217
245
|
"aria-invalid": ariaInvalid,
|
|
218
246
|
"aria-required": ariaRequired,
|
|
247
|
+
"aria-readonly": readOnly || void 0,
|
|
219
248
|
disabled,
|
|
220
249
|
"data-testid": dataTestId,
|
|
221
250
|
className: cn(
|
|
@@ -317,7 +346,12 @@ function SearchSelect({
|
|
|
317
346
|
loadingMessage ?? t("dataEntry.searchSelect.loading")
|
|
318
347
|
]
|
|
319
348
|
}
|
|
320
|
-
) : error ?
|
|
349
|
+
) : error ? renderError ? renderError({
|
|
350
|
+
message: errorMessage ?? t("dataEntry.searchSelect.error"),
|
|
351
|
+
// Retry always reloads from the first page (a defined, predictable recovery —
|
|
352
|
+
// NOT a resume of a failed page-N append, which would need to re-derive state).
|
|
353
|
+
retry: () => void fetchPage(1, debouncedQuery, false)
|
|
354
|
+
}) : /* @__PURE__ */ jsx(
|
|
321
355
|
"div",
|
|
322
356
|
{
|
|
323
357
|
role: "option",
|
|
@@ -338,7 +372,14 @@ function SearchSelect({
|
|
|
338
372
|
) : null
|
|
339
373
|
]
|
|
340
374
|
}
|
|
341
|
-
)
|
|
375
|
+
),
|
|
376
|
+
renderLoadMore && hasMore ? /* @__PURE__ */ jsx("div", { className: "border-border shrink-0 border-t p-1", children: renderLoadMore({
|
|
377
|
+
hasMore,
|
|
378
|
+
loading,
|
|
379
|
+
loadMore: () => {
|
|
380
|
+
if (!loading) void fetchPage(page + 1, debouncedQuery, true);
|
|
381
|
+
}
|
|
382
|
+
}) }) : null
|
|
342
383
|
] })
|
|
343
384
|
}
|
|
344
385
|
)
|
|
@@ -14,6 +14,13 @@ export declare function SelectGroup(props: React.ComponentProps<typeof SelectPri
|
|
|
14
14
|
export declare function SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>): import("react/jsx-runtime").JSX.Element;
|
|
15
15
|
export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
|
|
16
16
|
size?: "sm" | "md";
|
|
17
|
+
/**
|
|
18
|
+
* Show the built-in chevron disclosure indicator (default true). Set `false` for specialized
|
|
19
|
+
* triggers — icon-only, or one that already renders its own affordance — so it isn't
|
|
20
|
+
* duplicated. Omits the icon from the DOM entirely (not a CSS hide), so no consumer
|
|
21
|
+
* descendant CSS is needed to remove it.
|
|
22
|
+
*/
|
|
23
|
+
showIndicator?: boolean;
|
|
17
24
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
18
25
|
export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
19
26
|
export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -21,7 +21,7 @@ function SelectGroup(props) {
|
|
|
21
21
|
function SelectValue(props) {
|
|
22
22
|
return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
23
23
|
}
|
|
24
|
-
const SelectTrigger = React.forwardRef(({ className, children, size = "md", ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
24
|
+
const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
25
25
|
SelectPrimitive.Trigger,
|
|
26
26
|
{
|
|
27
27
|
ref,
|
|
@@ -35,14 +35,14 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", ...p
|
|
|
35
35
|
...props,
|
|
36
36
|
children: [
|
|
37
37
|
children,
|
|
38
|
-
/* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
38
|
+
showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
39
39
|
ChevronDown,
|
|
40
40
|
{
|
|
41
41
|
"data-slot": "select-chevron",
|
|
42
42
|
className: "size-4 shrink-0 opacity-50",
|
|
43
43
|
"aria-hidden": "true"
|
|
44
44
|
}
|
|
45
|
-
) })
|
|
45
|
+
) }) : null
|
|
46
46
|
]
|
|
47
47
|
}
|
|
48
48
|
));
|
|
@@ -165,6 +165,15 @@ function DataSelect({
|
|
|
165
165
|
clearLabel,
|
|
166
166
|
clearable,
|
|
167
167
|
disabled,
|
|
168
|
+
readOnly,
|
|
169
|
+
size,
|
|
170
|
+
open,
|
|
171
|
+
onOpenChange,
|
|
172
|
+
search,
|
|
173
|
+
onSearchChange,
|
|
174
|
+
filterOption,
|
|
175
|
+
renderError,
|
|
176
|
+
renderLoadMore,
|
|
168
177
|
name,
|
|
169
178
|
id,
|
|
170
179
|
className,
|
|
@@ -197,6 +206,15 @@ function DataSelect({
|
|
|
197
206
|
clearLabel,
|
|
198
207
|
clearable,
|
|
199
208
|
disabled: disabled || !loadOptions && !hasOptions,
|
|
209
|
+
readOnly,
|
|
210
|
+
size,
|
|
211
|
+
open,
|
|
212
|
+
onOpenChange,
|
|
213
|
+
search,
|
|
214
|
+
onSearchChange,
|
|
215
|
+
filterOption,
|
|
216
|
+
renderError,
|
|
217
|
+
renderLoadMore,
|
|
200
218
|
name,
|
|
201
219
|
id,
|
|
202
220
|
className,
|
|
@@ -7,4 +7,4 @@ export type { TimePickerProp, TimePickerProp as TimePickerProps, } from "../../p
|
|
|
7
7
|
* The column display honours the active `timeFormat` (12h shows 1-12 + AM/PM) while the canonical
|
|
8
8
|
* value remains 24h `HH:mm`.
|
|
9
9
|
*/
|
|
10
|
-
export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, allowClear, }: TimePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, allowClear, ...ariaProps }: TimePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Clock } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { isValidHhmm, normalizeHhmm } from "../../lib/datetime/index.js";
|
|
7
|
+
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { cn } from "../../lib/utils.js";
|
|
8
9
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
9
10
|
import { Input } from "./input.js";
|
|
@@ -210,12 +211,16 @@ function TimePicker({
|
|
|
210
211
|
id,
|
|
211
212
|
name,
|
|
212
213
|
minuteStep = 5,
|
|
213
|
-
allowClear = true
|
|
214
|
+
allowClear = true,
|
|
215
|
+
...ariaProps
|
|
214
216
|
}) {
|
|
215
217
|
const { t } = useTranslation();
|
|
216
218
|
const { timeFormat } = usePickerLocales();
|
|
217
219
|
const use12h = timeFormat === "12h";
|
|
218
220
|
const [open, setOpen] = React.useState(false);
|
|
221
|
+
const fieldA11y = pickFieldA11y(ariaProps);
|
|
222
|
+
const reactId = React.useId();
|
|
223
|
+
const dialogId = `${id ?? reactId}-dialog`;
|
|
219
224
|
const [internal, setInternal] = React.useState(defaultValue ?? "");
|
|
220
225
|
const isControlled = controlledValue !== void 0;
|
|
221
226
|
const value = isControlled ? controlledValue : internal;
|
|
@@ -246,6 +251,8 @@ function TimePicker({
|
|
|
246
251
|
role: "combobox",
|
|
247
252
|
"aria-expanded": open,
|
|
248
253
|
"aria-haspopup": "dialog",
|
|
254
|
+
"aria-controls": open ? dialogId : void 0,
|
|
255
|
+
...fieldA11y,
|
|
249
256
|
className: "tabular-nums",
|
|
250
257
|
allowClear,
|
|
251
258
|
onClear: clear,
|
|
@@ -285,6 +292,9 @@ function TimePicker({
|
|
|
285
292
|
/* @__PURE__ */ jsx(
|
|
286
293
|
PopoverContent,
|
|
287
294
|
{
|
|
295
|
+
id: dialogId,
|
|
296
|
+
role: "dialog",
|
|
297
|
+
"aria-label": t("dataEntry.timePicker.openPicker") ?? "Time picker",
|
|
288
298
|
className: "w-auto p-0",
|
|
289
299
|
align: "end",
|
|
290
300
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { TransferProp } from "../../props/components/data-entry.prop.js";
|
|
2
2
|
export type { TransferProp, TransferProp as TransferProps, TransferItemProp, } from "../../props/components/data-entry.prop.js";
|
|
3
|
-
export declare function Transfer({ dataSource, targetKeys, onValueChange, titles, showSearch, oneWay, disabled, className, selectedKeys: selectedKeysProp, onSelectChange, }: TransferProp): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function Transfer({ dataSource, targetKeys, onValueChange, titles, showSearch, oneWay, disabled, id, className, selectedKeys: selectedKeysProp, onSelectChange, ...ariaProps }: TransferProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { Button } from "../general/button.js";
|
|
8
9
|
import { ScrollArea } from "../data-display/scroll-area.js";
|
|
9
10
|
import { Checkbox } from "./checkbox.js";
|
|
@@ -104,11 +105,14 @@ function Transfer({
|
|
|
104
105
|
showSearch,
|
|
105
106
|
oneWay,
|
|
106
107
|
disabled,
|
|
108
|
+
id,
|
|
107
109
|
className,
|
|
108
110
|
selectedKeys: selectedKeysProp,
|
|
109
|
-
onSelectChange
|
|
111
|
+
onSelectChange,
|
|
112
|
+
...ariaProps
|
|
110
113
|
}) {
|
|
111
114
|
const { t } = useTranslation();
|
|
115
|
+
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
112
116
|
const [internalSelected, setInternalSelected] = React.useState([[], []]);
|
|
113
117
|
const selected = selectedKeysProp ?? internalSelected;
|
|
114
118
|
const sourceItems = dataSource.filter((item) => !targetKeys.includes(item.key));
|
|
@@ -130,64 +134,74 @@ function Transfer({
|
|
|
130
134
|
};
|
|
131
135
|
const leftTitle = titles?.[0] ?? t("dataEntry.transfer.source");
|
|
132
136
|
const rightTitle = titles?.[1] ?? t("dataEntry.transfer.target");
|
|
133
|
-
return /* @__PURE__ */ jsxs(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
137
|
+
return /* @__PURE__ */ jsxs(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
role: "group",
|
|
141
|
+
id,
|
|
142
|
+
...groupA11y,
|
|
143
|
+
"aria-disabled": disabled ? true : void 0,
|
|
144
|
+
className: cn("flex flex-wrap items-stretch gap-3", className),
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
TransferPanel,
|
|
148
|
+
{
|
|
149
|
+
direction: "left",
|
|
150
|
+
title: leftTitle,
|
|
151
|
+
items: sourceItems,
|
|
152
|
+
selectedKeys: selected[0],
|
|
153
|
+
onSelectChange: (keys) => setSelected(0, keys),
|
|
154
|
+
onSelectAll: (checked) => setSelected(0, checked ? sourceItems.filter((i) => !i.disabled).map((i) => i.key) : []),
|
|
155
|
+
showSearch,
|
|
156
|
+
disabled,
|
|
157
|
+
searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
|
|
158
|
+
emptyText: t("dataEntry.transfer.empty")
|
|
159
|
+
}
|
|
160
|
+
),
|
|
161
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center gap-2", children: [
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
Button,
|
|
164
|
+
{
|
|
165
|
+
type: "button",
|
|
166
|
+
size: "icon",
|
|
167
|
+
variant: "outline",
|
|
168
|
+
disabled: Boolean(disabled) || selected[0].length === 0,
|
|
169
|
+
"aria-label": t("dataEntry.transfer.moveRight"),
|
|
170
|
+
onClick: () => move("right"),
|
|
171
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
|
|
172
|
+
}
|
|
173
|
+
),
|
|
174
|
+
!oneWay && /* @__PURE__ */ jsx(
|
|
175
|
+
Button,
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
size: "icon",
|
|
179
|
+
variant: "outline",
|
|
180
|
+
disabled: Boolean(disabled) || selected[1].length === 0,
|
|
181
|
+
"aria-label": t("dataEntry.transfer.moveLeft"),
|
|
182
|
+
onClick: () => move("left"),
|
|
183
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsx(
|
|
188
|
+
TransferPanel,
|
|
189
|
+
{
|
|
190
|
+
direction: "right",
|
|
191
|
+
title: rightTitle,
|
|
192
|
+
items: targetItems,
|
|
193
|
+
selectedKeys: selected[1],
|
|
194
|
+
onSelectChange: (keys) => setSelected(1, keys),
|
|
195
|
+
onSelectAll: (checked) => setSelected(1, checked ? targetItems.filter((i) => !i.disabled).map((i) => i.key) : []),
|
|
196
|
+
showSearch,
|
|
197
|
+
disabled,
|
|
198
|
+
searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
|
|
199
|
+
emptyText: t("dataEntry.transfer.empty")
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
);
|
|
191
205
|
}
|
|
192
206
|
export {
|
|
193
207
|
Transfer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TreeSelectProp } from "../../props/components/data-entry.prop.js";
|
|
2
2
|
export type { TreeSelectProp, TreeSelectProp as TreeSelectProps, } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export { SHOW_CHILD, SHOW_PARENT, SHOW_ALL } from "./tree-select-strategy.js";
|
|
4
|
-
declare function TreeSelectRoot({ treeData: treeDataProp, value, defaultValue, onValueChange, multiple, treeCheckable, treeCheckStrictly, showSearch, showCheckedStrategy, treeDefaultExpandAll, placeholder, disabled, allowClear, className, id, fieldNames, }: TreeSelectProp): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function TreeSelectRoot({ treeData: treeDataProp, value, defaultValue, onValueChange, multiple, treeCheckable, treeCheckStrictly, showSearch, showCheckedStrategy, treeDefaultExpandAll, placeholder, disabled, allowClear, className, id, fieldNames, ...ariaProps }: TreeSelectProp): import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
export declare const TreeSelect: typeof TreeSelectRoot & {
|
|
6
6
|
SHOW_CHILD: "SHOW_CHILD";
|
|
7
7
|
SHOW_PARENT: "SHOW_PARENT";
|