@godxjp/ui 33.22.2 → 33.25.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/DESIGN.md +1 -1
- package/README.md +16 -5
- package/agent/DESIGN.md +1 -1
- package/agent/START-HERE.md +8 -8
- package/agent/components/AgentBadge.json +43 -0
- package/agent/components/AppShell.json +6 -0
- package/agent/components/Avatar.json +7 -0
- package/agent/components/CatalogDetail.json +7 -3
- package/agent/components/DatePicker.json +1 -1
- package/agent/components/DropdownMenu.json +1 -0
- package/agent/components/EditorialCard.json +2 -1
- package/agent/components/Image.json +6 -1
- package/agent/components/PageContainer.json +5 -0
- package/agent/components/PageErrorBoundary.json +48 -0
- package/agent/components/Pagination.json +5 -0
- package/agent/components/Popover.json +1 -0
- package/agent/components/Select.json +2 -1
- package/agent/components/Toaster.json +2 -2
- package/agent/components/Toolbar.json +1 -1
- package/agent/components/Topbar.json +1 -1
- package/agent/components-index.json +10 -0
- package/agent/components.json +138 -11
- package/agent/index.json +8 -8
- package/agent/llms.txt +7 -7
- package/agent/patterns/notice-bar.json +16 -0
- package/agent/patterns-index.json +15 -0
- package/agent/patterns.json +16 -0
- package/agent/rules.json +2 -2
- package/agent/tokens.json +121 -7
- package/dist/components/charts/chart-cartesian.js +5 -1
- package/dist/components/charts/chart-summary.d.ts +1 -1
- package/dist/components/charts/chart-summary.js +17 -6
- package/dist/components/charts/compact-bar-trend.js +4 -3
- package/dist/components/charts/pie-chart.js +2 -1
- package/dist/components/data-display/avatar.d.ts +1 -1
- package/dist/components/data-display/avatar.js +7 -1
- package/dist/components/data-display/badge.d.ts +11 -0
- package/dist/components/data-display/badge.js +20 -0
- package/dist/components/data-display/image.d.ts +1 -0
- package/dist/components/data-display/image.js +10 -2
- package/dist/components/data-display/index.d.ts +2 -2
- package/dist/components/data-display/index.js +2 -1
- package/dist/components/data-display/popover.js +11 -0
- package/dist/components/data-entry/calendar.js +1 -1
- package/dist/components/data-entry/date-picker.js +22 -5
- package/dist/components/data-entry/record-picker.js +122 -98
- package/dist/components/data-entry/select.js +13 -3
- package/dist/components/general/button.js +2 -2
- package/dist/components/layout/affix.js +1 -1
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +3 -1
- package/dist/components/layout/editorial-catalog.d.ts +1 -1
- package/dist/components/layout/editorial-catalog.js +53 -15
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +6 -3
- package/dist/components/layout/page-error-boundary.d.ts +32 -0
- package/dist/components/layout/page-error-boundary.js +57 -0
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +100 -49
- package/dist/components/layout/topbar.js +108 -21
- package/dist/components/navigation/dropdown-menu.d.ts +13 -3
- package/dist/components/navigation/dropdown-menu.js +36 -6
- package/dist/components/navigation/pagination.d.ts +1 -1
- package/dist/components/navigation/pagination.js +13 -1
- package/dist/components/navigation/toolbar.js +2 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +7 -0
- package/dist/i18n/messages/ja.json +7 -0
- package/dist/i18n/messages/vi.json +7 -0
- package/dist/props/components/data-display.prop.d.ts +29 -0
- package/dist/props/components/layout.prop.d.ts +52 -2
- package/dist/props/components/navigation.prop.d.ts +15 -1
- package/dist/props/registry.d.ts +65 -5
- package/dist/props/registry.js +90 -4
- package/dist/styles/alert-layout.css +4 -0
- package/dist/styles/application.css +24 -0
- package/dist/styles/badge-layout.css +14 -0
- package/dist/styles/base.css +11 -0
- package/dist/styles/control.css +32 -19
- package/dist/styles/core.css +1 -0
- package/dist/styles/data-display-layout.css +51 -0
- package/dist/styles/dialog-layout.css +9 -0
- package/dist/styles/editorial-layout.css +2076 -0
- package/dist/styles/editorial.css +3 -0
- package/dist/styles/icon-layout.css +3 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/layers.json +60 -9
- package/dist/styles/layout.css +9 -1985
- package/dist/styles/precompiled/alert-layout.css +2 -0
- package/dist/styles/precompiled/badge-layout.css +2 -0
- package/dist/styles/precompiled/base.css +2 -0
- package/dist/styles/precompiled/card-layout.css +2 -0
- package/dist/styles/precompiled/chart-layout.css +2 -0
- package/dist/styles/precompiled/control.css +2 -0
- package/dist/styles/precompiled/data-display-layout.css +2 -0
- package/dist/styles/precompiled/data-entry-layout.css +2 -0
- package/dist/styles/precompiled/dialog-layout.css +2 -0
- package/dist/styles/precompiled/editorial-account-layout.css +2 -0
- package/dist/styles/precompiled/editorial-layout.css +2 -0
- package/dist/styles/precompiled/float-button-layout.css +2 -0
- package/dist/styles/precompiled/form-layout.css +2 -0
- package/dist/styles/precompiled/icon-layout.css +2 -0
- package/dist/styles/precompiled/layout.css +2 -0
- package/dist/styles/precompiled/logo-layout.css +2 -0
- package/dist/styles/precompiled/motion.css +2 -0
- package/dist/styles/precompiled/navigation-layout.css +2 -0
- package/dist/styles/precompiled/shell-layout.css +2 -0
- package/dist/styles/precompiled/table-layout.css +2 -0
- package/dist/styles/precompiled/text-layout.css +2 -0
- package/dist/styles/precompiled/toggle.css +2 -0
- package/dist/styles/precompiled/vendor-day-picker.css +2 -0
- package/dist/styles/precompiled/vendor-sonner.css +2 -0
- package/dist/styles/shell-layout.css +17 -1
- package/dist/styles/table-layout.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/tokens/components/badge.css +3 -0
- package/dist/tokens/components/control.css +3 -0
- package/dist/tokens/components/data-display.css +9 -0
- package/dist/tokens/components/editorial.css +9 -6
- package/dist/tokens/components/shell.css +2 -0
- package/dist/tokens/foundation.css +8 -0
- package/docs/CONSUMER-RULES.md +36 -1
- package/package.json +4 -2
- package/scripts/consumer-rule.md +1 -1
- package/scripts/prune-css.mjs +56 -8
- package/scripts/ui-audit.mjs +36 -0
- package/skills/godxjp-ui-navigation/SKILL.md +14 -9
- package/skills/godxjp-ui-navigation/references/principles.md +2 -2
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { Command, CommandGroup } from "./command.js";
|
|
22
22
|
import { Input } from "./input.js";
|
|
23
23
|
import { isImeComposing } from "../../lib/ime.js";
|
|
24
|
+
import { FieldNameContext } from "../../lib/field-a11y.js";
|
|
24
25
|
import { Select } from "./select.js";
|
|
25
26
|
function selectLoadOptionsFor(loadOptions) {
|
|
26
27
|
if (!loadOptions) return void 0;
|
|
@@ -35,103 +36,53 @@ function selectLoadOptionsFor(loadOptions) {
|
|
|
35
36
|
return { options: r.options, hasMore: r.nextCursor !== void 0 };
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
const RecordPicker = React.forwardRef(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(next)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (shape === "inline") {
|
|
82
|
-
return /* @__PURE__ */ jsx(
|
|
83
|
-
InlinePicker,
|
|
84
|
-
{
|
|
85
|
-
ref,
|
|
86
|
-
isMultiple,
|
|
87
|
-
selected,
|
|
88
|
-
commit,
|
|
89
|
-
staticOptions,
|
|
90
|
-
loadOptions,
|
|
91
|
-
filters,
|
|
92
|
-
selectedOptions,
|
|
93
|
-
emptyOption,
|
|
94
|
-
placeholder,
|
|
95
|
-
dialogTitle,
|
|
96
|
-
notFoundContent,
|
|
97
|
-
loadMoreLabel,
|
|
98
|
-
disabled,
|
|
99
|
-
size,
|
|
100
|
-
className,
|
|
101
|
-
"data-field": fieldName,
|
|
102
|
-
...props
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
const total = count ?? (options ? staticOptions.length : void 0);
|
|
107
|
-
const asDialog = total === void 0 ? Boolean(loadOptions) : total > threshold;
|
|
108
|
-
if (!asDialog) {
|
|
109
|
-
return /* @__PURE__ */ jsx(
|
|
110
|
-
Select,
|
|
111
|
-
{
|
|
112
|
-
mode: isMultiple ? "multiple" : void 0,
|
|
113
|
-
loadOptions: selectLoadOptions,
|
|
114
|
-
id: props.id,
|
|
115
|
-
"data-field": fieldName,
|
|
116
|
-
"aria-describedby": props["aria-describedby"],
|
|
117
|
-
labelInValue: false,
|
|
118
|
-
showSearch: true,
|
|
119
|
-
options: withEmptyOption(options, emptyOption),
|
|
120
|
-
value: isMultiple ? selected : selected[0] ?? void 0,
|
|
121
|
-
onValueChange: ((next) => commit(toArray(next))),
|
|
122
|
-
placeholder: placeholder ?? t("dataEntry.recordPicker.placeholder"),
|
|
123
|
-
notFoundContent,
|
|
124
|
-
disabled,
|
|
125
|
-
size,
|
|
126
|
-
className
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
}
|
|
39
|
+
const RecordPicker = React.forwardRef(function RecordPicker2({
|
|
40
|
+
mode = "single",
|
|
41
|
+
shape = "auto",
|
|
42
|
+
value,
|
|
43
|
+
defaultValue,
|
|
44
|
+
onValueChange,
|
|
45
|
+
options,
|
|
46
|
+
loadOptions,
|
|
47
|
+
count,
|
|
48
|
+
threshold = 10,
|
|
49
|
+
filters,
|
|
50
|
+
selectedOptions,
|
|
51
|
+
emptyOption,
|
|
52
|
+
placeholder,
|
|
53
|
+
dialogTitle,
|
|
54
|
+
notFoundContent,
|
|
55
|
+
loadMoreLabel,
|
|
56
|
+
disabled = false,
|
|
57
|
+
size,
|
|
58
|
+
className,
|
|
59
|
+
"data-field": fieldName,
|
|
60
|
+
...props
|
|
61
|
+
}, ref) {
|
|
62
|
+
const { t } = useTranslation();
|
|
63
|
+
const isMultiple = mode === "multiple";
|
|
64
|
+
const controlled = value !== void 0;
|
|
65
|
+
const [internal, setInternal] = React.useState(
|
|
66
|
+
() => toArray(defaultValue ?? (isMultiple ? [] : null))
|
|
67
|
+
);
|
|
68
|
+
const selected = controlled ? toArray(value) : internal;
|
|
69
|
+
const commit = React.useCallback(
|
|
70
|
+
(next) => {
|
|
71
|
+
if (!controlled) setInternal(next);
|
|
72
|
+
onValueChange?.(isMultiple ? next : next[0] ?? null);
|
|
73
|
+
},
|
|
74
|
+
[controlled, isMultiple, onValueChange]
|
|
75
|
+
);
|
|
76
|
+
const selectLoadOptions = React.useMemo(() => selectLoadOptionsFor(loadOptions), [loadOptions]);
|
|
77
|
+
const staticOptions = React.useMemo(
|
|
78
|
+
() => options ? normalizeSelectOptions(options) : [],
|
|
79
|
+
[options]
|
|
80
|
+
);
|
|
81
|
+
if (shape === "inline") {
|
|
130
82
|
return /* @__PURE__ */ jsx(
|
|
131
|
-
|
|
83
|
+
InlinePicker,
|
|
132
84
|
{
|
|
133
85
|
ref,
|
|
134
|
-
"data-field": fieldName,
|
|
135
86
|
isMultiple,
|
|
136
87
|
selected,
|
|
137
88
|
commit,
|
|
@@ -147,11 +98,59 @@ const RecordPicker = React.forwardRef(
|
|
|
147
98
|
disabled,
|
|
148
99
|
size,
|
|
149
100
|
className,
|
|
101
|
+
"data-field": fieldName,
|
|
150
102
|
...props
|
|
151
103
|
}
|
|
152
104
|
);
|
|
153
105
|
}
|
|
154
|
-
);
|
|
106
|
+
const total = count ?? (options ? staticOptions.length : void 0);
|
|
107
|
+
const asDialog = total === void 0 ? Boolean(loadOptions) : total > threshold;
|
|
108
|
+
if (!asDialog) {
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
Select,
|
|
111
|
+
{
|
|
112
|
+
mode: isMultiple ? "multiple" : void 0,
|
|
113
|
+
loadOptions: selectLoadOptions,
|
|
114
|
+
id: props.id,
|
|
115
|
+
"data-field": fieldName,
|
|
116
|
+
"aria-describedby": props["aria-describedby"],
|
|
117
|
+
labelInValue: false,
|
|
118
|
+
showSearch: true,
|
|
119
|
+
options: withEmptyOption(options, emptyOption),
|
|
120
|
+
value: isMultiple ? selected : selected[0] ?? void 0,
|
|
121
|
+
onValueChange: ((next) => commit(toArray(next))),
|
|
122
|
+
placeholder: placeholder ?? t("dataEntry.recordPicker.placeholder"),
|
|
123
|
+
notFoundContent,
|
|
124
|
+
disabled,
|
|
125
|
+
size,
|
|
126
|
+
className
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
DialogPicker,
|
|
132
|
+
{
|
|
133
|
+
ref,
|
|
134
|
+
"data-field": fieldName,
|
|
135
|
+
isMultiple,
|
|
136
|
+
selected,
|
|
137
|
+
commit,
|
|
138
|
+
staticOptions,
|
|
139
|
+
loadOptions,
|
|
140
|
+
filters,
|
|
141
|
+
selectedOptions,
|
|
142
|
+
emptyOption,
|
|
143
|
+
placeholder,
|
|
144
|
+
dialogTitle,
|
|
145
|
+
notFoundContent,
|
|
146
|
+
loadMoreLabel,
|
|
147
|
+
disabled,
|
|
148
|
+
size,
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
});
|
|
155
154
|
const INLINE_SUGGESTION_LIMIT = 8;
|
|
156
155
|
function toArray(v) {
|
|
157
156
|
if (v === null || v === void 0) return [];
|
|
@@ -179,10 +178,30 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
179
178
|
className,
|
|
180
179
|
id,
|
|
181
180
|
"data-field": fieldName,
|
|
182
|
-
"aria-describedby": describedBy
|
|
181
|
+
"aria-describedby": describedBy,
|
|
182
|
+
"aria-label": ariaLabel,
|
|
183
|
+
"aria-labelledby": ariaLabelledBy,
|
|
184
|
+
"aria-errormessage": ariaErrorMessage,
|
|
185
|
+
"aria-invalid": ariaInvalid,
|
|
186
|
+
"aria-required": ariaRequired
|
|
183
187
|
}, ref) {
|
|
184
188
|
const { t } = useTranslation();
|
|
185
189
|
const [query, setQuery] = React.useState("");
|
|
190
|
+
const inputRef = React.useRef(null);
|
|
191
|
+
const [hasNativeLabel, setHasNativeLabel] = React.useState(false);
|
|
192
|
+
React.useLayoutEffect(() => {
|
|
193
|
+
setHasNativeLabel((inputRef.current?.labels?.length ?? 0) > 0);
|
|
194
|
+
}, [id]);
|
|
195
|
+
const enclosingField = React.useContext(FieldNameContext);
|
|
196
|
+
const fallbackName = ariaLabel === void 0 && ariaLabelledBy === void 0 && !enclosingField && !hasNativeLabel ? placeholder ?? t("dataEntry.recordPicker.search") : void 0;
|
|
197
|
+
const setInputRef = React.useCallback(
|
|
198
|
+
(node) => {
|
|
199
|
+
inputRef.current = node;
|
|
200
|
+
if (typeof ref === "function") ref(node);
|
|
201
|
+
else if (ref) ref.current = node;
|
|
202
|
+
},
|
|
203
|
+
[ref]
|
|
204
|
+
);
|
|
186
205
|
const [rows, setRows] = React.useState(staticOptions);
|
|
187
206
|
const [openSuggest, setOpenSuggest] = React.useState(false);
|
|
188
207
|
const [dialogQuery, setDialogQuery] = React.useState(null);
|
|
@@ -214,7 +233,7 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
214
233
|
/* @__PURE__ */ jsx(
|
|
215
234
|
Input,
|
|
216
235
|
{
|
|
217
|
-
ref,
|
|
236
|
+
ref: setInputRef,
|
|
218
237
|
type: "search",
|
|
219
238
|
value: query,
|
|
220
239
|
disabled,
|
|
@@ -232,7 +251,12 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
232
251
|
className: "ui-record-picker-inline-input",
|
|
233
252
|
id,
|
|
234
253
|
"data-field": fieldName,
|
|
235
|
-
"aria-describedby": describedBy
|
|
254
|
+
"aria-describedby": describedBy,
|
|
255
|
+
"aria-label": ariaLabel ?? fallbackName,
|
|
256
|
+
"aria-labelledby": ariaLabelledBy,
|
|
257
|
+
"aria-errormessage": ariaErrorMessage,
|
|
258
|
+
"aria-invalid": ariaInvalid,
|
|
259
|
+
"aria-required": ariaRequired
|
|
236
260
|
}
|
|
237
261
|
),
|
|
238
262
|
/* @__PURE__ */ jsxs(
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
Select as AriaSelect,
|
|
14
14
|
SelectStateContext,
|
|
15
15
|
SelectValue as AriaSelectValue,
|
|
16
|
-
Separator as AriaSeparator
|
|
16
|
+
Separator as AriaSeparator,
|
|
17
|
+
Text as AriaText
|
|
17
18
|
} from "react-aria-components";
|
|
18
19
|
import { ChevronDown, Loader2, X } from "lucide-react";
|
|
19
20
|
import { cn } from "../../lib/utils.js";
|
|
@@ -613,6 +614,14 @@ function withOptionIcon(option, label) {
|
|
|
613
614
|
/* @__PURE__ */ jsx("span", { className: "ui-select-option-label", children: label })
|
|
614
615
|
] });
|
|
615
616
|
}
|
|
617
|
+
function optionRow(option) {
|
|
618
|
+
if (!option.sublabel) return withOptionIcon(option, option.label);
|
|
619
|
+
return /* @__PURE__ */ jsxs("span", { className: "ui-select-option-body", children: [
|
|
620
|
+
option.icon ? /* @__PURE__ */ jsx("span", { className: "ui-select-option-icon", "aria-hidden": "true", children: option.icon }) : null,
|
|
621
|
+
/* @__PURE__ */ jsx(AriaText, { slot: "label", className: "ui-select-option-label", children: option.label }),
|
|
622
|
+
/* @__PURE__ */ jsx(AriaText, { slot: "description", className: "ui-search-select-option-sublabel", children: option.sublabel })
|
|
623
|
+
] });
|
|
624
|
+
}
|
|
616
625
|
function DataSelectLabel({
|
|
617
626
|
labelRender,
|
|
618
627
|
options
|
|
@@ -729,7 +738,7 @@ function DataSelect(props) {
|
|
|
729
738
|
disabled: option.disabled,
|
|
730
739
|
"data-testid": optionTestId(option.value),
|
|
731
740
|
selectedIcon: menuItemSelectedIcon,
|
|
732
|
-
children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) :
|
|
741
|
+
children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) : optionRow(option)
|
|
733
742
|
},
|
|
734
743
|
option.value
|
|
735
744
|
);
|
|
@@ -784,8 +793,9 @@ function DataSelect(props) {
|
|
|
784
793
|
...ariaProps,
|
|
785
794
|
children: [
|
|
786
795
|
prefix === void 0 || prefix === null ? null : /* @__PURE__ */ jsx("span", { "data-slot": "select-prefix", className: "ui-select-prefix", children: prefix }),
|
|
787
|
-
/* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : options.some((option) => option.icon) ? (
|
|
796
|
+
/* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : options.some((option) => option.icon || option.sublabel) ? (
|
|
788
797
|
// gh#1444 — the selected option's icon leads the trigger too, as in the search panel.
|
|
798
|
+
// gh#1495 — and a sublabel stays in the row: the trigger names the pick by its label.
|
|
789
799
|
/* @__PURE__ */ jsx(
|
|
790
800
|
DataSelectLabel,
|
|
791
801
|
{
|
|
@@ -54,11 +54,11 @@ const buttonVariants = cva("ui-button", {
|
|
|
54
54
|
md: "ui-button--default-size",
|
|
55
55
|
// utilities after components, so only a utility can out-rank a child's own `size-*`.
|
|
56
56
|
// They read the token, so the value is still themeable.
|
|
57
|
-
xs: "ui-button--xs [&_svg:not([class*='size-'])]:size-[var(--button-xs-icon-size)]",
|
|
57
|
+
xs: "ui-button--xs [&_svg:not([class*='size-'])]:size-[var(--button-icon-size,var(--button-xs-icon-size))]",
|
|
58
58
|
sm: "ui-button--sm",
|
|
59
59
|
lg: "ui-button--lg",
|
|
60
60
|
icon: "ui-button--icon",
|
|
61
|
-
"icon-xs": "ui-button--icon-xs [&_svg]:size-[var(--button-xs-icon-size)] [&_svg]:shrink-0",
|
|
61
|
+
"icon-xs": "ui-button--icon-xs [&_svg]:size-[var(--button-icon-size,var(--button-xs-icon-size))] [&_svg]:shrink-0",
|
|
62
62
|
"icon-sm": "ui-button--icon-sm",
|
|
63
63
|
"icon-lg": "ui-button--icon-lg",
|
|
64
64
|
// The glove step (gh#1304): ≥56×56, `--control-height-touch`.
|
|
@@ -106,7 +106,7 @@ const Affix = React.forwardRef(function Affix2({
|
|
|
106
106
|
};
|
|
107
107
|
}, [measure, targetElement]);
|
|
108
108
|
React.useEffect(() => {
|
|
109
|
-
if (!affixed ||
|
|
109
|
+
if (!affixed || typeof window === "undefined") return void 0;
|
|
110
110
|
let frame = null;
|
|
111
111
|
const onScroll = () => {
|
|
112
112
|
if (frame !== null) return;
|
|
@@ -3,4 +3,4 @@ import type { AppShellProp } from "../../props/components/layout.prop.js";
|
|
|
3
3
|
export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
|
|
4
4
|
/** Same rail boundary as shell-layout.css; SSR starts in docked mode. */
|
|
5
5
|
export declare function useAppShellNavigationMode(): "drawer" | "docked";
|
|
6
|
-
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileTabBar, mobileTabBarLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
6
|
+
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, errorBoundary, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileTabBar, mobileTabBarLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
@@ -10,6 +10,7 @@ import { ShellTabBar } from "./mobile-shell.js";
|
|
|
10
10
|
import { useToastStickyBarClearance } from "../../lib/toast-clearance.js";
|
|
11
11
|
import { NavSurfaceProvider } from "./nav-surface.js";
|
|
12
12
|
import { TopbarItem } from "./topbar-item.js";
|
|
13
|
+
import { PageErrorBoundary } from "./page-error-boundary.js";
|
|
13
14
|
import { isDevelopment } from "../../lib/dev.js";
|
|
14
15
|
function useAppShellNavigationMode() {
|
|
15
16
|
return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
|
|
@@ -25,6 +26,7 @@ function AppShell({
|
|
|
25
26
|
breadcrumb,
|
|
26
27
|
footer,
|
|
27
28
|
children,
|
|
29
|
+
errorBoundary = true,
|
|
28
30
|
sidebarCollapsed = false,
|
|
29
31
|
responsiveNavigation = "drawer",
|
|
30
32
|
topbarSpan = "content",
|
|
@@ -201,7 +203,7 @@ function AppShell({
|
|
|
201
203
|
tabIndex: 0,
|
|
202
204
|
children: [
|
|
203
205
|
breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
|
|
204
|
-
children,
|
|
206
|
+
errorBoundary === false ? children : /* @__PURE__ */ jsx(PageErrorBoundary, { ...errorBoundary === true ? {} : errorBoundary, children }),
|
|
205
207
|
showTabBar && footerRegion
|
|
206
208
|
]
|
|
207
209
|
}
|
|
@@ -7,7 +7,7 @@ export declare function EditorialFilterPanel({ variant, children, action, classN
|
|
|
7
7
|
/** One field of an `EditorialFilterPanel`: a visible label over one control. */
|
|
8
8
|
export declare function EditorialFilterField({ label, htmlFor, children, className, }: EditorialFilterFieldProp): React.JSX.Element;
|
|
9
9
|
/** The head of a catalog item's page: framed picture beside kicker, h1, tags, summary, facts. */
|
|
10
|
-
export declare function CatalogDetail({ variant, media, favorite, mediaCaption, overline, title, meta, summary, actions, facts, className, }: CatalogDetailProp): React.JSX.Element;
|
|
10
|
+
export declare function CatalogDetail({ variant, media, image, favorite, mediaCaption, overline, title, meta, summary, actions, facts, className, }: CatalogDetailProp): React.JSX.Element;
|
|
11
11
|
/** One outlined tag in a `CatalogDetail` meta row. */
|
|
12
12
|
export declare function CatalogDetailTag({ children, className }: CatalogDetailTagProp): React.JSX.Element;
|
|
13
13
|
/** One band of an item page under its head: hairline, h2, muted paragraph, then the rest. */
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
2
4
|
import { Bookmark } from "lucide-react";
|
|
5
|
+
import { Image } from "../data-display/image.js";
|
|
3
6
|
import { cn } from "../../lib/utils.js";
|
|
4
7
|
function EditorialFilterPanel({
|
|
5
8
|
variant = "panel",
|
|
@@ -37,6 +40,7 @@ function EditorialFilterField({
|
|
|
37
40
|
function CatalogDetail({
|
|
38
41
|
variant = "card",
|
|
39
42
|
media,
|
|
43
|
+
image,
|
|
40
44
|
favorite,
|
|
41
45
|
mediaCaption,
|
|
42
46
|
overline,
|
|
@@ -47,6 +51,18 @@ function CatalogDetail({
|
|
|
47
51
|
facts,
|
|
48
52
|
className
|
|
49
53
|
}) {
|
|
54
|
+
const [load, setLoad] = React.useState(null);
|
|
55
|
+
const src = image?.src;
|
|
56
|
+
const status = image ? load && load.src === src ? load.status : "loading" : void 0;
|
|
57
|
+
const onStatusChange = React.useCallback(
|
|
58
|
+
(next) => {
|
|
59
|
+
if (src !== void 0)
|
|
60
|
+
setLoad(
|
|
61
|
+
(previous) => previous?.src === src && previous.status === next ? previous : { src, status: next }
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
[src]
|
|
65
|
+
);
|
|
50
66
|
return /* @__PURE__ */ jsxs(
|
|
51
67
|
"div",
|
|
52
68
|
{
|
|
@@ -55,21 +71,43 @@ function CatalogDetail({
|
|
|
55
71
|
className: cn("ui-catalog-detail", className),
|
|
56
72
|
children: [
|
|
57
73
|
/* @__PURE__ */ jsxs("div", { className: "ui-catalog-detail-visual", children: [
|
|
58
|
-
/* @__PURE__ */ jsxs(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
/* @__PURE__ */ jsxs(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: "ui-catalog-detail-art",
|
|
78
|
+
"data-status": status,
|
|
79
|
+
"aria-busy": status === "loading" ? true : void 0,
|
|
80
|
+
children: [
|
|
81
|
+
favorite && /* @__PURE__ */ jsx(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
type: "button",
|
|
85
|
+
"aria-pressed": favorite.pressed,
|
|
86
|
+
"aria-label": favorite.label,
|
|
87
|
+
disabled: favorite.disabled,
|
|
88
|
+
className: "ui-catalog-card-favorite ui-focus-ring",
|
|
89
|
+
onClick: () => favorite.onPressedChange(!favorite.pressed),
|
|
90
|
+
children: /* @__PURE__ */ jsx(Bookmark, { "aria-hidden": true, className: "ui-catalog-card-favorite-icon" })
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
image ? /* @__PURE__ */ jsx(
|
|
94
|
+
Image,
|
|
95
|
+
{
|
|
96
|
+
src: image.src,
|
|
97
|
+
alt: image.alt,
|
|
98
|
+
width: image.width,
|
|
99
|
+
height: image.height,
|
|
100
|
+
fit: "contain",
|
|
101
|
+
fallback: image.fallback,
|
|
102
|
+
fetchPriority: image.priority ? "high" : void 0,
|
|
103
|
+
preview: false,
|
|
104
|
+
onStatusChange
|
|
105
|
+
},
|
|
106
|
+
image.src
|
|
107
|
+
) : media
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
),
|
|
73
111
|
mediaCaption != null && /* @__PURE__ */ jsx("p", { className: "ui-catalog-detail-caption", children: mediaCaption })
|
|
74
112
|
] }),
|
|
75
113
|
/* @__PURE__ */ jsxs("div", { className: "ui-catalog-detail-main", children: [
|
|
@@ -31,6 +31,8 @@ export { CenteredShell } from "./centered-shell.js";
|
|
|
31
31
|
export type { CenteredShellProp, CenteredShellProps, CenteredShellAuthProp, CenteredShellPageProp, } from "./centered-shell.js";
|
|
32
32
|
export type { CenteredShellWidthProp, CenteredShellAlignProp } from "../../props/vocabulary/index.js";
|
|
33
33
|
export { ErrorSurface } from "./error-surface.js";
|
|
34
|
+
export { PageErrorBoundary } from "./page-error-boundary.js";
|
|
35
|
+
export type { PageErrorBoundaryFallbackProp, PageErrorBoundaryProp, PageErrorBoundaryProps, } from "./page-error-boundary.js";
|
|
34
36
|
export type { ErrorSurfaceMaintenanceProp, ErrorSurfaceProp, ErrorSurfaceProps, } from "./error-surface.js";
|
|
35
37
|
export type { ErrorSurfaceModeProp, ErrorSurfaceStatusProp } from "../../props/vocabulary/index.js";
|
|
36
38
|
export { Breadcrumb } from "./breadcrumb.js";
|
|
@@ -14,6 +14,7 @@ import { AuthStack } from "./auth-stack.js";
|
|
|
14
14
|
import { MobileShell } from "./mobile-shell.js";
|
|
15
15
|
import { CenteredShell } from "./centered-shell.js";
|
|
16
16
|
import { ErrorSurface } from "./error-surface.js";
|
|
17
|
+
import { PageErrorBoundary } from "./page-error-boundary.js";
|
|
17
18
|
import { Breadcrumb } from "./breadcrumb.js";
|
|
18
19
|
import { createSidebarLink, Sidebar, SidebarHeader, SidebarItem, SidebarSection } from "./sidebar.js";
|
|
19
20
|
import { Topbar } from "./topbar.js";
|
|
@@ -141,6 +142,7 @@ export {
|
|
|
141
142
|
NavList,
|
|
142
143
|
OrgSwitcher,
|
|
143
144
|
PageContainer,
|
|
145
|
+
PageErrorBoundary,
|
|
144
146
|
PriceSummary,
|
|
145
147
|
ResizableHandle,
|
|
146
148
|
ResizablePanel,
|
|
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
|
|
|
2
2
|
export type { PageContainerProp, PageContainerProp as PageContainerProps, PageContainerExtraProp, } from "../../props/components/layout.prop.js";
|
|
3
3
|
export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
|
|
4
4
|
export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
|
|
5
|
-
declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbCurrentItem, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
5
|
+
declare function PageContainerRoot({ title, subtitle, status, extra, help, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbCurrentItem, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
6
6
|
export declare const PageContainer: typeof PageContainerRoot & {
|
|
7
7
|
Inset: typeof PageContainerInset;
|
|
8
8
|
};
|
|
@@ -37,6 +37,7 @@ function PageContainerRoot({
|
|
|
37
37
|
subtitle,
|
|
38
38
|
status,
|
|
39
39
|
extra,
|
|
40
|
+
help,
|
|
40
41
|
toolbar,
|
|
41
42
|
footer,
|
|
42
43
|
toolbarPad,
|
|
@@ -68,7 +69,8 @@ function PageContainerRoot({
|
|
|
68
69
|
const hasHeading = headerLoading || hasTitle || hasIcon || status != null || !!subtitle;
|
|
69
70
|
const hasBreadcrumb = !!breadcrumb && breadcrumb.length > 0;
|
|
70
71
|
const hasEyecatch = isValidElement(cover) && cover.props.variant === "eyecatch";
|
|
71
|
-
const
|
|
72
|
+
const hasHelp = help != null && help !== false;
|
|
73
|
+
const hasHeader = hasHeading || hasBreadcrumb || hasEyecatch || extraStart != null || extraEnd != null || hasHelp;
|
|
72
74
|
const reveal = stickyFooter && footer != null && footerReveal === "onScroll" && hasHeader;
|
|
73
75
|
const { headerRef, revealed } = useFooterReveal(reveal);
|
|
74
76
|
const footerRef = useRef(null);
|
|
@@ -177,9 +179,10 @@ function PageContainerRoot({
|
|
|
177
179
|
)
|
|
178
180
|
) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
|
|
179
181
|
] }),
|
|
180
|
-
(extraStart != null || extraEnd != null) && /* @__PURE__ */ jsxs("div", { className: "ui-page-header-extra", children: [
|
|
182
|
+
(extraStart != null || extraEnd != null || hasHelp) && /* @__PURE__ */ jsxs("div", { className: "ui-page-header-extra", children: [
|
|
181
183
|
extraStart,
|
|
182
|
-
extraEnd
|
|
184
|
+
extraEnd,
|
|
185
|
+
hasHelp ? /* @__PURE__ */ jsx("div", { className: "ui-page-header-help", "data-slot": "page-header-help", children: help }) : null
|
|
183
186
|
] })
|
|
184
187
|
] })
|
|
185
188
|
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { PageErrorBoundaryProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { PageErrorBoundaryFallbackProp, PageErrorBoundaryProp, PageErrorBoundaryProp as PageErrorBoundaryProps, } from "../../props/components/layout.prop.js";
|
|
4
|
+
type BoundaryState = {
|
|
5
|
+
error: unknown;
|
|
6
|
+
failedAt: string | undefined;
|
|
7
|
+
} | null;
|
|
8
|
+
/**
|
|
9
|
+
* PageErrorBoundary — catches a render error thrown by ONE page and draws the failure in its
|
|
10
|
+
* place, so the shell around it (sidebar, topbar, breadcrumb) stays up and the user can go
|
|
11
|
+
* somewhere else (gh#1494). `AppShell` wraps its `children` in one by default.
|
|
12
|
+
*
|
|
13
|
+
* It RESETS ON NAVIGATION without knowing the router: a failed boundary records the URL it failed
|
|
14
|
+
* on, and the next render under a different URL (the router re-renders the shell's children on
|
|
15
|
+
* every visit) draws the page again. `resetKey` is the explicit form, for a change the URL does
|
|
16
|
+
* not show. Without a reset, Back through history stayed on the error surface — the symptom a
|
|
17
|
+
* consumer measured before it wrote its own boundary.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PageErrorBoundary extends React.Component<PageErrorBoundaryProp, {
|
|
20
|
+
failure: BoundaryState;
|
|
21
|
+
}> {
|
|
22
|
+
state: {
|
|
23
|
+
failure: BoundaryState;
|
|
24
|
+
};
|
|
25
|
+
static getDerivedStateFromError(error: unknown): {
|
|
26
|
+
failure: BoundaryState;
|
|
27
|
+
};
|
|
28
|
+
componentDidCatch(error: unknown, info: React.ErrorInfo): void;
|
|
29
|
+
componentDidUpdate(previous: PageErrorBoundaryProp): void;
|
|
30
|
+
reset: () => void;
|
|
31
|
+
render(): React.ReactNode;
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { isDevelopment } from "../../lib/dev.js";
|
|
6
|
+
import { Button } from "../general/button.js";
|
|
7
|
+
import { ErrorSurface } from "./error-surface.js";
|
|
8
|
+
function DefaultPageErrorFallback() {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
ErrorSurface,
|
|
12
|
+
{
|
|
13
|
+
mode: "application",
|
|
14
|
+
status: 500,
|
|
15
|
+
titleLevel: 1,
|
|
16
|
+
title: t("layout.pageErrorBoundary.title"),
|
|
17
|
+
description: t("layout.pageErrorBoundary.description"),
|
|
18
|
+
action: /* @__PURE__ */ jsx(Button, { type: "button", onClick: () => window.location.reload(), children: t("layout.pageErrorBoundary.reload") })
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const currentLocation = () => typeof window === "undefined" ? void 0 : window.location.href;
|
|
23
|
+
class PageErrorBoundary extends React.Component {
|
|
24
|
+
state = { failure: null };
|
|
25
|
+
static getDerivedStateFromError(error) {
|
|
26
|
+
return { failure: { error, failedAt: currentLocation() } };
|
|
27
|
+
}
|
|
28
|
+
componentDidCatch(error, info) {
|
|
29
|
+
if (this.props.onError) {
|
|
30
|
+
this.props.onError(error, info);
|
|
31
|
+
} else if (isDevelopment()) {
|
|
32
|
+
console.error("[@godxjp/ui] PageErrorBoundary caught a page render error:", error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
componentDidUpdate(previous) {
|
|
36
|
+
const { failure } = this.state;
|
|
37
|
+
if (!failure) return;
|
|
38
|
+
const keyChanged = !Object.is(previous.resetKey, this.props.resetKey);
|
|
39
|
+
const moved = failure.failedAt !== void 0 && failure.failedAt !== currentLocation();
|
|
40
|
+
if (keyChanged || moved) this.reset();
|
|
41
|
+
}
|
|
42
|
+
reset = () => {
|
|
43
|
+
this.setState({ failure: null });
|
|
44
|
+
};
|
|
45
|
+
render() {
|
|
46
|
+
const { failure } = this.state;
|
|
47
|
+
if (!failure) return this.props.children;
|
|
48
|
+
const { fallback } = this.props;
|
|
49
|
+
if (typeof fallback === "function")
|
|
50
|
+
return fallback({ error: failure.error, reset: this.reset });
|
|
51
|
+
if (fallback !== void 0) return fallback;
|
|
52
|
+
return /* @__PURE__ */ jsx(DefaultPageErrorFallback, {});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
PageErrorBoundary
|
|
57
|
+
};
|
|
@@ -41,7 +41,7 @@ type RowProps = {
|
|
|
41
41
|
* only within `src/components/layout`) so `NavList` renders nested items through the SAME group as
|
|
42
42
|
* the rail instead of dropping `item.children` on the floor (gh#815).
|
|
43
43
|
*/
|
|
44
|
-
export declare function NavGroup({ item, activeId, onSelect, linkComponent, renderItem }: RowProps): React.JSX.Element;
|
|
44
|
+
export declare function NavGroup({ item, activeId, onSelect, sub, linkComponent, renderItem, }: RowProps): React.JSX.Element;
|
|
45
45
|
export { createSidebarLink } from "./sidebar-link.js";
|
|
46
46
|
/**
|
|
47
47
|
* Sidebar — data-driven vertical nav rail. Use {@link createSidebarLink} to adapt a router `Link`,
|