@godxjp/ui 20.0.0 → 20.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/README.md +8 -2
- package/dist/app/theme-axes.d.ts +2 -0
- package/dist/app/theme-axes.js +45 -0
- package/dist/components/charts/chart-cartesian.d.ts +2 -1
- package/dist/components/charts/chart-cartesian.js +52 -3
- package/dist/components/charts/chart-category-axis.d.ts +55 -0
- package/dist/components/charts/chart-category-axis.js +93 -0
- package/dist/components/charts/chart-frame.d.ts +10 -1
- package/dist/components/charts/chart-frame.js +19 -3
- package/dist/components/charts/compact-bar-trend.d.ts +1 -1
- package/dist/components/charts/compact-bar-trend.js +2 -0
- package/dist/components/charts/pie-chart.d.ts +1 -1
- package/dist/components/charts/pie-chart.js +12 -1
- package/dist/components/charts/recharts-peer.d.ts +49 -0
- package/dist/components/charts/recharts-peer.js +45 -0
- package/dist/components/data-display/card.d.ts +12 -1
- package/dist/components/data-display/card.js +22 -4
- package/dist/components/data-display/code-block.js +6 -2
- package/dist/components/data-display/data-table.d.ts +39 -4
- package/dist/components/data-display/data-table.js +802 -270
- package/dist/components/data-display/descriptions.d.ts +27 -6
- package/dist/components/data-display/descriptions.js +58 -17
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/list-row.d.ts +10 -1
- package/dist/components/data-display/list-row.js +7 -1
- package/dist/components/data-display/popover.d.ts +2 -2
- package/dist/components/data-display/popover.js +61 -11
- package/dist/components/data-display/range-timeline.d.ts +38 -0
- package/dist/components/data-display/range-timeline.js +161 -0
- package/dist/components/data-display/scroll-area.js +13 -2
- package/dist/components/data-display/service-launcher-card.js +10 -10
- package/dist/components/data-display/table.d.ts +11 -3
- package/dist/components/data-display/table.js +48 -18
- package/dist/components/data-display/tree-list.js +4 -9
- package/dist/components/data-entry/calendar.d.ts +2 -2
- package/dist/components/data-entry/calendar.js +12 -1
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +188 -24
- package/dist/components/data-entry/color-picker.d.ts +1 -1
- package/dist/components/data-entry/color-picker.js +17 -5
- package/dist/components/data-entry/control-appearance.d.ts +64 -0
- package/dist/components/data-entry/control-appearance.js +39 -0
- package/dist/components/data-entry/control-surface.d.ts +61 -0
- package/dist/components/data-entry/control-surface.js +39 -0
- package/dist/components/data-entry/date-picker.d.ts +1 -1
- package/dist/components/data-entry/date-picker.js +344 -113
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.js +278 -140
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +39 -4
- package/dist/components/data-entry/form.d.ts +4 -0
- package/dist/components/data-entry/form.js +4 -2
- package/dist/components/data-entry/index.d.ts +7 -3
- package/dist/components/data-entry/index.js +10 -1
- package/dist/components/data-entry/input-otp.d.ts +1 -1
- package/dist/components/data-entry/input.d.ts +12 -34
- package/dist/components/data-entry/input.js +92 -24
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-picker.js +47 -10
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.js +51 -11
- package/dist/components/data-entry/number-input.d.ts +7 -0
- package/dist/components/data-entry/number-input.js +147 -98
- package/dist/components/data-entry/password-input.d.ts +1 -1
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/radio.js +61 -13
- package/dist/components/data-entry/search-input.d.ts +1 -1
- package/dist/components/data-entry/search-input.js +16 -2
- package/dist/components/data-entry/search-select.d.ts +2 -2
- package/dist/components/data-entry/search-select.js +209 -78
- package/dist/components/data-entry/select.d.ts +22 -4
- package/dist/components/data-entry/select.js +215 -163
- package/dist/components/data-entry/slider.d.ts +9 -1
- package/dist/components/data-entry/slider.js +87 -9
- package/dist/components/data-entry/switch.d.ts +3 -0
- package/dist/components/data-entry/switch.js +29 -3
- package/dist/components/data-entry/textarea.d.ts +14 -56
- package/dist/components/data-entry/textarea.js +80 -33
- package/dist/components/data-entry/time-picker.d.ts +2 -2
- package/dist/components/data-entry/time-picker.js +333 -109
- package/dist/components/data-entry/time-range-picker.d.ts +5 -0
- package/dist/components/data-entry/time-range-picker.js +89 -0
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +86 -28
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +201 -113
- package/dist/components/data-entry/tree-utils.js +7 -14
- package/dist/components/data-entry/upload-files.d.ts +2 -0
- package/dist/components/data-entry/upload-files.js +31 -0
- package/dist/components/data-entry/upload-request.d.ts +4 -0
- package/dist/components/data-entry/upload-request.js +53 -0
- package/dist/components/data-entry/upload-types.d.ts +28 -0
- package/dist/components/data-entry/upload-types.js +2 -0
- package/dist/components/data-entry/upload.d.ts +2 -2
- package/dist/components/data-entry/upload.js +475 -115
- package/dist/components/feedback/dialog.js +16 -19
- package/dist/components/general/button.d.ts +1 -1
- package/dist/components/general/button.js +5 -1
- package/dist/components/general/index.d.ts +1 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/general/typography.d.ts +3 -0
- package/dist/components/general/typography.js +15 -1
- package/dist/components/general/visually-hidden.d.ts +3 -0
- package/dist/components/general/visually-hidden.js +9 -0
- package/dist/components/layout/app-launcher.d.ts +34 -0
- package/dist/components/layout/app-launcher.js +228 -0
- package/dist/components/layout/app-shell.d.ts +2 -0
- package/dist/components/layout/app-shell.js +20 -7
- package/dist/components/layout/breadcrumb.d.ts +14 -2
- package/dist/components/layout/breadcrumb.js +46 -4
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +23 -2
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.js +5 -1
- package/dist/components/layout/org-switcher.js +20 -1
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +5 -3
- package/dist/components/layout/responsive-grid.d.ts +16 -2
- package/dist/components/layout/responsive-grid.js +29 -2
- package/dist/components/layout/topbar-item.d.ts +3 -0
- package/dist/components/layout/topbar-item.js +18 -3
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +27 -6
- package/dist/components/navigation/dropdown-menu.d.ts +30 -2
- package/dist/components/navigation/dropdown-menu.js +68 -11
- package/dist/components/navigation/pagination.d.ts +2 -2
- package/dist/components/navigation/pagination.js +155 -83
- package/dist/components/navigation/steps.d.ts +2 -2
- package/dist/components/navigation/steps.js +29 -4
- package/dist/components/navigation/tabs.d.ts +5 -33
- package/dist/components/navigation/tabs.js +232 -64
- package/dist/components/ui/input-otp.d.ts +26 -28
- package/dist/components/ui/input-otp.js +50 -7
- package/dist/components/ui/password-input.d.ts +36 -2
- package/dist/components/ui/password-input.js +27 -7
- package/dist/components/ui/rating.d.ts +25 -0
- package/dist/components/ui/rating.js +67 -27
- package/dist/components/ui/segmented.d.ts +9 -0
- package/dist/components/ui/segmented.js +17 -2
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +13 -0
- package/dist/components/ui/tag-input.d.ts +45 -0
- package/dist/components/ui/tag-input.js +109 -27
- package/dist/form/form-context.d.ts +29 -0
- package/dist/form/form-context.js +44 -4
- package/dist/form/form-field-array.d.ts +22 -0
- package/dist/form/form-field-array.js +50 -0
- package/dist/form/form-field-control.d.ts +2 -1
- package/dist/form/form-field-control.js +116 -40
- package/dist/form/form-root.d.ts +2 -1
- package/dist/form/form-root.js +142 -23
- package/dist/form/index.d.ts +3 -1
- package/dist/form/index.js +12 -1
- package/dist/i18n/messages/en.json +56 -5
- package/dist/i18n/messages/ja.json +56 -5
- package/dist/i18n/messages/vi.json +56 -5
- package/dist/inertia/index.d.ts +20 -0
- package/dist/inertia/index.js +60 -1
- package/dist/lib/control-styles.d.ts +25 -3
- package/dist/lib/control-styles.js +9 -3
- package/dist/lib/datetime/picker-format.d.ts +8 -0
- package/dist/lib/datetime/picker-format.js +54 -0
- package/dist/props/components/charts.prop.d.ts +21 -0
- package/dist/props/components/data-display.prop.d.ts +55 -4
- package/dist/props/components/data-entry.prop.d.ts +687 -43
- package/dist/props/components/form.prop.d.ts +142 -4
- package/dist/props/components/general.prop.d.ts +17 -1
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +163 -6
- package/dist/props/components/navigation.prop.d.ts +113 -3
- package/dist/props/registry.d.ts +376 -10
- package/dist/props/registry.js +493 -9
- package/dist/props/vocabulary/data.prop.d.ts +179 -1
- package/dist/props/vocabulary/index.d.ts +4 -4
- package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
- package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
- package/dist/props/vocabulary/shared.prop.d.ts +33 -0
- package/dist/styles/badge-layout.css +2 -1
- package/dist/styles/card-layout.css +26 -4
- package/dist/styles/chart-layout.css +15 -0
- package/dist/styles/control.css +515 -14
- package/dist/styles/data-display-layout.css +241 -6
- package/dist/styles/data-entry-layout.css +13 -0
- package/dist/styles/layout.css +221 -4
- package/dist/styles/navigation-layout.css +227 -0
- package/dist/styles/shell-layout.css +229 -5
- package/dist/styles/table-layout.css +95 -6
- package/dist/styles/text-layout.css +5 -0
- package/dist/tokens/base.css +1 -0
- package/dist/tokens/components/badge.css +1 -0
- package/dist/tokens/components/chart.css +6 -0
- package/dist/tokens/components/control.css +79 -2
- package/dist/tokens/components/data-display.css +9 -0
- package/dist/tokens/components/descriptions.css +11 -0
- package/dist/tokens/components/flex.css +8 -0
- package/dist/tokens/components/navigation.css +55 -0
- package/dist/tokens/components/shell.css +50 -2
- package/dist/tokens/components/table.css +14 -0
- package/dist/tokens/semantic/layout.css +9 -0
- package/docs/CONSUMER-RULES.md +13 -0
- package/docs/DESIGN-AUTHORITY.md +43 -6
- package/docs/DEVELOPMENT.md +4 -3
- package/docs/FORMS.md +151 -6
- package/docs/FRAME-COVERAGE-REPORT.md +29 -16
- package/docs/README.md +1 -1
- package/docs/STANDARDS-vocabulary-tokens.md +1 -1
- package/docs/TESTING.md +15 -6
- package/docs/charts/cjk-category-axis.tsx +81 -0
- package/docs/data-display/card/index.tsx +22 -0
- package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
- package/docs/data-display/data-table/index.tsx +23 -0
- package/docs/data-display/descriptions.tsx +41 -0
- package/docs/data-display/scroll-area.tsx +31 -25
- package/docs/data-display/table.tsx +104 -45
- package/docs/data-display/timeline.tsx +41 -0
- package/docs/data-entry/date-picker.tsx +85 -0
- package/docs/data-entry/date-range-picker.tsx +26 -0
- package/docs/data-entry/form-dynamic-fields.tsx +199 -0
- package/docs/data-entry/form.tsx +74 -4
- package/docs/data-entry/input-otp.tsx +24 -0
- package/docs/data-entry/input.tsx +46 -1
- package/docs/data-entry/month-range-picker.tsx +1 -1
- package/docs/data-entry/select.tsx +27 -0
- package/docs/data-entry/switch.tsx +41 -0
- package/docs/data-entry/textarea.tsx +38 -0
- package/docs/data-entry/time-picker.tsx +85 -1
- package/docs/data-entry/time-range-picker.tsx +55 -0
- package/docs/data-entry/transfer.tsx +17 -0
- package/docs/data-entry/upload.tsx +56 -12
- package/docs/feedback/tooltip.tsx +1 -1
- package/docs/general/activity.tsx +2 -2
- package/docs/general/button/index.tsx +14 -1
- package/docs/general/typography.tsx +14 -1
- package/docs/layout/app-launcher.tsx +151 -0
- package/docs/layout/app-shell.tsx +11 -0
- package/docs/layout/flex.tsx +50 -0
- package/docs/layout/responsive-grid.tsx +21 -1
- package/docs/layout/topbar.tsx +5 -9
- package/docs/navigation/app-setting-picker.tsx +11 -0
- package/docs/navigation/breadcrumb.tsx +48 -0
- package/docs/navigation/dropdown-menu.tsx +21 -0
- package/docs/navigation/pagination.tsx +52 -0
- package/docs/navigation/steps.tsx +37 -0
- package/docs/navigation/tabs.tsx +97 -1
- package/docs/query/button-refetch.tsx +1 -0
- package/package.json +19 -2
- package/scripts/_agent-setup.mjs +19 -2
- package/scripts/init-guinea-pig.mjs +26 -4
- package/scripts/ui-audit.mjs +243 -30
|
@@ -5,50 +5,67 @@ import { ChevronsUpDown, Loader2, X } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
-
import {
|
|
8
|
+
import { controlSurfaceTriggerClass } from "../../lib/control-styles.js";
|
|
9
|
+
import {
|
|
10
|
+
applyMaxTagCount,
|
|
11
|
+
controlSurfaceAttrs,
|
|
12
|
+
resolveAllowClear,
|
|
13
|
+
resolveAriaInvalid
|
|
14
|
+
} from "./control-surface.js";
|
|
9
15
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
10
16
|
import { Command, CommandGroup } from "./command.js";
|
|
11
17
|
import { Input } from "./input.js";
|
|
12
18
|
const DEBOUNCE_MS = 250;
|
|
13
|
-
function SearchSelect({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
19
|
+
function SearchSelect(props) {
|
|
20
|
+
const {
|
|
21
|
+
options: staticOptions,
|
|
22
|
+
loadOptions,
|
|
23
|
+
renderOption,
|
|
24
|
+
labelRender,
|
|
25
|
+
selectedLabel,
|
|
26
|
+
selectedIcon,
|
|
27
|
+
placeholder,
|
|
28
|
+
searchPlaceholder,
|
|
29
|
+
emptyMessage,
|
|
30
|
+
loadingMessage,
|
|
31
|
+
errorMessage,
|
|
32
|
+
clearLabel,
|
|
33
|
+
clearable = true,
|
|
34
|
+
disabled = false,
|
|
35
|
+
readOnly = false,
|
|
36
|
+
size,
|
|
37
|
+
status,
|
|
38
|
+
variant,
|
|
39
|
+
loading: loadingProp = false,
|
|
40
|
+
open: openProp,
|
|
41
|
+
defaultOpen = false,
|
|
42
|
+
onOpenChange,
|
|
43
|
+
search: searchProp,
|
|
44
|
+
onSearchChange,
|
|
45
|
+
filterOption,
|
|
46
|
+
filterSort,
|
|
47
|
+
autoClearSearchValue = true,
|
|
48
|
+
optionRender,
|
|
49
|
+
menuItemSelectedIcon,
|
|
50
|
+
notFoundContent,
|
|
51
|
+
popupMatchSelectWidth = true,
|
|
52
|
+
allowClear,
|
|
53
|
+
onClear,
|
|
54
|
+
renderError,
|
|
55
|
+
renderLoadMore,
|
|
56
|
+
name,
|
|
57
|
+
id,
|
|
58
|
+
className,
|
|
59
|
+
"data-testid": dataTestId,
|
|
60
|
+
"data-field": dataField,
|
|
61
|
+
"aria-label": ariaLabel,
|
|
62
|
+
"aria-labelledby": ariaLabelledby,
|
|
63
|
+
"aria-describedby": ariaDescribedby,
|
|
64
|
+
"aria-errormessage": ariaErrorMessage,
|
|
65
|
+
"aria-invalid": ariaInvalid,
|
|
66
|
+
"aria-required": ariaRequired
|
|
67
|
+
} = props;
|
|
68
|
+
const multiple = props.mode === "multiple";
|
|
52
69
|
const { t } = useTranslation();
|
|
53
70
|
const nameFallback = useFieldNameFallback({
|
|
54
71
|
"aria-label": ariaLabel,
|
|
@@ -62,7 +79,7 @@ function SearchSelect({
|
|
|
62
79
|
const reactId = React.useId();
|
|
63
80
|
const listId = `${reactId}-listbox`;
|
|
64
81
|
const optionDomId = (optionValue) => `${reactId}-opt-${optionValue}`;
|
|
65
|
-
const [internalOpen, setInternalOpen] = React.useState(
|
|
82
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
66
83
|
const isOpenControlled = openProp !== void 0;
|
|
67
84
|
const open = isOpenControlled ? openProp : internalOpen;
|
|
68
85
|
const setOpen = React.useCallback(
|
|
@@ -89,22 +106,34 @@ function SearchSelect({
|
|
|
89
106
|
const [loading, setLoading] = React.useState(false);
|
|
90
107
|
const [error, setError] = React.useState(false);
|
|
91
108
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
92
|
-
const [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
109
|
+
const [pickedOptions, setPickedOptions] = React.useState(
|
|
110
|
+
{}
|
|
111
|
+
);
|
|
112
|
+
const [internalValue, setInternalValue] = React.useState(
|
|
113
|
+
props.defaultValue ?? (props.mode === "multiple" ? [] : "")
|
|
114
|
+
);
|
|
115
|
+
const isControlled = props.value !== void 0;
|
|
116
|
+
const rawValue = isControlled ? props.value : internalValue;
|
|
117
|
+
const values = React.useMemo(() => {
|
|
118
|
+
if (!multiple) return [];
|
|
119
|
+
return Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : [];
|
|
120
|
+
}, [multiple, rawValue]);
|
|
121
|
+
const value = multiple ? "" : Array.isArray(rawValue) ? rawValue[0] ?? "" : rawValue;
|
|
122
|
+
const isSelected = (optionValue) => multiple ? values.includes(optionValue) : value === optionValue;
|
|
123
|
+
const hasSelection = multiple ? values.length > 0 : Boolean(value);
|
|
96
124
|
const reqId = React.useRef(0);
|
|
97
125
|
const resolvedLoad = React.useMemo(
|
|
98
126
|
() => loadOptions ?? (async ({ query: search }) => {
|
|
99
127
|
const needle = search.trim();
|
|
100
128
|
const list = staticOptions ?? [];
|
|
101
129
|
const matches = (option) => filterOption ? filterOption(option, needle) : option.label.toLowerCase().includes(needle.toLowerCase()) || option.value.toLowerCase().includes(needle.toLowerCase());
|
|
130
|
+
const kept = needle ? list.filter(matches) : list;
|
|
102
131
|
return {
|
|
103
|
-
options:
|
|
132
|
+
options: filterSort ? [...kept].sort((a, b) => filterSort(a, b, { searchValue: needle })) : kept,
|
|
104
133
|
hasMore: false
|
|
105
134
|
};
|
|
106
135
|
}),
|
|
107
|
-
[loadOptions, staticOptions, filterOption]
|
|
136
|
+
[loadOptions, staticOptions, filterOption, filterSort]
|
|
108
137
|
);
|
|
109
138
|
React.useEffect(() => {
|
|
110
139
|
const handle = window.setTimeout(() => setDebouncedQuery(query.trim()), DEBOUNCE_MS);
|
|
@@ -163,20 +192,59 @@ function SearchSelect({
|
|
|
163
192
|
[grouped]
|
|
164
193
|
);
|
|
165
194
|
const resolvedPlaceholder = placeholder ?? t("dataEntry.searchSelect.placeholder");
|
|
166
|
-
const
|
|
195
|
+
const optionFor = (optionValue) => pickedOptions[optionValue] ?? (staticOptions ?? []).find((option) => option.value === optionValue) ?? loaded.find((option) => option.value === optionValue) ?? null;
|
|
196
|
+
const selectedOption = value ? optionFor(value) : null;
|
|
167
197
|
const currentLabel = value ? selectedOption?.label ?? selectedLabel ?? value : resolvedPlaceholder;
|
|
168
198
|
const currentIcon = value ? selectedOption?.icon ?? selectedIcon : null;
|
|
199
|
+
const selectedChips = multiple ? values.map((entry) => ({
|
|
200
|
+
value: entry,
|
|
201
|
+
label: optionFor(entry)?.label ?? entry
|
|
202
|
+
})) : [];
|
|
203
|
+
const {
|
|
204
|
+
visible: visibleChips,
|
|
205
|
+
omitted: omittedChips,
|
|
206
|
+
overflow: chipOverflow
|
|
207
|
+
} = applyMaxTagCount(
|
|
208
|
+
selectedChips,
|
|
209
|
+
props.mode === "multiple" ? props.maxTagCount : void 0,
|
|
210
|
+
props.mode === "multiple" ? props.maxTagPlaceholder : void 0
|
|
211
|
+
);
|
|
212
|
+
const maxCount = props.mode === "multiple" ? props.maxCount : void 0;
|
|
213
|
+
const atMaxCount = maxCount !== void 0 && values.length >= maxCount;
|
|
214
|
+
const remember = (option) => setPickedOptions((prev) => ({ ...prev, [option.value]: option }));
|
|
169
215
|
const select = (option) => {
|
|
170
216
|
if (option.disabled) return;
|
|
171
|
-
|
|
217
|
+
if (props.mode === "multiple") {
|
|
218
|
+
const already = values.includes(option.value);
|
|
219
|
+
if (!already && atMaxCount) return;
|
|
220
|
+
const next = already ? values.filter((entry) => entry !== option.value) : [...values, option.value];
|
|
221
|
+
remember(option);
|
|
222
|
+
if (!isControlled) setInternalValue(next);
|
|
223
|
+
props.onValueChange?.(
|
|
224
|
+
next,
|
|
225
|
+
next.map((entry) => entry === option.value ? option : optionFor(entry)).filter((entry) => entry !== null)
|
|
226
|
+
);
|
|
227
|
+
if (already) props.onDeselect?.(option.value, option);
|
|
228
|
+
else props.onSelect?.(option.value, option);
|
|
229
|
+
if (autoClearSearchValue) setQuery("");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
remember(option);
|
|
172
233
|
if (!isControlled) setInternalValue(option.value);
|
|
173
|
-
onValueChange?.(option.value, option);
|
|
234
|
+
props.onValueChange?.(option.value, option);
|
|
235
|
+
props.onSelect?.(option.value, option);
|
|
236
|
+
if (autoClearSearchValue) setQuery("");
|
|
174
237
|
setOpen(false);
|
|
175
238
|
};
|
|
176
239
|
const clear = () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
240
|
+
if (props.mode === "multiple") {
|
|
241
|
+
if (!isControlled) setInternalValue([]);
|
|
242
|
+
props.onValueChange?.([], []);
|
|
243
|
+
} else {
|
|
244
|
+
if (!isControlled) setInternalValue("");
|
|
245
|
+
props.onValueChange?.("", void 0);
|
|
246
|
+
}
|
|
247
|
+
onClear?.();
|
|
180
248
|
setOpen(false);
|
|
181
249
|
};
|
|
182
250
|
const onScroll = (event) => {
|
|
@@ -226,7 +294,13 @@ function SearchSelect({
|
|
|
226
294
|
const optionTestId = (optionValue) => dataTestId ? `${dataTestId}-option-${optionValue}` : void 0;
|
|
227
295
|
const activeOption = flatOrdered[activeIndex];
|
|
228
296
|
const activeOptionId = activeOption ? optionDomId(activeOption.value) : void 0;
|
|
229
|
-
const
|
|
297
|
+
const clearControl = resolveAllowClear(
|
|
298
|
+
allowClear,
|
|
299
|
+
clearable,
|
|
300
|
+
clearLabel ?? t("dataEntry.searchSelect.clear")
|
|
301
|
+
);
|
|
302
|
+
const showClear = clearControl.enabled && hasSelection && !disabled && !readOnly && !loadingProp;
|
|
303
|
+
const surface = controlSurfaceAttrs({ variant, status, size });
|
|
230
304
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
231
305
|
/* @__PURE__ */ jsxs(
|
|
232
306
|
Popover,
|
|
@@ -235,7 +309,7 @@ function SearchSelect({
|
|
|
235
309
|
onOpenChange: (next) => {
|
|
236
310
|
if (readOnly && next) return;
|
|
237
311
|
setOpen(next);
|
|
238
|
-
if (!next) setQuery("");
|
|
312
|
+
if (!next && autoClearSearchValue) setQuery("");
|
|
239
313
|
},
|
|
240
314
|
children: [
|
|
241
315
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
@@ -244,22 +318,24 @@ function SearchSelect({
|
|
|
244
318
|
id,
|
|
245
319
|
type: "button",
|
|
246
320
|
role: "combobox",
|
|
247
|
-
|
|
321
|
+
...surface,
|
|
248
322
|
"aria-expanded": open,
|
|
249
323
|
"aria-controls": open ? listId : void 0,
|
|
250
324
|
"aria-label": triggerAriaLabel,
|
|
251
325
|
"aria-labelledby": triggerAriaLabelledby,
|
|
252
326
|
"aria-describedby": ariaDescribedby,
|
|
253
327
|
"aria-errormessage": ariaErrorMessage,
|
|
254
|
-
"aria-invalid": ariaInvalid,
|
|
328
|
+
"aria-invalid": resolveAriaInvalid(ariaInvalid, status),
|
|
255
329
|
"aria-required": ariaRequired,
|
|
256
330
|
"aria-readonly": readOnly || void 0,
|
|
331
|
+
"aria-busy": loadingProp || void 0,
|
|
257
332
|
disabled,
|
|
258
333
|
"data-testid": dataTestId,
|
|
259
334
|
"data-field": resolvedField,
|
|
260
|
-
"data-value": value || void 0,
|
|
335
|
+
"data-value": (multiple ? values.join(",") : value) || void 0,
|
|
336
|
+
"data-mode": multiple ? "multiple" : void 0,
|
|
261
337
|
className: cn(
|
|
262
|
-
|
|
338
|
+
controlSurfaceTriggerClass,
|
|
263
339
|
"w-full justify-start",
|
|
264
340
|
// Reserve trailing room for the single clear-or-chevron overlay rendered below.
|
|
265
341
|
"ui-control-trigger-affixed"
|
|
@@ -269,9 +345,36 @@ function SearchSelect({
|
|
|
269
345
|
{
|
|
270
346
|
className: cn(
|
|
271
347
|
"ui-search-select-option-body text-start",
|
|
272
|
-
!
|
|
348
|
+
!hasSelection && "text-muted-foreground"
|
|
273
349
|
),
|
|
274
|
-
children:
|
|
350
|
+
children: multiple ? hasSelection ? (
|
|
351
|
+
// Labels, not removable chips: the trigger IS a <button>, and a per-label remove
|
|
352
|
+
// button inside it would be a button nested in a button (invalid HTML →
|
|
353
|
+
// hydration error). Removal is a second click on the row, or the clear ✕.
|
|
354
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-search-select-values", "data-slot": "search-select-values", children: [
|
|
355
|
+
visibleChips.map((chip) => /* @__PURE__ */ jsx(
|
|
356
|
+
"span",
|
|
357
|
+
{
|
|
358
|
+
className: "ui-search-select-value",
|
|
359
|
+
"data-slot": "search-select-value",
|
|
360
|
+
children: labelRender ? labelRender({
|
|
361
|
+
value: chip.value,
|
|
362
|
+
label: chip.label,
|
|
363
|
+
option: optionFor(chip.value) ?? void 0
|
|
364
|
+
}) : chip.label
|
|
365
|
+
},
|
|
366
|
+
chip.value
|
|
367
|
+
)),
|
|
368
|
+
omittedChips.length > 0 ? /* @__PURE__ */ jsx(
|
|
369
|
+
"span",
|
|
370
|
+
{
|
|
371
|
+
className: "ui-search-select-value",
|
|
372
|
+
"data-slot": "search-select-value-overflow",
|
|
373
|
+
children: chipOverflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length })
|
|
374
|
+
}
|
|
375
|
+
) : null
|
|
376
|
+
] })
|
|
377
|
+
) : /* @__PURE__ */ jsx("span", { className: "truncate", children: resolvedPlaceholder }) : value && labelRender ? labelRender({ value, label: currentLabel, option: selectedOption ?? void 0 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
275
378
|
currentIcon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: currentIcon }) : null,
|
|
276
379
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: currentLabel })
|
|
277
380
|
] })
|
|
@@ -279,7 +382,7 @@ function SearchSelect({
|
|
|
279
382
|
)
|
|
280
383
|
}
|
|
281
384
|
) }),
|
|
282
|
-
resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value, readOnly: true }) : null,
|
|
385
|
+
resolvedName ? multiple ? values.map((entry) => /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: entry, readOnly: true }, entry)) : /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value, readOnly: true }) : null,
|
|
283
386
|
/* @__PURE__ */ jsx(
|
|
284
387
|
PopoverContent,
|
|
285
388
|
{
|
|
@@ -289,6 +392,10 @@ function SearchSelect({
|
|
|
289
392
|
sideOffset: 4,
|
|
290
393
|
collisionPadding: 12,
|
|
291
394
|
className: "ui-search-select-panel",
|
|
395
|
+
"data-popup-match": popupMatchSelectWidth === true ? void 0 : popupMatchSelectWidth === false ? "content" : "fixed",
|
|
396
|
+
style: typeof popupMatchSelectWidth === "number" ? {
|
|
397
|
+
"--search-select-panel-inline-size": `${popupMatchSelectWidth}px`
|
|
398
|
+
} : void 0,
|
|
292
399
|
children: /* @__PURE__ */ jsxs(Command, { value, shouldFilter: false, className: "ui-search-select-command", children: [
|
|
293
400
|
/* @__PURE__ */ jsx("div", { className: "ui-search-select-search", children: /* @__PURE__ */ jsx(
|
|
294
401
|
Input,
|
|
@@ -310,38 +417,50 @@ function SearchSelect({
|
|
|
310
417
|
{
|
|
311
418
|
id: listId,
|
|
312
419
|
role: "listbox",
|
|
420
|
+
"aria-multiselectable": multiple || void 0,
|
|
313
421
|
"aria-busy": loading,
|
|
314
422
|
className: "ui-search-select-list",
|
|
315
423
|
onScroll,
|
|
316
424
|
children: [
|
|
317
425
|
grouped.map((group) => {
|
|
318
|
-
const rows = group.items.map(({ option, index }) => /* @__PURE__ */
|
|
426
|
+
const rows = group.items.map(({ option, index }) => /* @__PURE__ */ jsxs(
|
|
319
427
|
"div",
|
|
320
428
|
{
|
|
321
429
|
id: optionDomId(option.value),
|
|
322
430
|
role: "option",
|
|
323
431
|
"data-testid": optionTestId(option.value),
|
|
324
|
-
"aria-selected":
|
|
325
|
-
"aria-disabled": option.disabled || void 0,
|
|
326
|
-
"data-disabled": option.disabled || void 0,
|
|
432
|
+
"aria-selected": isSelected(option.value),
|
|
433
|
+
"aria-disabled": option.disabled || atMaxCount && !isSelected(option.value) || void 0,
|
|
434
|
+
"data-disabled": option.disabled || atMaxCount && !isSelected(option.value) || void 0,
|
|
327
435
|
className: cn(
|
|
328
436
|
"ui-command-item",
|
|
329
437
|
// Selected = persistent bg-accent + medium weight (NO check icon — saves width),
|
|
330
438
|
// matching the plain SelectItem's `data-[state=checked]` convention; active =
|
|
331
439
|
// hover/keyboard accent. Same bg so selection stays coherent across both Selects.
|
|
332
|
-
|
|
440
|
+
isSelected(option.value) && "bg-accent text-foreground font-medium",
|
|
333
441
|
activeIndex === index && "bg-accent text-accent-foreground"
|
|
334
442
|
),
|
|
335
443
|
onMouseEnter: () => !option.disabled && setActiveIndex(index),
|
|
336
444
|
onMouseDown: (event) => event.preventDefault(),
|
|
337
445
|
onClick: () => select(option),
|
|
338
|
-
children:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
/* @__PURE__ */
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
446
|
+
children: [
|
|
447
|
+
optionRender ? /* @__PURE__ */ jsx("div", { className: "ui-search-select-option-slot", children: optionRender(option, { index }) }) : renderOption ? /* @__PURE__ */ jsx("div", { className: "ui-search-select-option-slot", children: renderOption(option) }) : /* @__PURE__ */ jsxs("div", { className: "ui-search-select-option-body", children: [
|
|
448
|
+
option.icon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: option.icon }) : null,
|
|
449
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-search-select-option-text", children: [
|
|
450
|
+
/* @__PURE__ */ jsx("span", { className: "ui-search-select-option-label", children: option.label }),
|
|
451
|
+
option.sublabel ? /* @__PURE__ */ jsx("span", { className: "ui-search-select-option-sublabel", children: option.sublabel }) : null
|
|
452
|
+
] })
|
|
453
|
+
] }),
|
|
454
|
+
menuItemSelectedIcon && isSelected(option.value) ? /* @__PURE__ */ jsx(
|
|
455
|
+
"span",
|
|
456
|
+
{
|
|
457
|
+
"data-slot": "search-select-selected-icon",
|
|
458
|
+
className: "ui-search-select-selected-icon",
|
|
459
|
+
"aria-hidden": "true",
|
|
460
|
+
children: menuItemSelectedIcon
|
|
461
|
+
}
|
|
462
|
+
) : null
|
|
463
|
+
]
|
|
345
464
|
},
|
|
346
465
|
option.value
|
|
347
466
|
));
|
|
@@ -372,7 +491,7 @@ function SearchSelect({
|
|
|
372
491
|
"aria-disabled": "true",
|
|
373
492
|
"aria-selected": false,
|
|
374
493
|
className: "ui-search-select-placeholder",
|
|
375
|
-
children: emptyMessage ?? t("dataEntry.searchSelect.empty")
|
|
494
|
+
children: notFoundContent ?? emptyMessage ?? t("dataEntry.searchSelect.empty")
|
|
376
495
|
}
|
|
377
496
|
) : null
|
|
378
497
|
]
|
|
@@ -398,15 +517,27 @@ function SearchSelect({
|
|
|
398
517
|
]
|
|
399
518
|
}
|
|
400
519
|
),
|
|
401
|
-
/* @__PURE__ */ jsx("div", { className: "ui-control-affix", children:
|
|
520
|
+
/* @__PURE__ */ jsx("div", { className: "ui-control-affix", children: loadingProp ? (
|
|
521
|
+
// antd `loading` — the FIELD is in flight (the form is still resolving this value), which
|
|
522
|
+
// is a different claim from the list-level spinner inside the panel. It takes the affix
|
|
523
|
+
// seat so the two can never both animate in the same place.
|
|
524
|
+
/* @__PURE__ */ jsx(
|
|
525
|
+
Loader2,
|
|
526
|
+
{
|
|
527
|
+
"data-slot": "search-select-loading",
|
|
528
|
+
className: "ui-control-affix-icon ui-control-affix-indicator animate-spin",
|
|
529
|
+
"aria-hidden": "true"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
) : showClear ? /* @__PURE__ */ jsx(
|
|
402
533
|
"button",
|
|
403
534
|
{
|
|
404
535
|
type: "button",
|
|
405
|
-
"aria-label":
|
|
536
|
+
"aria-label": clearControl.label,
|
|
406
537
|
"data-testid": optionTestId("clear"),
|
|
407
538
|
className: "ui-control-affix-action",
|
|
408
539
|
onClick: clear,
|
|
409
|
-
children: /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
|
|
540
|
+
children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
|
|
410
541
|
}
|
|
411
542
|
) : /* @__PURE__ */ jsx(
|
|
412
543
|
ChevronsUpDown,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { ControlWidthProp } from "../../props/vocabulary/
|
|
2
|
+
import type { ControlStatusProp, ControlVariantProp, ControlWidthProp, SizeProp } from "../../props/vocabulary/index.js";
|
|
3
3
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
4
|
import type { FieldA11yProps } from "../../lib/field-a11y.js";
|
|
5
5
|
import type { SelectDataProp } from "../../props/components/data-entry.prop.js";
|
|
@@ -20,7 +20,12 @@ export declare function Select(props: SelectProp): React.JSX.Element;
|
|
|
20
20
|
export declare function SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>): React.JSX.Element;
|
|
21
21
|
export declare function SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>): React.JSX.Element;
|
|
22
22
|
export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
|
|
23
|
-
|
|
23
|
+
/** Control height tier — the shared `--control-height` ladder (antd `size`). */
|
|
24
|
+
size?: SizeProp;
|
|
25
|
+
/** Control surface (antd `variant`). Default `outlined`. */
|
|
26
|
+
variant?: ControlVariantProp;
|
|
27
|
+
/** Validation status (antd `status`). `error` also sets `aria-invalid`. */
|
|
28
|
+
status?: ControlStatusProp;
|
|
24
29
|
/**
|
|
25
30
|
* `full` (default) fills the field column — right inside a FormField. `auto` sizes to the
|
|
26
31
|
* selected label — right in a PageContainer `extra` slot, a toolbar or a footer row, where a
|
|
@@ -45,7 +50,20 @@ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectP
|
|
|
45
50
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
46
51
|
export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
47
52
|
export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
48
|
-
export declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> &
|
|
53
|
+
export declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
54
|
+
/**
|
|
55
|
+
* How the popup sizes against its trigger — the DOM half of antd `popupMatchSelectWidth`.
|
|
56
|
+
* Absent = the trigger's width is the floor (antd's `true`); `content` releases that floor;
|
|
57
|
+
* `fixed` pins both edges to `--select-content-inline-size`.
|
|
58
|
+
*/
|
|
59
|
+
"data-popup-match"?: "content" | "fixed";
|
|
60
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
49
61
|
export declare const SelectLabel: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
50
|
-
export declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> &
|
|
62
|
+
export declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
63
|
+
/**
|
|
64
|
+
* antd `menuItemSelectedIcon` — a decorative mark on the picked row. Off by default: this
|
|
65
|
+
* library marks the picked row with fill + weight, which costs no width.
|
|
66
|
+
*/
|
|
67
|
+
selectedIcon?: React.ReactNode;
|
|
68
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
51
69
|
export declare const SelectSeparator: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|