@godxjp/ui 33.1.1 → 33.3.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 +5 -5
- package/agent/START-HERE.md +7 -7
- package/agent/components/Command.json +9 -1
- package/agent/components/ResponsiveGrid.json +2 -2
- package/agent/components/SearchForm.json +115 -0
- package/agent/components/Toolbar.json +6 -3
- package/agent/components/TranslatableInput.json +92 -0
- package/agent/components/TranslatableTextarea.json +84 -0
- package/agent/components-index.json +20 -1
- package/agent/components.json +308 -6
- package/agent/index.json +8 -8
- package/agent/llms.txt +8 -8
- package/agent/patterns/condition-builder.json +31 -0
- package/agent/patterns/data-table-page.json +1 -1
- package/agent/patterns/filter-bar-layouts.json +30 -0
- package/agent/patterns/inertia-list-page.json +1 -1
- package/agent/patterns/list-filters.json +1 -1
- package/agent/patterns/search-form.json +27 -0
- package/agent/patterns-index.json +85 -0
- package/agent/patterns.json +91 -3
- package/agent/rules.json +1 -1
- package/agent/tokens.json +12 -0
- package/dist/components/data-entry/command.d.ts +22 -1
- package/dist/components/data-entry/command.js +14 -1
- package/dist/components/data-entry/form-field.js +10 -4
- package/dist/components/data-entry/index.d.ts +4 -2
- package/dist/components/data-entry/index.js +6 -1
- package/dist/components/data-entry/translatable-input.d.ts +29 -0
- package/dist/components/data-entry/translatable-input.js +184 -0
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/search-form.d.ts +31 -0
- package/dist/components/navigation/search-form.js +112 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +13 -0
- package/dist/i18n/messages/ja.json +13 -0
- package/dist/i18n/messages/vi.json +13 -0
- package/dist/lib/field-a11y.d.ts +15 -0
- package/dist/lib/field-a11y.js +13 -1
- package/dist/lib/intl-cache.d.ts +1 -0
- package/dist/lib/intl-cache.js +4 -0
- package/dist/props/components/data-entry.prop.d.ts +46 -0
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +3 -1
- package/dist/props/components/navigation.prop.d.ts +43 -3
- package/dist/props/registry.d.ts +56 -0
- package/dist/props/registry.js +91 -0
- package/dist/styles/control.css +15 -0
- package/dist/styles/data-entry-layout.css +25 -0
- package/dist/styles/layers.json +29 -1
- package/dist/styles/layout.css +41 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/components/data-entry.css +4 -0
- package/docs/CONSUMER-RULES.md +1 -1
- package/package.json +2 -2
- package/scripts/ui-audit.mjs +148 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { CommandProp } from "../../props/components/data-entry.prop.js";
|
|
2
|
+
import type { CommandProp, CommandSeparatorProp } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { CommandProp, CommandProp as CommandProps };
|
|
4
|
+
export type { CommandSeparatorProp, CommandSeparatorProp as CommandSeparatorProps };
|
|
4
5
|
export declare const Command: React.ForwardRefExoticComponent<Omit<{
|
|
5
6
|
children?: React.ReactNode;
|
|
6
7
|
} & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
@@ -66,3 +67,23 @@ export declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
|
|
|
66
67
|
keywords?: string[];
|
|
67
68
|
forceMount?: boolean;
|
|
68
69
|
} & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
70
|
+
/**
|
|
71
|
+
* A hairline between two groups of one list — e.g. the matches, then `+ Create "<q>"` (gh#1335).
|
|
72
|
+
* It is cmdk's own `Command.Separator`, so it must sit inside the same `Command` as its groups.
|
|
73
|
+
*
|
|
74
|
+
* `alwaysRender` defaults to `true`, the OPPOSITE of cmdk: cmdk drops a separator as soon as the
|
|
75
|
+
* search is non-empty, because in a palette the groups collapse under filtering. A "Create" group
|
|
76
|
+
* exists only BECAUSE the user typed, so with cmdk's default the rule vanished exactly when it
|
|
77
|
+
* was needed. Pass `alwaysRender={false}` for cmdk's palette behaviour.
|
|
78
|
+
*
|
|
79
|
+
* `aria-hidden` by default: the rule is decorative, and a `role="separator"` child is not allowed
|
|
80
|
+
* in the `listbox` CommandList renders (ARIA owns only `option` / `group` there); the groups'
|
|
81
|
+
* headings carry the structure.
|
|
82
|
+
*/
|
|
83
|
+
export declare const CommandSeparator: React.ForwardRefExoticComponent<Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
84
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
85
|
+
} & {
|
|
86
|
+
asChild?: boolean;
|
|
87
|
+
}, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
88
|
+
alwaysRender?: boolean;
|
|
89
|
+
} & React.RefAttributes<HTMLDivElement>, "ref"> & CommandSeparatorProp & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -50,11 +50,24 @@ const CommandGroup = React.forwardRef(({ className, ...props }, ref) => /* @__PU
|
|
|
50
50
|
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
|
51
51
|
const CommandItem = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(CommandPrimitive.Item, { ref, className: cn("ui-command-item", className), ...props }));
|
|
52
52
|
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
|
53
|
+
const CommandSeparator = React.forwardRef(({ className, alwaysRender = true, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
54
|
+
CommandPrimitive.Separator,
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
className: cn("ui-command-separator", className),
|
|
58
|
+
"data-slot": "command-separator",
|
|
59
|
+
"aria-hidden": "true",
|
|
60
|
+
alwaysRender,
|
|
61
|
+
...props
|
|
62
|
+
}
|
|
63
|
+
));
|
|
64
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
|
53
65
|
export {
|
|
54
66
|
Command,
|
|
55
67
|
CommandEmpty,
|
|
56
68
|
CommandGroup,
|
|
57
69
|
CommandInput,
|
|
58
70
|
CommandItem,
|
|
59
|
-
CommandList
|
|
71
|
+
CommandList,
|
|
72
|
+
CommandSeparator
|
|
60
73
|
};
|
|
@@ -5,7 +5,12 @@ import { CheckCircle, CircleAlert, LoaderCircle, TriangleAlert } from "lucide-re
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { Label } from "../data-entry/label.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
FieldErrorReportContext,
|
|
10
|
+
FieldIdentityContext,
|
|
11
|
+
FieldNameContext,
|
|
12
|
+
mergeAriaIds
|
|
13
|
+
} from "../../lib/field-a11y.js";
|
|
9
14
|
import { useOptionalAppContext } from "../../app/app-provider.js";
|
|
10
15
|
import { useFormLayout } from "./form.js";
|
|
11
16
|
import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
|
|
@@ -42,7 +47,8 @@ function FormField({
|
|
|
42
47
|
const collapseBelow = form?.collapseBelow ?? "md";
|
|
43
48
|
const errorsRegistry = useFormErrorsRegistry();
|
|
44
49
|
useClaimErrorKey(name);
|
|
45
|
-
const
|
|
50
|
+
const [reportedError, setReportedError] = React.useState(void 0);
|
|
51
|
+
const error = errorProp ?? (name && errorsRegistry ? firstBagMessage(errorsRegistry.errors[name]) : void 0) ?? (reportedError === null || reportedError === false || reportedError === "" ? void 0 : reportedError);
|
|
46
52
|
const autoId = React.useId();
|
|
47
53
|
const resolvedId = id ?? autoId;
|
|
48
54
|
const labelId = `${resolvedId}-label`;
|
|
@@ -165,7 +171,7 @@ function FormField({
|
|
|
165
171
|
] }) : null,
|
|
166
172
|
/* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
|
|
167
173
|
helperPlacement === "before" ? helperNode : null,
|
|
168
|
-
isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: /* @__PURE__ */ jsx(FieldIdentityContext.Provider, { value: fieldIdentityContext, children: /* @__PURE__ */ jsx(
|
|
174
|
+
isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: /* @__PURE__ */ jsx(FieldIdentityContext.Provider, { value: fieldIdentityContext, children: /* @__PURE__ */ jsx(FieldErrorReportContext.Provider, { value: setReportedError, children: /* @__PURE__ */ jsx(
|
|
169
175
|
"fieldset",
|
|
170
176
|
{
|
|
171
177
|
role: "presentation",
|
|
@@ -173,7 +179,7 @@ function FormField({
|
|
|
173
179
|
disabled: form?.disabled && childProps?.disabled !== false,
|
|
174
180
|
children: childWithA11y
|
|
175
181
|
}
|
|
176
|
-
) }) }),
|
|
182
|
+
) }) }) }),
|
|
177
183
|
hasFeedback && validationStatus ? /* @__PURE__ */ jsxs(
|
|
178
184
|
"span",
|
|
179
185
|
{
|
|
@@ -9,6 +9,8 @@ export { CheckboxGroup } from "./checkbox-group.js";
|
|
|
9
9
|
export { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
10
10
|
export { Textarea } from "./textarea.js";
|
|
11
11
|
export type { TextareaProps } from "./textarea.js";
|
|
12
|
+
export { TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
|
|
13
|
+
export type { TranslatableValue, TranslatableInputProp, TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProps, } from "./translatable-input.js";
|
|
12
14
|
export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
|
|
13
15
|
export type { FormProp, FormProps } from "./form.js";
|
|
14
16
|
export { FormField } from "./form-field.js";
|
|
@@ -47,8 +49,8 @@ export { TreeSelect, SHOW_CHILD, SHOW_PARENT, SHOW_ALL } from "./tree-select.js"
|
|
|
47
49
|
export type { TreeSelectProps } from "./tree-select.js";
|
|
48
50
|
export { Transfer } from "./transfer.js";
|
|
49
51
|
export type { TransferProps, TransferItemProp } from "./transfer.js";
|
|
50
|
-
export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "./command.js";
|
|
51
|
-
export type { CommandProps } from "./command.js";
|
|
52
|
+
export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "./command.js";
|
|
53
|
+
export type { CommandProps, CommandSeparatorProps } from "./command.js";
|
|
52
54
|
export { CommandPalette } from "./command-palette.js";
|
|
53
55
|
export type { CommandPaletteGroup, CommandPaletteItem, CommandPaletteLabels, CommandPaletteProps, CommandPaletteRecent, CommandPaletteSelectModifiers, } from "./command-palette.js";
|
|
54
56
|
export { PasswordInput } from "./password-input.js";
|
|
@@ -17,6 +17,7 @@ import { Checkbox } from "./checkbox.js";
|
|
|
17
17
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
18
18
|
import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
19
19
|
import { Textarea } from "./textarea.js";
|
|
20
|
+
import { TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
|
|
20
21
|
import { Form, useFormLayout } from "./form.js";
|
|
21
22
|
import { FormField } from "./form-field.js";
|
|
22
23
|
import { FormErrors, FormErrorsProvider } from "./form-errors.js";
|
|
@@ -48,7 +49,8 @@ import {
|
|
|
48
49
|
CommandGroup,
|
|
49
50
|
CommandInput,
|
|
50
51
|
CommandItem,
|
|
51
|
-
CommandList
|
|
52
|
+
CommandList,
|
|
53
|
+
CommandSeparator
|
|
52
54
|
} from "./command.js";
|
|
53
55
|
import { CommandPalette } from "./command-palette.js";
|
|
54
56
|
import { PasswordInput } from "./password-input.js";
|
|
@@ -77,6 +79,7 @@ export {
|
|
|
77
79
|
CommandItem,
|
|
78
80
|
CommandList,
|
|
79
81
|
CommandPalette,
|
|
82
|
+
CommandSeparator,
|
|
80
83
|
DatePicker,
|
|
81
84
|
DayButton,
|
|
82
85
|
Field,
|
|
@@ -123,6 +126,8 @@ export {
|
|
|
123
126
|
ToggleGroup,
|
|
124
127
|
ToggleGroupItem,
|
|
125
128
|
Transfer,
|
|
129
|
+
TranslatableInput,
|
|
130
|
+
TranslatableTextarea,
|
|
126
131
|
TreeSelect,
|
|
127
132
|
UPLOAD_LIST_IGNORE,
|
|
128
133
|
Upload,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TranslatableValue } from "../../props/components/data-entry.prop.js";
|
|
3
|
+
export type { TranslatableValue, TranslatableInputProp, TranslatableInputProp as TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProp as TranslatableTextareaProps, } from "../../props/components/data-entry.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* TranslatableInput — ONE text field that holds a value per locale (`{ ja, en, vi }`), switched by
|
|
6
|
+
* locale tabs above it (gh#1336). The locale list, the first tab and the fallback come from
|
|
7
|
+
* `AppProvider` and are overridable per field; an empty locale shows the fallback locale's text as
|
|
8
|
+
* its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to
|
|
9
|
+
* the single control, and `ref` is that control's `<input>`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TranslatableInput: React.ForwardRefExoticComponent<Omit<import("./input.js").InputProp, "defaultValue" | "lang" | "onValueChange" | "value"> & {
|
|
12
|
+
value?: import("../../props/index.js").ValueProp<TranslatableValue>;
|
|
13
|
+
defaultValue?: import("../../props/index.js").DefaultValueProp<TranslatableValue>;
|
|
14
|
+
onValueChange?: import("../../props/index.js").OnValueChangeProp<TranslatableValue>;
|
|
15
|
+
locales?: readonly string[];
|
|
16
|
+
defaultLocale?: string;
|
|
17
|
+
fallbackLocale?: string;
|
|
18
|
+
errors?: Partial<Record<string, import("../../props/index.js").ErrorProp>>;
|
|
19
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
20
|
+
/** TranslatableTextarea — {@link TranslatableInput} over a `Textarea`; `ref` is the `<textarea>`. */
|
|
21
|
+
export declare const TranslatableTextarea: React.ForwardRefExoticComponent<Omit<import("./textarea.js").TextareaProp, "defaultValue" | "lang" | "onValueChange" | "value"> & {
|
|
22
|
+
value?: import("../../props/index.js").ValueProp<TranslatableValue>;
|
|
23
|
+
defaultValue?: import("../../props/index.js").DefaultValueProp<TranslatableValue>;
|
|
24
|
+
onValueChange?: import("../../props/index.js").OnValueChangeProp<TranslatableValue>;
|
|
25
|
+
locales?: readonly string[];
|
|
26
|
+
defaultLocale?: string;
|
|
27
|
+
fallbackLocale?: string;
|
|
28
|
+
errors?: Partial<Record<string, import("../../props/index.js").ErrorProp>>;
|
|
29
|
+
} & React.RefAttributes<HTMLTextAreaElement>>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { CircleAlert } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { getRegisteredLocales } from "../../i18n/locale-tags.js";
|
|
7
|
+
import { mergeAriaIds, useReportFieldError } from "../../lib/field-a11y.js";
|
|
8
|
+
import { displayNames } from "../../lib/intl-cache.js";
|
|
9
|
+
import { cn } from "../../lib/utils.js";
|
|
10
|
+
import { Tabs, TabsList, TabsTrigger } from "../navigation/tabs.js";
|
|
11
|
+
import { Input } from "./input.js";
|
|
12
|
+
import { Textarea } from "./textarea.js";
|
|
13
|
+
const NO_VALUE = {};
|
|
14
|
+
function localeName(code, uiLocale) {
|
|
15
|
+
try {
|
|
16
|
+
return displayNames(uiLocale, { type: "language" }).of(code) ?? code;
|
|
17
|
+
} catch {
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const idPart = (code) => code.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
22
|
+
function TranslatableField({
|
|
23
|
+
kind,
|
|
24
|
+
value: valueProp,
|
|
25
|
+
defaultValue,
|
|
26
|
+
onValueChange,
|
|
27
|
+
locales: localesProp,
|
|
28
|
+
defaultLocale,
|
|
29
|
+
fallbackLocale: fallbackLocaleProp,
|
|
30
|
+
errors,
|
|
31
|
+
size,
|
|
32
|
+
placeholder,
|
|
33
|
+
className,
|
|
34
|
+
forwardedRef,
|
|
35
|
+
...rest
|
|
36
|
+
}) {
|
|
37
|
+
const { t, locale: uiLocale, fallbackLocale: appFallbackLocale } = useTranslation();
|
|
38
|
+
const locales = React.useMemo(() => {
|
|
39
|
+
const list = localesProp ?? getRegisteredLocales();
|
|
40
|
+
return list.length > 0 ? list : [uiLocale];
|
|
41
|
+
}, [localesProp, uiLocale]);
|
|
42
|
+
const pick = (candidates) => candidates.find((code) => code !== void 0 && locales.includes(code)) ?? locales[0];
|
|
43
|
+
const fallbackLocale = pick([fallbackLocaleProp, appFallbackLocale]);
|
|
44
|
+
const [activeState, setActiveState] = React.useState(
|
|
45
|
+
() => pick([defaultLocale, uiLocale, fallbackLocale])
|
|
46
|
+
);
|
|
47
|
+
const active = locales.includes(activeState) ? activeState : pick([defaultLocale, fallbackLocale]);
|
|
48
|
+
const [innerValue, setInnerValue] = React.useState(defaultValue ?? NO_VALUE);
|
|
49
|
+
const current = valueProp ?? innerValue;
|
|
50
|
+
const currentRef = React.useRef(current);
|
|
51
|
+
currentRef.current = current;
|
|
52
|
+
const handleText = (text) => {
|
|
53
|
+
const next = { ...currentRef.current, [active]: text };
|
|
54
|
+
currentRef.current = next;
|
|
55
|
+
if (valueProp === void 0) setInnerValue(next);
|
|
56
|
+
onValueChange?.(next);
|
|
57
|
+
};
|
|
58
|
+
const activeText = current[active] ?? "";
|
|
59
|
+
const fallbackText = active !== fallbackLocale && activeText === "" ? current[fallbackLocale] : void 0;
|
|
60
|
+
const activeError = errors?.[active];
|
|
61
|
+
const hasActiveError = activeError !== void 0 && activeError !== null && activeError !== false && activeError !== "";
|
|
62
|
+
const insideFormField = useReportFieldError(hasActiveError ? activeError : void 0);
|
|
63
|
+
const baseId = React.useId();
|
|
64
|
+
const tabId = (code) => `${baseId}-tab-${idPart(code)}`;
|
|
65
|
+
const panelId = `${baseId}-panel`;
|
|
66
|
+
const switcherNameId = `${baseId}-languages`;
|
|
67
|
+
const localeNameId = `${baseId}-locale`;
|
|
68
|
+
const ownErrorId = `${baseId}-error`;
|
|
69
|
+
const labelledBy = rest["aria-labelledby"];
|
|
70
|
+
const ariaLabel = rest["aria-label"];
|
|
71
|
+
const showOwnError = hasActiveError && !insideFormField;
|
|
72
|
+
const controlProps = {
|
|
73
|
+
...rest,
|
|
74
|
+
ref: forwardedRef,
|
|
75
|
+
lang: active,
|
|
76
|
+
value: activeText,
|
|
77
|
+
onValueChange: handleText,
|
|
78
|
+
placeholder: fallbackText ? fallbackText : placeholder,
|
|
79
|
+
// The control is named by the field AND the locale it holds: "商品名 English".
|
|
80
|
+
"aria-labelledby": labelledBy ? `${labelledBy} ${localeNameId}` : void 0,
|
|
81
|
+
"aria-invalid": hasActiveError ? true : rest["aria-invalid"],
|
|
82
|
+
"aria-errormessage": mergeAriaIds(
|
|
83
|
+
rest["aria-errormessage"],
|
|
84
|
+
showOwnError ? ownErrorId : void 0
|
|
85
|
+
),
|
|
86
|
+
status: hasActiveError ? "error" : rest.status
|
|
87
|
+
};
|
|
88
|
+
return /* @__PURE__ */ jsxs(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
"data-slot": "translatable-field",
|
|
92
|
+
"data-size": size,
|
|
93
|
+
className: cn("ui-translatable-field", className),
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
Tabs,
|
|
97
|
+
{
|
|
98
|
+
value: active,
|
|
99
|
+
onValueChange: setActiveState,
|
|
100
|
+
size: size === "lg" ? "md" : "sm",
|
|
101
|
+
controls: panelId,
|
|
102
|
+
className: "ui-translatable-field-tabs",
|
|
103
|
+
...labelledBy ? { "aria-labelledby": `${labelledBy} ${switcherNameId}` } : {
|
|
104
|
+
"aria-label": [ariaLabel, t("dataEntry.translatable.languages")].filter(Boolean).join(" ")
|
|
105
|
+
},
|
|
106
|
+
children: /* @__PURE__ */ jsx(TabsList, { children: locales.map((code) => {
|
|
107
|
+
const error = errors?.[code];
|
|
108
|
+
const invalid = error !== void 0 && error !== null && error !== false && error !== "";
|
|
109
|
+
return /* @__PURE__ */ jsxs(
|
|
110
|
+
TabsTrigger,
|
|
111
|
+
{
|
|
112
|
+
id: tabId(code),
|
|
113
|
+
value: code,
|
|
114
|
+
"data-locale": code,
|
|
115
|
+
"data-invalid": invalid ? "true" : void 0,
|
|
116
|
+
"data-filled": current[code] ? "true" : void 0,
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ jsx("span", { children: localeName(code, uiLocale) }),
|
|
119
|
+
invalid ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
120
|
+
/* @__PURE__ */ jsx(CircleAlert, { className: "ui-translatable-field-error-mark", "aria-hidden": "true" }),
|
|
121
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${t("dataEntry.translatable.hasError")}` })
|
|
122
|
+
] }) : null
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
code
|
|
126
|
+
);
|
|
127
|
+
}) })
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
/* @__PURE__ */ jsx("span", { id: switcherNameId, hidden: true, children: t("dataEntry.translatable.languages") }),
|
|
131
|
+
/* @__PURE__ */ jsx("span", { id: localeNameId, hidden: true, children: localeName(active, uiLocale) }),
|
|
132
|
+
/* @__PURE__ */ jsx(
|
|
133
|
+
"div",
|
|
134
|
+
{
|
|
135
|
+
role: "tabpanel",
|
|
136
|
+
id: panelId,
|
|
137
|
+
"aria-labelledby": tabId(active),
|
|
138
|
+
className: "ui-translatable-field-panel",
|
|
139
|
+
children: kind === "input" ? /* @__PURE__ */ jsx(
|
|
140
|
+
Input,
|
|
141
|
+
{
|
|
142
|
+
...controlProps,
|
|
143
|
+
size
|
|
144
|
+
}
|
|
145
|
+
) : /* @__PURE__ */ jsx(
|
|
146
|
+
Textarea,
|
|
147
|
+
{
|
|
148
|
+
...controlProps,
|
|
149
|
+
size
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
),
|
|
154
|
+
showOwnError ? /* @__PURE__ */ jsx("p", { id: ownErrorId, role: "alert", className: "ui-form-field-note text-error-strong text-xs", children: activeError }) : null
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const TranslatableInput = React.forwardRef(
|
|
160
|
+
(props, ref) => /* @__PURE__ */ jsx(
|
|
161
|
+
TranslatableField,
|
|
162
|
+
{
|
|
163
|
+
...props,
|
|
164
|
+
kind: "input",
|
|
165
|
+
forwardedRef: ref
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
TranslatableInput.displayName = "TranslatableInput";
|
|
170
|
+
const TranslatableTextarea = React.forwardRef(
|
|
171
|
+
(props, ref) => /* @__PURE__ */ jsx(
|
|
172
|
+
TranslatableField,
|
|
173
|
+
{
|
|
174
|
+
...props,
|
|
175
|
+
kind: "textarea",
|
|
176
|
+
forwardedRef: ref
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
TranslatableTextarea.displayName = "TranslatableTextarea";
|
|
181
|
+
export {
|
|
182
|
+
TranslatableInput,
|
|
183
|
+
TranslatableTextarea
|
|
184
|
+
};
|
|
@@ -3,6 +3,8 @@ export { Pagination, PaginationContent, PaginationItem, PaginationLink, Paginati
|
|
|
3
3
|
export { Steps } from "./steps.js";
|
|
4
4
|
export { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./toolbar.js";
|
|
5
5
|
export type { FilterBarChipProp, FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarProp, FilterBarProps, FilterBarSearchProp, FilterBarSearchProps, ToolbarGroupProps, ToolbarProps, } from "./toolbar.js";
|
|
6
|
+
export { SearchForm } from "./search-form.js";
|
|
7
|
+
export type { SearchFormProp, SearchFormProps } from "./search-form.js";
|
|
6
8
|
export { AppSettingPicker } from "./app-setting-picker.js";
|
|
7
9
|
export type { AppSettingPickerProp, AppSettingPickerProps, AppSettingPickerMenuProp, AppSettingPickerCycleProp, AppSettingKind, AppSettingCycleKind, } from "./app-setting-picker.js";
|
|
8
10
|
export type { BreadcrumbItemProp as BreadcrumbItem } from "../../props/vocabulary/navigation.prop.js";
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "./pagination.js";
|
|
11
11
|
import { Steps } from "./steps.js";
|
|
12
12
|
import { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./toolbar.js";
|
|
13
|
+
import { SearchForm } from "./search-form.js";
|
|
13
14
|
import { AppSettingPicker } from "./app-setting-picker.js";
|
|
14
15
|
import {
|
|
15
16
|
DropdownMenu,
|
|
@@ -54,6 +55,7 @@ export {
|
|
|
54
55
|
PaginationLink,
|
|
55
56
|
PaginationNext,
|
|
56
57
|
PaginationPrevious,
|
|
58
|
+
SearchForm,
|
|
57
59
|
Steps,
|
|
58
60
|
Tabs,
|
|
59
61
|
TabsContent,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SearchFormProp } from "../../props/components/navigation.prop.js";
|
|
3
|
+
export type { SearchFormProp, SearchFormProp as SearchFormProps };
|
|
4
|
+
/**
|
|
5
|
+
* SearchForm — the submit-driven 検索条件フォーム (Ant Design ProComponents `QueryFilter`).
|
|
6
|
+
*
|
|
7
|
+
* A `search` landmark (`<form role="search">`) holding a grid of labelled conditions, aligned in
|
|
8
|
+
* columns. The conditions past the first row sit behind one 詳細条件を開く / 閉じる toggle that
|
|
9
|
+
* names how many are hidden; Search (検索) submits, Clear (クリア) resets. After a search the
|
|
10
|
+
* conditions the results were searched with, and the result count, are announced in one
|
|
11
|
+
* `role="status"` line. Every label is localized through the library catalogue.
|
|
12
|
+
*
|
|
13
|
+
* Choose it over a live FilterBar when the conditions are many, the query is heavy, or the users
|
|
14
|
+
* expect an explicit 検索 button (Japanese business systems). Hidden conditions stay MOUNTED, so a
|
|
15
|
+
* value typed into one survives collapsing the section.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SearchForm: React.ForwardRefExoticComponent<Omit<import("../data-entry/index.js").FormProp, "aria-label" | "asChild" | "children" | "columns" | "onReset" | "role"> & {
|
|
18
|
+
children: import("../../props/index.js").ChildrenProp;
|
|
19
|
+
columns?: import("../../props/components/layout.prop.js").ResponsiveGridColumnsProp;
|
|
20
|
+
visibleFields?: number;
|
|
21
|
+
open?: import("../../props/index.js").OpenProp;
|
|
22
|
+
defaultOpen?: import("../../props/index.js").DefaultOpenProp;
|
|
23
|
+
onOpenChange?: import("../../props/index.js").OnOpenChangeProp;
|
|
24
|
+
onReset?: () => void;
|
|
25
|
+
searchLabel?: import("../../props/index.js").LabelProp;
|
|
26
|
+
resetLabel?: import("../../props/index.js").LabelProp;
|
|
27
|
+
summary?: React.ReactNode;
|
|
28
|
+
resultCount?: number;
|
|
29
|
+
loading?: import("../../props/index.js").PendingProp;
|
|
30
|
+
label?: string;
|
|
31
|
+
} & React.RefAttributes<HTMLFormElement>>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { Form } from "../data-entry/form.js";
|
|
8
|
+
import { Button } from "../general/button.js";
|
|
9
|
+
import { ResponsiveGrid } from "../layout/responsive-grid.js";
|
|
10
|
+
const DEFAULT_COLUMNS = { base: 1, md: 2, lg: 3 };
|
|
11
|
+
function widestStep(columns) {
|
|
12
|
+
if (typeof columns === "number") return Math.max(1, columns);
|
|
13
|
+
const steps = Object.values(columns ?? DEFAULT_COLUMNS).filter(
|
|
14
|
+
(n) => typeof n === "number"
|
|
15
|
+
);
|
|
16
|
+
return Math.max(1, ...steps);
|
|
17
|
+
}
|
|
18
|
+
const SearchForm = React.forwardRef(function SearchForm2({
|
|
19
|
+
children,
|
|
20
|
+
columns = DEFAULT_COLUMNS,
|
|
21
|
+
visibleFields,
|
|
22
|
+
open,
|
|
23
|
+
defaultOpen = false,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
onReset,
|
|
26
|
+
searchLabel,
|
|
27
|
+
resetLabel,
|
|
28
|
+
summary,
|
|
29
|
+
resultCount,
|
|
30
|
+
loading,
|
|
31
|
+
label,
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}, ref) {
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
const baseId = React.useId();
|
|
37
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
38
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
39
|
+
const fields = React.Children.toArray(children).filter(React.isValidElement);
|
|
40
|
+
const limit = Math.max(1, visibleFields ?? widestStep(columns));
|
|
41
|
+
const hiddenCount = Math.max(0, fields.length - limit);
|
|
42
|
+
const hiddenIds = Array.from({ length: hiddenCount }, (_, i) => `${baseId}-more-${i}`);
|
|
43
|
+
const toggle = () => {
|
|
44
|
+
const next = !isOpen;
|
|
45
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
46
|
+
onOpenChange?.(next);
|
|
47
|
+
};
|
|
48
|
+
const showMeta = summary != null || resultCount !== void 0;
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
Form,
|
|
51
|
+
{
|
|
52
|
+
ref,
|
|
53
|
+
role: "search",
|
|
54
|
+
"aria-label": label ?? t("navigation.searchForm.label"),
|
|
55
|
+
className: cn("ui-search-form", className),
|
|
56
|
+
...props,
|
|
57
|
+
children: /* @__PURE__ */ jsxs("div", { className: "ui-search-form-body", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(ResponsiveGrid, { columns, children: fields.map(
|
|
59
|
+
(field, index) => index < limit ? field : (
|
|
60
|
+
/* Past the first row: kept mounted and hidden, so a typed value survives a collapse. */
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
id: hiddenIds[index - limit],
|
|
65
|
+
hidden: !isOpen,
|
|
66
|
+
className: "ui-search-form-more",
|
|
67
|
+
children: field
|
|
68
|
+
},
|
|
69
|
+
field.key ?? index
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
) }),
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-search-form-actions", children: [
|
|
74
|
+
hiddenCount > 0 && /* @__PURE__ */ jsxs(
|
|
75
|
+
Button,
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
variant: "ghost",
|
|
79
|
+
size: "sm",
|
|
80
|
+
className: "ui-search-form-toggle",
|
|
81
|
+
"aria-expanded": isOpen,
|
|
82
|
+
"aria-controls": hiddenIds.join(" "),
|
|
83
|
+
onClick: toggle,
|
|
84
|
+
count: isOpen ? void 0 : hiddenCount,
|
|
85
|
+
showZero: false,
|
|
86
|
+
countLabel: t("navigation.searchForm.hiddenCount"),
|
|
87
|
+
children: [
|
|
88
|
+
isOpen ? t("navigation.searchForm.collapse") : t("navigation.searchForm.expand"),
|
|
89
|
+
isOpen ? /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" })
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-search-form-submit", children: [
|
|
94
|
+
onReset && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onReset, children: resetLabel ?? t("navigation.searchForm.reset") }),
|
|
95
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: searchLabel ?? t("navigation.searchForm.search") })
|
|
96
|
+
] })
|
|
97
|
+
] }),
|
|
98
|
+
showMeta && /* @__PURE__ */ jsxs("p", { role: "status", className: "ui-search-form-meta", children: [
|
|
99
|
+
summary != null && /* @__PURE__ */ jsxs("span", { className: "ui-search-form-summary", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: "ui-search-form-summary-label", children: t("navigation.searchForm.summary") }),
|
|
101
|
+
" ",
|
|
102
|
+
summary
|
|
103
|
+
] }),
|
|
104
|
+
resultCount !== void 0 && /* @__PURE__ */ jsx("span", { className: "ui-search-form-count", children: t("navigation.filterBar.resultCount", { count: resultCount }) })
|
|
105
|
+
] })
|
|
106
|
+
] })
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
export {
|
|
111
|
+
SearchForm
|
|
112
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "33.
|
|
3
|
+
"version": "33.3.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
|
@@ -278,6 +278,10 @@
|
|
|
278
278
|
"number": "Contains number",
|
|
279
279
|
"symbol": "Contains symbol"
|
|
280
280
|
}
|
|
281
|
+
},
|
|
282
|
+
"translatable": {
|
|
283
|
+
"languages": "Language",
|
|
284
|
+
"hasError": "has an error"
|
|
281
285
|
}
|
|
282
286
|
},
|
|
283
287
|
"feedback": {
|
|
@@ -584,6 +588,15 @@
|
|
|
584
588
|
"other": "{count} results"
|
|
585
589
|
}
|
|
586
590
|
},
|
|
591
|
+
"searchForm": {
|
|
592
|
+
"label": "Search conditions",
|
|
593
|
+
"search": "Search",
|
|
594
|
+
"reset": "Clear",
|
|
595
|
+
"expand": "More conditions",
|
|
596
|
+
"collapse": "Fewer conditions",
|
|
597
|
+
"hiddenCount": "hidden conditions",
|
|
598
|
+
"summary": "Searched with:"
|
|
599
|
+
},
|
|
587
600
|
"localePicker": {
|
|
588
601
|
"ariaLabel": "Language"
|
|
589
602
|
},
|
|
@@ -273,6 +273,10 @@
|
|
|
273
273
|
"number": "数字を含む",
|
|
274
274
|
"symbol": "記号を含む"
|
|
275
275
|
}
|
|
276
|
+
},
|
|
277
|
+
"translatable": {
|
|
278
|
+
"languages": "言語",
|
|
279
|
+
"hasError": "エラーあり"
|
|
276
280
|
}
|
|
277
281
|
},
|
|
278
282
|
"feedback": {
|
|
@@ -570,6 +574,15 @@
|
|
|
570
574
|
"removeFilter": "フィルターを解除: {label}",
|
|
571
575
|
"resultCount": "{count} 件の結果"
|
|
572
576
|
},
|
|
577
|
+
"searchForm": {
|
|
578
|
+
"label": "検索条件",
|
|
579
|
+
"search": "検索",
|
|
580
|
+
"reset": "クリア",
|
|
581
|
+
"expand": "詳細条件を開く",
|
|
582
|
+
"collapse": "詳細条件を閉じる",
|
|
583
|
+
"hiddenCount": "件の条件が非表示",
|
|
584
|
+
"summary": "検索条件:"
|
|
585
|
+
},
|
|
573
586
|
"localePicker": {
|
|
574
587
|
"ariaLabel": "言語"
|
|
575
588
|
},
|
|
@@ -274,6 +274,10 @@
|
|
|
274
274
|
"number": "Có chữ số",
|
|
275
275
|
"symbol": "Có ký hiệu"
|
|
276
276
|
}
|
|
277
|
+
},
|
|
278
|
+
"translatable": {
|
|
279
|
+
"languages": "Ngôn ngữ",
|
|
280
|
+
"hasError": "có lỗi"
|
|
277
281
|
}
|
|
278
282
|
},
|
|
279
283
|
"feedback": {
|
|
@@ -571,6 +575,15 @@
|
|
|
571
575
|
"removeFilter": "Bỏ bộ lọc: {label}",
|
|
572
576
|
"resultCount": "{count} kết quả"
|
|
573
577
|
},
|
|
578
|
+
"searchForm": {
|
|
579
|
+
"label": "Điều kiện tìm kiếm",
|
|
580
|
+
"search": "Tìm kiếm",
|
|
581
|
+
"reset": "Xoá điều kiện",
|
|
582
|
+
"expand": "Mở thêm điều kiện",
|
|
583
|
+
"collapse": "Thu gọn điều kiện",
|
|
584
|
+
"hiddenCount": "điều kiện đang ẩn",
|
|
585
|
+
"summary": "Điều kiện tìm kiếm:"
|
|
586
|
+
},
|
|
574
587
|
"localePicker": {
|
|
575
588
|
"ariaLabel": "Ngôn ngữ"
|
|
576
589
|
},
|
package/dist/lib/field-a11y.d.ts
CHANGED
|
@@ -106,3 +106,18 @@ export declare function pickGroupFieldA11y(props: FieldA11yProps): {
|
|
|
106
106
|
"aria-describedby"?: string;
|
|
107
107
|
"data-field"?: string;
|
|
108
108
|
};
|
|
109
|
+
/**
|
|
110
|
+
* A control that owns SEVERAL values reports which of its messages is current (gh#1336). A
|
|
111
|
+
* `TranslatableInput` holds one value per locale and gets one message per locale; only the active
|
|
112
|
+
* locale's message describes the text the user is looking at, and only the control knows which
|
|
113
|
+
* locale that is. FormField reads the report as the lowest-precedence source of its `error` — its
|
|
114
|
+
* own `error` prop and the form's error bag both win — so the message renders in FormField's own
|
|
115
|
+
* error line and reaches the control through the same `aria-invalid` / `aria-errormessage` wiring
|
|
116
|
+
* every other field uses. `null` outside a FormField.
|
|
117
|
+
*/
|
|
118
|
+
export declare const FieldErrorReportContext: React.Context<((message: React.ReactNode) => void) | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Report `message` (or its absence) to the enclosing FormField. Returns `true` when a FormField is
|
|
121
|
+
* listening — a caller with no FormField above it has to render its own message.
|
|
122
|
+
*/
|
|
123
|
+
export declare function useReportFieldError(message: React.ReactNode): boolean;
|