@godxjp/ui 23.0.0 → 23.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/date-format-labels.d.ts +14 -2
- package/dist/app/date-format-labels.js +1 -1
- package/dist/app/date-formats.d.ts +14 -2
- package/dist/app/date-formats.js +9 -2
- package/dist/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/form-field.js +2 -2
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/number-input.js +27 -6
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/switch.js +1 -1
- package/dist/components/feedback/dialog.js +2 -2
- package/dist/components/feedback/sheet.js +2 -22
- package/dist/components/navigation/filter-bar.js +2 -1
- package/dist/components/navigation/tabs.js +16 -4
- package/dist/i18n/messages/en.json +5 -1
- package/dist/i18n/messages/ja.json +5 -1
- package/dist/i18n/messages/vi.json +5 -1
- package/dist/lib/breakpoint-token.d.ts +8 -0
- package/dist/lib/breakpoint-token.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +8 -1
- package/dist/props/components/navigation.prop.d.ts +8 -1
- package/dist/props/registry.d.ts +6 -1
- package/dist/props/registry.js +6 -0
- package/dist/props/vocabulary/data.prop.d.ts +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +41 -5
- package/dist/styles/focus-ring.css +28 -0
- package/dist/styles/form-layout.css +4 -0
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +13 -2
- package/dist/styles/shell-layout.css +4 -0
- package/dist/styles/table-layout.css +5 -2
- package/dist/tokens/components/control.css +8 -3
- package/dist/tokens/components/navigation.css +2 -0
- package/docs/CONSUMER-RULES.md +7 -2
- package/docs/CUSTOMER-THEMING.md +18 -8
- package/docs/DATETIME.md +11 -2
- package/docs/SPACING.md +35 -5
- package/docs/TOKENS.md +1 -1
- package/docs/data-display/data-table/index.tsx +17 -2
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
- package/docs/navigation/dropdown-menu.tsx +4 -4
- package/docs/navigation/tabs.tsx +72 -0
- package/docs/roadmap/ai-chat-components.md +1 -2
- package/docs/roadmap/tree-components.md +0 -1
- package/package.json +11 -17
- package/scripts/_agent-setup.mjs +18 -0
- package/scripts/guinea-pig-skill.md +12 -6
- package/scripts/ui-audit.mjs +184 -6
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import type { AppLocale } from "./types.js";
|
|
2
2
|
import type { AppDateFormat } from "./date-formats.js";
|
|
3
3
|
export declare const APP_DATE_FORMAT_OPTIONS: {
|
|
4
|
-
value: "dmy" | "iso" | "mdy";
|
|
4
|
+
value: "dmy" | "iso" | "mdy" | "ymd";
|
|
5
5
|
}[];
|
|
6
6
|
export declare function getDateFormatLabel(dateFormat: AppDateFormat, locale: AppLocale, fallbackLocale?: AppLocale): string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Suggested default per locale — vi → dmy, ja → ymd, en → mdy.
|
|
9
|
+
*
|
|
10
|
+
* `ja` used to be `iso` (`yyyy-MM-dd`). Nothing recorded a reason for it, and it is not the form a
|
|
11
|
+
* Japanese business document is written in: those use `YYYY/MM/DD` (or `YYYY年MM月DD日`). The cost
|
|
12
|
+
* of the old default was measured in a consumer — gino-cloud shipped its own date formatter as an
|
|
13
|
+
* explicit stopgap purely to get the slashes, which is the package's own rule 1 broken by the
|
|
14
|
+
* package's own default.
|
|
15
|
+
*
|
|
16
|
+
* ONLY THE DEFAULT MOVES. A stored preference still wins (see `resolveDateFormat` in
|
|
17
|
+
* app-provider.tsx), a service can pin any value through `defaultDateFormat`, and `iso` is still
|
|
18
|
+
* one keystroke away in `DateFormatPicker`.
|
|
19
|
+
*/
|
|
8
20
|
export declare function resolveDefaultDateFormat(locale: AppLocale): AppDateFormat;
|
|
@@ -6,7 +6,7 @@ function getDateFormatLabel(dateFormat, locale, fallbackLocale = "en") {
|
|
|
6
6
|
}
|
|
7
7
|
function resolveDefaultDateFormat(locale) {
|
|
8
8
|
if (locale === "en") return "mdy";
|
|
9
|
-
if (locale === "ja") return "
|
|
9
|
+
if (locale === "ja") return "ymd";
|
|
10
10
|
return "dmy";
|
|
11
11
|
}
|
|
12
12
|
export {
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
/** Date-only display preset — sent as `x-date-format` to backend. */
|
|
2
2
|
import { type AppTimeFormat } from "./time-formats.js";
|
|
3
|
-
export type AppDateFormat = "iso" | "dmy" | "mdy";
|
|
4
|
-
|
|
3
|
+
export type AppDateFormat = "iso" | "ymd" | "dmy" | "mdy";
|
|
4
|
+
/**
|
|
5
|
+
* `ymd` (`yyyy/MM/dd`) IS THE JAPANESE BUSINESS FORM, AND ITS ABSENCE WAS A HOLE WITH NO WAY OUT.
|
|
6
|
+
*
|
|
7
|
+
* The axis shipped three values and none of them said `2026/05/01`: `iso` writes hyphens, and the
|
|
8
|
+
* two slash forms put the day or the month first. A Japanese-first consumer (gino-cloud) therefore
|
|
9
|
+
* wrote its own formatter and marked it an explicit stopgap — the tell that a named axis was
|
|
10
|
+
* missing a value rather than the consumer wanting something exotic, since the only alternative
|
|
11
|
+
* the package left was the thing docs/DATETIME.md rule 1 forbids ("never use date-fns/format,
|
|
12
|
+
* toLocaleString, or raw ISO slices for UI").
|
|
13
|
+
*
|
|
14
|
+
* It sits BEFORE the day/month-first pair because it is the other year-first form, next to `iso`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const APP_DATE_FORMATS: readonly ["iso", "ymd", "dmy", "mdy"];
|
|
5
17
|
export declare const APP_REQUEST_HEADER_DATE_FORMAT: "x-date-format";
|
|
6
18
|
/** date-fns pattern for date-only display. */
|
|
7
19
|
export declare function getDatePattern(dateFormat: AppDateFormat): string;
|
package/dist/app/date-formats.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { getTimePattern } from "./time-formats.js";
|
|
2
|
-
const APP_DATE_FORMATS = [
|
|
2
|
+
const APP_DATE_FORMATS = [
|
|
3
|
+
"iso",
|
|
4
|
+
"ymd",
|
|
5
|
+
"dmy",
|
|
6
|
+
"mdy"
|
|
7
|
+
];
|
|
3
8
|
const APP_REQUEST_HEADER_DATE_FORMAT = "x-date-format";
|
|
4
9
|
function getDatePattern(dateFormat) {
|
|
5
10
|
switch (dateFormat) {
|
|
11
|
+
case "ymd":
|
|
12
|
+
return "yyyy/MM/dd";
|
|
6
13
|
case "dmy":
|
|
7
14
|
return "dd/MM/yyyy";
|
|
8
15
|
case "mdy":
|
|
@@ -16,7 +23,7 @@ function getDateTimePattern(timeFormat, dateFormat) {
|
|
|
16
23
|
return `${getDatePattern(dateFormat)} ${getTimePattern(timeFormat)}`;
|
|
17
24
|
}
|
|
18
25
|
function isAppDateFormat(value) {
|
|
19
|
-
return value
|
|
26
|
+
return APP_DATE_FORMATS.includes(value);
|
|
20
27
|
}
|
|
21
28
|
export {
|
|
22
29
|
APP_DATE_FORMATS,
|
|
@@ -13,6 +13,13 @@ interface DataTableProps<T> {
|
|
|
13
13
|
columns: ColumnDef<T>[];
|
|
14
14
|
/** Required when `selectable` is true. Default: assume row.id (typed as any). */
|
|
15
15
|
getRowId?: (row: T) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Human name of a row, announced by its selection checkbox or radio as "Select row {label}".
|
|
18
|
+
* Default: the text of the `priority: "primary"` column, else of the first column, when that
|
|
19
|
+
* value is a string or number; the row id only as a last resort — an id is a KEY, and announced
|
|
20
|
+
* it reads a UUID aloud. `rowSelection.getCheckboxProps` `aria-label` still overrides a row.
|
|
21
|
+
*/
|
|
22
|
+
getRowLabel?: (row: T) => string;
|
|
16
23
|
selectable?: boolean;
|
|
17
24
|
selected?: Set<string>;
|
|
18
25
|
onSelectChange?: (next: Set<string>) => void;
|
|
@@ -130,7 +137,7 @@ interface DataTableProps<T> {
|
|
|
130
137
|
className?: string;
|
|
131
138
|
children?: React.ReactNode;
|
|
132
139
|
}
|
|
133
|
-
export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
140
|
+
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
|
|
134
141
|
export declare namespace DataTable {
|
|
135
142
|
var Toolbar: ({ children, className, }: {
|
|
136
143
|
children?: React.ReactNode;
|
|
@@ -190,6 +190,7 @@ function DataTable({
|
|
|
190
190
|
data,
|
|
191
191
|
columns,
|
|
192
192
|
getRowId = noopGetRowId,
|
|
193
|
+
getRowLabel,
|
|
193
194
|
selectable = false,
|
|
194
195
|
selected: controlledSelected,
|
|
195
196
|
onSelectChange,
|
|
@@ -443,6 +444,7 @@ function DataTable({
|
|
|
443
444
|
toggleExpanded,
|
|
444
445
|
rowSelection,
|
|
445
446
|
getRowId,
|
|
447
|
+
getRowLabel,
|
|
446
448
|
showSorterTooltip,
|
|
447
449
|
sortDirections,
|
|
448
450
|
sortPriorities,
|
|
@@ -764,6 +766,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
764
766
|
toggleExpanded,
|
|
765
767
|
rowSelection,
|
|
766
768
|
getRowId,
|
|
769
|
+
getRowLabel,
|
|
767
770
|
showSorterTooltip,
|
|
768
771
|
sortDirections,
|
|
769
772
|
sortPriorities
|
|
@@ -791,6 +794,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
791
794
|
);
|
|
792
795
|
}
|
|
793
796
|
}, [missingHeaderNames]);
|
|
797
|
+
const selectLabelColumn = visibleColumns.find((col) => col.priority === "primary") ?? visibleColumns[0];
|
|
794
798
|
const scrollRef = React.useRef(null);
|
|
795
799
|
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
796
800
|
React.useEffect(() => {
|
|
@@ -1096,6 +1100,9 @@ DataTable.Content = function DataTableContent() {
|
|
|
1096
1100
|
const rowKey = getRowId(original);
|
|
1097
1101
|
const isSelected = row.getIsSelected();
|
|
1098
1102
|
const checkboxProps = rowSelection?.getCheckboxProps?.(original) ?? {};
|
|
1103
|
+
const labelValue = selectLabelColumn ? original[selectLabelColumn.key] : void 0;
|
|
1104
|
+
const rowLabel = getRowLabel?.(original) ?? (typeof labelValue === "string" && labelValue.trim() !== "" || typeof labelValue === "number" ? String(labelValue) : row.id);
|
|
1105
|
+
const selectRowLabel = checkboxProps["aria-label"] ?? t("dataTable.selectRow", { id: rowLabel });
|
|
1099
1106
|
const canExpand = expandColumnShown && (expandable?.rowExpandable?.(original) ?? true);
|
|
1100
1107
|
const isExpanded = canExpand && expandedKeys.includes(rowKey);
|
|
1101
1108
|
const rowProps = onRow?.(original, rowIndex) ?? {};
|
|
@@ -1206,7 +1213,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1206
1213
|
RadioItem,
|
|
1207
1214
|
{
|
|
1208
1215
|
value: rowKey,
|
|
1209
|
-
"aria-label":
|
|
1216
|
+
"aria-label": selectRowLabel,
|
|
1210
1217
|
onClick: (e) => {
|
|
1211
1218
|
e.stopPropagation();
|
|
1212
1219
|
}
|
|
@@ -1222,7 +1229,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1222
1229
|
onCheckedChange: (v) => {
|
|
1223
1230
|
row.toggleSelected(!!v);
|
|
1224
1231
|
},
|
|
1225
|
-
"aria-label":
|
|
1232
|
+
"aria-label": selectRowLabel,
|
|
1226
1233
|
onClick: (e) => {
|
|
1227
1234
|
e.stopPropagation();
|
|
1228
1235
|
}
|
|
@@ -94,7 +94,7 @@ const CheckboxRoot = React.forwardRef((props, ref) => {
|
|
|
94
94
|
// itself). react-aria's root is a `<label>`, which is `display:inline` — without this the
|
|
95
95
|
// 16px box collapses to nothing. CheckboxVisual already carries the same three for the
|
|
96
96
|
// same reason.
|
|
97
|
-
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow
|
|
97
|
+
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow",
|
|
98
98
|
className
|
|
99
99
|
),
|
|
100
100
|
render: (domProps) => /* @__PURE__ */ jsx("label", { ...domProps, children: withOwnHitTarget(domProps.children) }),
|
|
@@ -47,7 +47,7 @@ function FormField({
|
|
|
47
47
|
const resolvedId = id ?? autoId;
|
|
48
48
|
const labelId = `${resolvedId}-label`;
|
|
49
49
|
const helperId = helper ? `${resolvedId}-helper` : void 0;
|
|
50
|
-
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null;
|
|
50
|
+
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "ui-form-field-note text-muted-foreground text-xs", children: helper }) : null;
|
|
51
51
|
const validationStatus = error ? "error" : validateStatus;
|
|
52
52
|
const feedbackId = hasFeedback && validationStatus ? `${resolvedId}-feedback` : void 0;
|
|
53
53
|
const FeedbackIcon = validationStatus === "validating" ? LoaderCircle : validationStatus === "error" ? CircleAlert : validationStatus === "warning" ? TriangleAlert : CheckCircle;
|
|
@@ -175,7 +175,7 @@ function FormField({
|
|
|
175
175
|
t(`dataEntry.form.${validationStatus}`)
|
|
176
176
|
] }) : null,
|
|
177
177
|
helperPlacement === "after" ? helperNode : null,
|
|
178
|
-
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
|
|
178
|
+
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-destructive text-xs", children: error }) : null
|
|
179
179
|
] })
|
|
180
180
|
]
|
|
181
181
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./control-appearance.js";
|
|
14
14
|
import { resolveAllowClear } from "./control-surface.js";
|
|
15
15
|
const inputBaseClass = [
|
|
16
|
-
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]
|
|
16
|
+
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]",
|
|
17
17
|
"selection:bg-primary selection:text-primary-foreground",
|
|
18
18
|
"placeholder:text-muted-foreground",
|
|
19
19
|
"aria-invalid:border-destructive",
|
|
@@ -57,6 +57,21 @@ const NumberInput = React.forwardRef(
|
|
|
57
57
|
}, ref) => {
|
|
58
58
|
const { t, locale } = useTranslation();
|
|
59
59
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
60
|
+
const stepperVerbId = React.useId();
|
|
61
|
+
const fieldLabel = typeof fieldA11y["aria-label"] === "string" ? fieldA11y["aria-label"] : void 0;
|
|
62
|
+
const fieldLabelledBy = typeof fieldA11y["aria-labelledby"] === "string" ? fieldA11y["aria-labelledby"] : void 0;
|
|
63
|
+
const stepperName = (direction) => {
|
|
64
|
+
const verb = t(`ui.numberInput.${direction}`);
|
|
65
|
+
if (fieldLabelledBy) {
|
|
66
|
+
return { "aria-labelledby": `${fieldLabelledBy} ${stepperVerbId}-${direction}` };
|
|
67
|
+
}
|
|
68
|
+
if (fieldLabel) {
|
|
69
|
+
return {
|
|
70
|
+
"aria-label": t(`ui.numberInput.${direction}Field`, { label: fieldLabel }) || verb
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { "aria-label": verb };
|
|
74
|
+
};
|
|
60
75
|
const isControlled = controlledValue !== void 0;
|
|
61
76
|
const [internal, setInternal] = React.useState(defaultValue);
|
|
62
77
|
const numericValue = isControlled ? controlledValue ?? null : internal;
|
|
@@ -228,7 +243,7 @@ const NumberInput = React.forwardRef(
|
|
|
228
243
|
}
|
|
229
244
|
) : null,
|
|
230
245
|
controls ? /* @__PURE__ */ jsxs("span", { "data-slot": "number-input-steppers", className: "ui-number-input-steppers", children: [
|
|
231
|
-
/* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ jsxs(
|
|
232
247
|
Button,
|
|
233
248
|
{
|
|
234
249
|
type: "button",
|
|
@@ -237,12 +252,15 @@ const NumberInput = React.forwardRef(
|
|
|
237
252
|
className: "ui-number-input-step ui-number-input-step-up",
|
|
238
253
|
tabIndex: -1,
|
|
239
254
|
disabled: !interactive || atMax,
|
|
240
|
-
|
|
255
|
+
...stepperName("increment"),
|
|
241
256
|
onClick: () => stepBy(1),
|
|
242
|
-
children:
|
|
257
|
+
children: [
|
|
258
|
+
/* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }),
|
|
259
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-increment`, className: "sr-only", children: t("ui.numberInput.increment") })
|
|
260
|
+
]
|
|
243
261
|
}
|
|
244
262
|
),
|
|
245
|
-
/* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ jsxs(
|
|
246
264
|
Button,
|
|
247
265
|
{
|
|
248
266
|
type: "button",
|
|
@@ -251,9 +269,12 @@ const NumberInput = React.forwardRef(
|
|
|
251
269
|
className: "ui-number-input-step ui-number-input-step-down",
|
|
252
270
|
tabIndex: -1,
|
|
253
271
|
disabled: !interactive || atMin,
|
|
254
|
-
|
|
272
|
+
...stepperName("decrement"),
|
|
255
273
|
onClick: () => stepBy(-1),
|
|
256
|
-
children:
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }),
|
|
276
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-decrement`, className: "sr-only", children: t("ui.numberInput.decrement") })
|
|
277
|
+
]
|
|
257
278
|
}
|
|
258
279
|
)
|
|
259
280
|
] }) : null
|
|
@@ -62,7 +62,7 @@ const RadioItem = React.forwardRef(
|
|
|
62
62
|
// records: react-aria's root is a `<label>`, which is `display:inline`, so without it the
|
|
63
63
|
// 16px dot collapses to nothing. `data-[invalid]` replaces `aria-invalid:` because
|
|
64
64
|
// react-aria puts `aria-invalid` on the `<input>` and `data-invalid` on this box.
|
|
65
|
-
"ui-radio data-[invalid]:border-destructive inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow
|
|
65
|
+
"ui-radio data-[invalid]:border-destructive inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow",
|
|
66
66
|
className
|
|
67
67
|
),
|
|
68
68
|
...props,
|
|
@@ -80,7 +80,7 @@ const Switch = React.forwardRef(
|
|
|
80
80
|
// components, so it silently outranked that token. Byte-identical: the token
|
|
81
81
|
// defaults to 0.5. (`shadow-xs` STAYS — .ui-switch declares --shadow-sm, so this
|
|
82
82
|
// utility is the switch's real resting elevation, not a duplicate.)
|
|
83
|
-
"peer ui-switch shadow-xs transition-all
|
|
83
|
+
"peer ui-switch shadow-xs transition-all",
|
|
84
84
|
className
|
|
85
85
|
),
|
|
86
86
|
children: [
|
|
@@ -149,7 +149,7 @@ function DialogContent({
|
|
|
149
149
|
showCloseButton ? /* @__PURE__ */ jsxs(
|
|
150
150
|
DialogClose,
|
|
151
151
|
{
|
|
152
|
-
className: "ui-focus-ring transition-opacity
|
|
152
|
+
className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
|
|
153
153
|
children: [
|
|
154
154
|
/* @__PURE__ */ jsx(X, { className: "ui-dialog-close-icon", "aria-hidden": "true" }),
|
|
155
155
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("feedback.alert.dismiss") })
|
|
@@ -265,7 +265,7 @@ function AlertDialogContent({
|
|
|
265
265
|
{
|
|
266
266
|
type: "button",
|
|
267
267
|
"data-slot": "dialog-close",
|
|
268
|
-
className: "ui-focus-ring transition-opacity
|
|
268
|
+
className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
|
|
269
269
|
"aria-label": t("feedback.alert.dismiss"),
|
|
270
270
|
onClick: () => {
|
|
271
271
|
state.setOpen(false);
|
|
@@ -6,7 +6,7 @@ import { chain, mergeRefs } from "@react-aria/utils";
|
|
|
6
6
|
import { Dialog as RacDialog, Modal, ModalOverlay } from "react-aria-components";
|
|
7
7
|
import { cva } from "class-variance-authority";
|
|
8
8
|
import { X } from "lucide-react";
|
|
9
|
-
import {
|
|
9
|
+
import { useMaxWidthBreakpoint } from "../../lib/breakpoint-token.js";
|
|
10
10
|
import { cn } from "../../lib/utils.js";
|
|
11
11
|
import { Slot } from "../../lib/slot.js";
|
|
12
12
|
import { overlayHeaderToneClass } from "./overlay-header-tone.js";
|
|
@@ -15,28 +15,8 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
15
15
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
16
16
|
const SHEET_BREAKPOINT_TOKEN = "--sheet-responsive-breakpoint-width";
|
|
17
17
|
const SHEET_BREAKPOINT_FALLBACK_QUERY = "(max-width: 768px)";
|
|
18
|
-
function cssLengthToPx(value, rootFontSize) {
|
|
19
|
-
const match = /^(-?\d*\.?\d+)(px|rem|em)?$/.exec(value.trim());
|
|
20
|
-
if (match == null) return void 0;
|
|
21
|
-
const amount = Number(match[1]);
|
|
22
|
-
if (!Number.isFinite(amount)) return void 0;
|
|
23
|
-
return match[2] === "rem" || match[2] === "em" ? amount * rootFontSize : amount;
|
|
24
|
-
}
|
|
25
|
-
function readSheetBreakpointQuery() {
|
|
26
|
-
if (typeof document === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
27
|
-
return SHEET_BREAKPOINT_FALLBACK_QUERY;
|
|
28
|
-
}
|
|
29
|
-
const rootStyle = window.getComputedStyle(document.documentElement);
|
|
30
|
-
const rootFontSize = cssLengthToPx(rootStyle.fontSize || "16px", 16) ?? 16;
|
|
31
|
-
const px = cssLengthToPx(rootStyle.getPropertyValue(SHEET_BREAKPOINT_TOKEN), rootFontSize);
|
|
32
|
-
return px == null ? SHEET_BREAKPOINT_FALLBACK_QUERY : `(max-width: ${String(px)}px)`;
|
|
33
|
-
}
|
|
34
18
|
function useSheetResponsiveMode(responsive = "side") {
|
|
35
|
-
const
|
|
36
|
-
React.useEffect(() => {
|
|
37
|
-
setQuery(readSheetBreakpointQuery());
|
|
38
|
-
}, []);
|
|
39
|
-
const compact = useMediaQuery(query);
|
|
19
|
+
const compact = useMaxWidthBreakpoint(SHEET_BREAKPOINT_TOKEN, SHEET_BREAKPOINT_FALLBACK_QUERY);
|
|
40
20
|
if (responsive === "side" || responsive === "bottom") return responsive;
|
|
41
21
|
return compact ? "bottom" : "side";
|
|
42
22
|
}
|
|
@@ -96,7 +96,8 @@ function Toolbar({
|
|
|
96
96
|
defaultValue: filter.defaultSelected,
|
|
97
97
|
onValueChange: (selected) => filter.onSelectedChange?.(selected),
|
|
98
98
|
placeholder: filter.placeholder,
|
|
99
|
-
disabled: disabled || filter.disabled
|
|
99
|
+
disabled: disabled || filter.disabled,
|
|
100
|
+
clearLabel: typeof filter.label === "string" ? t("navigation.filterBar.clearFilter", { label: filter.label }) : void 0
|
|
100
101
|
}
|
|
101
102
|
)
|
|
102
103
|
},
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "react-aria-components";
|
|
10
10
|
import { MoreHorizontal, Plus, X } from "lucide-react";
|
|
11
11
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
12
|
+
import { useMaxWidthBreakpoint } from "../../lib/breakpoint-token.js";
|
|
12
13
|
import { cn } from "../../lib/utils.js";
|
|
13
14
|
import {
|
|
14
15
|
DropdownMenu,
|
|
@@ -57,6 +58,13 @@ function resolveTabsAxis(tabPlacement, orientation) {
|
|
|
57
58
|
orientation: orientation ?? "horizontal"
|
|
58
59
|
};
|
|
59
60
|
}
|
|
61
|
+
const TABS_PLACEMENT_BREAKPOINT_TOKEN = "--tabs-placement-responsive-breakpoint-width";
|
|
62
|
+
const TABS_PLACEMENT_BREAKPOINT_FALLBACK_QUERY = "(max-width: 768px)";
|
|
63
|
+
function foldVerticalPlacement(axis, narrow) {
|
|
64
|
+
if (!narrow || axis.orientation !== "vertical") return axis;
|
|
65
|
+
const trailing = axis.placement === "end" || axis.placement === "bottom";
|
|
66
|
+
return { placement: trailing ? "bottom" : "top", orientation: "horizontal" };
|
|
67
|
+
}
|
|
60
68
|
function Tabs({
|
|
61
69
|
className,
|
|
62
70
|
orientation,
|
|
@@ -85,9 +93,13 @@ function Tabs({
|
|
|
85
93
|
}) {
|
|
86
94
|
const { t } = useTranslation();
|
|
87
95
|
const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
const narrow = useMaxWidthBreakpoint(
|
|
97
|
+
TABS_PLACEMENT_BREAKPOINT_TOKEN,
|
|
98
|
+
TABS_PLACEMENT_BREAKPOINT_FALLBACK_QUERY
|
|
99
|
+
);
|
|
100
|
+
const { placement, orientation: resolvedOrientation } = foldVerticalPlacement(
|
|
101
|
+
resolveTabsAxis(tabPlacement, orientation),
|
|
102
|
+
narrow
|
|
91
103
|
);
|
|
92
104
|
const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
|
|
93
105
|
const frame = React.useMemo(
|
|
@@ -470,7 +482,7 @@ const TabsContent = React.forwardRef(
|
|
|
470
482
|
ref,
|
|
471
483
|
id: value,
|
|
472
484
|
shouldForceMount: forceMount,
|
|
473
|
-
className: cn("ui-focus-ring flex-1
|
|
485
|
+
className: cn("ui-focus-ring flex-1", className),
|
|
474
486
|
render: (domProps, renderProps) => withDomProps("div", "tabs-content", props, domProps, {
|
|
475
487
|
// A force-mounted panel whose tab is not selected: Radix marked it `data-state="inactive"`
|
|
476
488
|
// and `hidden`; RAC marks it inert. The state hook is re-emitted so CSS keys the same way.
|
|
@@ -341,7 +341,9 @@
|
|
|
341
341
|
},
|
|
342
342
|
"numberInput": {
|
|
343
343
|
"increment": "Increase",
|
|
344
|
-
"decrement": "Decrease"
|
|
344
|
+
"decrement": "Decrease",
|
|
345
|
+
"incrementField": "Increase {label}",
|
|
346
|
+
"decrementField": "Decrease {label}"
|
|
345
347
|
}
|
|
346
348
|
},
|
|
347
349
|
"navigation": {
|
|
@@ -368,6 +370,7 @@
|
|
|
368
370
|
},
|
|
369
371
|
"filterBar": {
|
|
370
372
|
"appliedFilters": "Applied filters",
|
|
373
|
+
"clearFilter": "Clear {label} selection",
|
|
371
374
|
"removeFilter": "Remove filter: {label}",
|
|
372
375
|
"resultCount": {
|
|
373
376
|
"one": "{count} result",
|
|
@@ -442,6 +445,7 @@
|
|
|
442
445
|
},
|
|
443
446
|
"dateFormat": {
|
|
444
447
|
"iso": "ISO (yyyy-MM-dd)",
|
|
448
|
+
"ymd": "Year / Month / Day (yyyy/MM/dd)",
|
|
445
449
|
"dmy": "Day / Month / Year",
|
|
446
450
|
"mdy": "Month / Day / Year"
|
|
447
451
|
},
|
|
@@ -334,7 +334,9 @@
|
|
|
334
334
|
},
|
|
335
335
|
"numberInput": {
|
|
336
336
|
"increment": "増やす",
|
|
337
|
-
"decrement": "減らす"
|
|
337
|
+
"decrement": "減らす",
|
|
338
|
+
"incrementField": "{label}を増やす",
|
|
339
|
+
"decrementField": "{label}を減らす"
|
|
338
340
|
}
|
|
339
341
|
},
|
|
340
342
|
"navigation": {
|
|
@@ -361,6 +363,7 @@
|
|
|
361
363
|
},
|
|
362
364
|
"filterBar": {
|
|
363
365
|
"appliedFilters": "適用中のフィルター",
|
|
366
|
+
"clearFilter": "{label}の選択をクリア",
|
|
364
367
|
"removeFilter": "フィルターを解除: {label}",
|
|
365
368
|
"resultCount": "{count} 件の結果"
|
|
366
369
|
},
|
|
@@ -429,6 +432,7 @@
|
|
|
429
432
|
},
|
|
430
433
|
"dateFormat": {
|
|
431
434
|
"iso": "YYYY-MM-DD(年-月-日)",
|
|
435
|
+
"ymd": "YYYY/MM/DD(年/月/日)",
|
|
432
436
|
"dmy": "日/月/年",
|
|
433
437
|
"mdy": "月/日/年"
|
|
434
438
|
},
|
|
@@ -335,7 +335,9 @@
|
|
|
335
335
|
},
|
|
336
336
|
"numberInput": {
|
|
337
337
|
"increment": "Tăng",
|
|
338
|
-
"decrement": "Giảm"
|
|
338
|
+
"decrement": "Giảm",
|
|
339
|
+
"incrementField": "Tăng {label}",
|
|
340
|
+
"decrementField": "Giảm {label}"
|
|
339
341
|
}
|
|
340
342
|
},
|
|
341
343
|
"navigation": {
|
|
@@ -362,6 +364,7 @@
|
|
|
362
364
|
},
|
|
363
365
|
"filterBar": {
|
|
364
366
|
"appliedFilters": "Bộ lọc đang áp dụng",
|
|
367
|
+
"clearFilter": "Xóa lựa chọn {label}",
|
|
365
368
|
"removeFilter": "Bỏ bộ lọc: {label}",
|
|
366
369
|
"resultCount": "{count} kết quả"
|
|
367
370
|
},
|
|
@@ -430,6 +433,7 @@
|
|
|
430
433
|
},
|
|
431
434
|
"dateFormat": {
|
|
432
435
|
"iso": "ISO (yyyy-MM-dd)",
|
|
436
|
+
"ymd": "Năm / Tháng / Ngày (yyyy/MM/dd)",
|
|
433
437
|
"dmy": "Ngày / Tháng / Năm",
|
|
434
438
|
"mdy": "Tháng / Ngày / Năm"
|
|
435
439
|
},
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True while the viewport is at or below the width the theme token `token` names.
|
|
3
|
+
*
|
|
4
|
+
* The token is read once per mount rather than at module scope: the fallback is what SSR and the
|
|
5
|
+
* first client render agree on, and the themed value arrives in the effect. Same-value updates
|
|
6
|
+
* bail out inside React, so a theme that left the default alone re-renders nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useMaxWidthBreakpoint(token: string, fallbackQuery: string): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useMediaQuery } from "./hooks.js";
|
|
4
|
+
function cssLengthToPx(value, rootFontSize) {
|
|
5
|
+
const match = /^(-?\d*\.?\d+)(px|rem|em)?$/.exec(value.trim());
|
|
6
|
+
if (match == null) return void 0;
|
|
7
|
+
const amount = Number(match[1]);
|
|
8
|
+
if (!Number.isFinite(amount)) return void 0;
|
|
9
|
+
return match[2] === "rem" || match[2] === "em" ? amount * rootFontSize : amount;
|
|
10
|
+
}
|
|
11
|
+
function readMaxWidthQuery(token, fallbackQuery) {
|
|
12
|
+
if (typeof document === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
13
|
+
return fallbackQuery;
|
|
14
|
+
}
|
|
15
|
+
const rootStyle = window.getComputedStyle(document.documentElement);
|
|
16
|
+
const rootFontSize = cssLengthToPx(rootStyle.fontSize || "16px", 16) ?? 16;
|
|
17
|
+
const px = cssLengthToPx(rootStyle.getPropertyValue(token), rootFontSize);
|
|
18
|
+
return px == null ? fallbackQuery : `(max-width: ${String(px)}px)`;
|
|
19
|
+
}
|
|
20
|
+
function useMaxWidthBreakpoint(token, fallbackQuery) {
|
|
21
|
+
const [query, setQuery] = React.useState(fallbackQuery);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
setQuery(readMaxWidthQuery(token, fallbackQuery));
|
|
24
|
+
}, [token, fallbackQuery]);
|
|
25
|
+
return useMediaQuery(query);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
useMaxWidthBreakpoint
|
|
29
|
+
};
|
|
@@ -28,7 +28,7 @@ export type ProseProp = {
|
|
|
28
28
|
className?: ClassNameProp;
|
|
29
29
|
children?: ChildrenProp;
|
|
30
30
|
};
|
|
31
|
-
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
|
|
31
|
+
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, GetRowLabelProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
|
|
32
32
|
import type { TreeFieldNamesProp, TreeOptionProp } from "./data-entry.prop.js";
|
|
33
33
|
/**
|
|
34
34
|
* One key in a `Legend`: a tone, and the words that tone stands for.
|
|
@@ -255,6 +255,13 @@ export type DataTableProp<T> = {
|
|
|
255
255
|
data: T[];
|
|
256
256
|
columns: ColumnDefProp<T>[];
|
|
257
257
|
getRowId?: GetRowIdProp<T>;
|
|
258
|
+
/**
|
|
259
|
+
* Human name of a row, announced by its selection checkbox or radio as "Select row {label}".
|
|
260
|
+
* Default: the text of the `priority: "primary"` column, else of the first column, when that
|
|
261
|
+
* value is a string or number; the row id only as a last resort. `rowSelection.getCheckboxProps`
|
|
262
|
+
* `aria-label` still overrides a single row.
|
|
263
|
+
*/
|
|
264
|
+
getRowLabel?: GetRowLabelProp<T>;
|
|
258
265
|
selectable?: boolean;
|
|
259
266
|
selected?: SelectedIdsProp;
|
|
260
267
|
onSelectChange?: OnSelectChangeProp;
|
|
@@ -337,7 +337,14 @@ export type TabsProp = {
|
|
|
337
337
|
defaultValue?: string;
|
|
338
338
|
onValueChange?: (value: string) => void;
|
|
339
339
|
variant?: TabsVariantProp;
|
|
340
|
-
/**
|
|
340
|
+
/**
|
|
341
|
+
* Ant Design `tabPlacement`. Default `top`.
|
|
342
|
+
*
|
|
343
|
+
* `start`/`end` FOLD to `top`/`bottom` at or below
|
|
344
|
+
* `--tabs-placement-responsive-breakpoint-width` (48rem), arrow keys included — a vertical strip
|
|
345
|
+
* shares one inline axis with its panel and a phone has no room for both. Ant Design folds the
|
|
346
|
+
* same pair the same way. Set the token to `0px` to keep the strip vertical at every width.
|
|
347
|
+
*/
|
|
341
348
|
tabPlacement?: TabsPlacementProp;
|
|
342
349
|
/** Control tier of the triggers. Default `md`. Ant Design `size` (`small`/`middle`/`large`). */
|
|
343
350
|
size?: "sm" | "md" | "lg";
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -584,6 +584,11 @@ export declare const VOCABULARY_REGISTRY: {
|
|
|
584
584
|
readonly category: "data";
|
|
585
585
|
readonly description: "Row ID extractor generic";
|
|
586
586
|
};
|
|
587
|
+
readonly GetRowLabelProp: {
|
|
588
|
+
readonly file: "vocabulary/data.prop.ts";
|
|
589
|
+
readonly category: "data";
|
|
590
|
+
readonly description: "Row label extractor generic — the accessible name of a row's selection control";
|
|
591
|
+
};
|
|
587
592
|
readonly OnRowClickProp: {
|
|
588
593
|
readonly file: "vocabulary/data.prop.ts";
|
|
589
594
|
readonly category: "data";
|
|
@@ -1533,7 +1538,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
1533
1538
|
readonly DataTableProp: {
|
|
1534
1539
|
readonly group: "data-display";
|
|
1535
1540
|
readonly file: "components/data-display.prop.ts";
|
|
1536
|
-
readonly vocabulary: readonly ["ColumnDefProp", "DensityProp", "SortStateProp", "SelectedIdsProp", "HandlerProp", "TablePresetProp", "TableColumnPriorityProp", "BreakpointProp", "ColumnFixedProp", "ColumnFilterItemProp", "ColumnFilterStateProp", "ColumnSorterProp", "TableRowSelectionProp", "TableExpandableProp", "TableSummaryProp", "TableScrollProp", "TableStickyProp", "OnRowProp", "TablePaginationProp", "SortDirectionProp", "OnColumnFilterChangeProp"];
|
|
1541
|
+
readonly vocabulary: readonly ["ColumnDefProp", "DensityProp", "SortStateProp", "SelectedIdsProp", "GetRowLabelProp", "HandlerProp", "TablePresetProp", "TableColumnPriorityProp", "BreakpointProp", "ColumnFixedProp", "ColumnFilterItemProp", "ColumnFilterStateProp", "ColumnSorterProp", "TableRowSelectionProp", "TableExpandableProp", "TableSummaryProp", "TableScrollProp", "TableStickyProp", "OnRowProp", "TablePaginationProp", "SortDirectionProp", "OnColumnFilterChangeProp"];
|
|
1537
1542
|
};
|
|
1538
1543
|
readonly ListRowDensityProp: {
|
|
1539
1544
|
readonly group: "data-display";
|
package/dist/props/registry.js
CHANGED
|
@@ -585,6 +585,11 @@ const VOCABULARY_REGISTRY = {
|
|
|
585
585
|
category: "data",
|
|
586
586
|
description: "Row ID extractor generic"
|
|
587
587
|
},
|
|
588
|
+
GetRowLabelProp: {
|
|
589
|
+
file: "vocabulary/data.prop.ts",
|
|
590
|
+
category: "data",
|
|
591
|
+
description: "Row label extractor generic \u2014 the accessible name of a row's selection control"
|
|
592
|
+
},
|
|
588
593
|
OnRowClickProp: {
|
|
589
594
|
file: "vocabulary/data.prop.ts",
|
|
590
595
|
category: "data",
|
|
@@ -1833,6 +1838,7 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
1833
1838
|
"DensityProp",
|
|
1834
1839
|
"SortStateProp",
|
|
1835
1840
|
"SelectedIdsProp",
|
|
1841
|
+
"GetRowLabelProp",
|
|
1836
1842
|
"HandlerProp",
|
|
1837
1843
|
"TablePresetProp",
|
|
1838
1844
|
"TableColumnPriorityProp",
|
|
@@ -7,6 +7,11 @@ import type { BreakpointProp, ColumnAlignProp, SortDirectionProp } from "./inter
|
|
|
7
7
|
import type { TableDensityProp } from "./layout.prop.js";
|
|
8
8
|
/** Generic row identifier extractor for tables with selection. */
|
|
9
9
|
export type GetRowIdProp<T> = (row: T) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable row name — what a row's selection control is announced as. The row id is a KEY,
|
|
12
|
+
* not a name: announced, it reads a UUID aloud.
|
|
13
|
+
*/
|
|
14
|
+
export type GetRowLabelProp<T> = (row: T) => string;
|
|
10
15
|
/** Row click navigation handler. */
|
|
11
16
|
export type OnRowClickProp<T> = (row: T) => void;
|
|
12
17
|
/** Column definition for DataTable. */
|