@godxjp/ui 32.4.0 → 32.4.2
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/agent/START-HERE.md +1 -1
- package/agent/components/AlertDialog.json +1 -1
- package/agent/components/AppProvider.json +1 -1
- package/agent/components.json +2 -2
- package/agent/index.json +2 -2
- package/agent/llms.txt +3 -3
- package/dist/app/app-provider.js +11 -0
- package/dist/app/storage.d.ts +6 -0
- package/dist/app/storage.js +2 -1
- package/dist/components/data-entry/select.js +7 -1
- package/dist/components/data-entry/upload.js +29 -13
- package/dist/components/feedback/dialog.js +12 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +6 -1
- package/dist/i18n/messages/ja.json +6 -1
- package/dist/i18n/messages/vi.json +8 -3
- package/dist/styles/layers.json +1 -1
- package/dist/themes/godx.d.ts +4 -0
- package/dist/themes/godx.js +0 -1
- package/package.json +2 -2
package/agent/START-HERE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are about to write code against a design system you did not author. This file is the whole
|
|
4
4
|
contract. Read it before you write JSX.
|
|
5
5
|
|
|
6
|
-
**This catalog describes `@godxjp/ui` 32.4.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 32.4.2.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"type": "string"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
"description": "Dismiss action label
|
|
34
|
+
"description": "Dismiss action label. Defaults to `feedback.alert.cancel` — en 'Cancel', ja 'キャンセル', vi 'Quay lại' (not 'Hủy': in Vietnamese that is also the verb of the destructive actions a confirm guards, gh#1284). A development warning fires when `confirmLabel` and the cancel label are the same word once tone marks are ignored.",
|
|
35
35
|
"name": "cancelLabel",
|
|
36
36
|
"type": "string"
|
|
37
37
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "string (BCP-47)"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
|
-
"description": "Product defaults the host opts into — `import { godxPreset } from \"@godxjp/ui/themes/godx\"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale`
|
|
13
|
+
"description": "Product defaults the host opts into — `import { godxPreset } from \"@godxjp/ui/themes/godx\"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale` fills only what the props leave unset — it pins NO `timeZone` since 32.4.1 (gh#1282: it pinned `Asia/Ho_Chi_Minh` and every app showed Vietnam time to users in Japan; pass `defaultTimezone` for a fixed zone), its `brandMark` is what an `AuthIdentity` with no `brand` draws, and its `name` is written to `<html data-preset>`. Omit for the neutral defaults (ink primary, `en`, no product mark).",
|
|
14
14
|
"name": "preset",
|
|
15
15
|
"type": "AppPreset"
|
|
16
16
|
},
|
package/agent/components.json
CHANGED
|
@@ -7664,7 +7664,7 @@
|
|
|
7664
7664
|
"type": "string"
|
|
7665
7665
|
},
|
|
7666
7666
|
{
|
|
7667
|
-
"description": "Dismiss action label
|
|
7667
|
+
"description": "Dismiss action label. Defaults to `feedback.alert.cancel` — en 'Cancel', ja 'キャンセル', vi 'Quay lại' (not 'Hủy': in Vietnamese that is also the verb of the destructive actions a confirm guards, gh#1284). A development warning fires when `confirmLabel` and the cancel label are the same word once tone marks are ignored.",
|
|
7668
7668
|
"name": "cancelLabel",
|
|
7669
7669
|
"type": "string"
|
|
7670
7670
|
},
|
|
@@ -8641,7 +8641,7 @@
|
|
|
8641
8641
|
"type": "string (BCP-47)"
|
|
8642
8642
|
},
|
|
8643
8643
|
{
|
|
8644
|
-
"description": "Product defaults the host opts into — `import { godxPreset } from \"@godxjp/ui/themes/godx\"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale`
|
|
8644
|
+
"description": "Product defaults the host opts into — `import { godxPreset } from \"@godxjp/ui/themes/godx\"` (pair it with `@godxjp/ui/themes/godx.css` after the base stylesheet; a shadow root that embeds `@godxjp/ui/styles/core` takes the font-free `@godxjp/ui/themes/godx-tokens.css` instead, since `@font-face` must live in the document). Its `defaultLocale` fills only what the props leave unset — it pins NO `timeZone` since 32.4.1 (gh#1282: it pinned `Asia/Ho_Chi_Minh` and every app showed Vietnam time to users in Japan; pass `defaultTimezone` for a fixed zone), its `brandMark` is what an `AuthIdentity` with no `brand` draws, and its `name` is written to `<html data-preset>`. Omit for the neutral defaults (ink primary, `en`, no product mark).",
|
|
8645
8645
|
"name": "preset",
|
|
8646
8646
|
"type": "AppPreset"
|
|
8647
8647
|
},
|
package/agent/index.json
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
|
|
49
49
|
"read": {
|
|
50
50
|
"live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
|
|
51
|
-
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.4.
|
|
51
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.4.2/agent/index.json"
|
|
52
52
|
},
|
|
53
53
|
"source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
|
|
54
54
|
"start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
|
|
63
63
|
"semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
|
|
64
64
|
},
|
|
65
|
-
"version": "32.4.
|
|
65
|
+
"version": "32.4.2"
|
|
66
66
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
3
|
> A Japanese-enterprise React design system: 161 components, 2195 design tokens,
|
|
4
|
-
> 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.4.
|
|
4
|
+
> 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.4.2.
|
|
5
5
|
|
|
6
6
|
If your client can run a process, do not read these files — run the MCP server instead
|
|
7
|
-
(`npx @godxjp/ui-mcp@32.4.
|
|
7
|
+
(`npx @godxjp/ui-mcp@32.4.2`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -26,7 +26,7 @@ that can only fetch URLs.
|
|
|
26
26
|
## Pinning
|
|
27
27
|
|
|
28
28
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
29
|
-
the tag: `.../godx-jp/godxjp-ui/v32.4.
|
|
29
|
+
the tag: `.../godx-jp/godxjp-ui/v32.4.2/agent/...`. A catalog that does not match the installed
|
|
30
30
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
31
31
|
|
|
32
32
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
package/dist/app/app-provider.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
APP_REQUEST_HEADER_TIME_FORMAT,
|
|
44
44
|
APP_REQUEST_HEADER_TIMEZONE
|
|
45
45
|
} from "./types.js";
|
|
46
|
+
const LEGACY_GODX_PRESET_TIMEZONE = "Asia/Ho_Chi_Minh";
|
|
46
47
|
function resolveProviderLocale(defaultLocale, presetLocale) {
|
|
47
48
|
const fromProp = resolveRegisteredLocale(defaultLocale);
|
|
48
49
|
if (fromProp) return fromProp;
|
|
@@ -138,6 +139,7 @@ function AppProvider({
|
|
|
138
139
|
[persistKey]
|
|
139
140
|
);
|
|
140
141
|
const formatChosenRef = React.useRef({ dateFormat: false, timeFormat: false });
|
|
142
|
+
const timezoneChosenRef = React.useRef(false);
|
|
141
143
|
const commitPreferences = React.useCallback(() => {
|
|
142
144
|
if (persistedAxes.size === 0) return;
|
|
143
145
|
const out = pickPersistedAxes(prefsRef.current, persistedAxes);
|
|
@@ -149,6 +151,10 @@ function AppProvider({
|
|
|
149
151
|
if (formatChosenRef.current.timeFormat) out.timeFormatChosen = true;
|
|
150
152
|
else delete out.timeFormat;
|
|
151
153
|
}
|
|
154
|
+
if (out.timezone !== void 0) {
|
|
155
|
+
if (timezoneChosenRef.current) out.timezoneChosen = true;
|
|
156
|
+
else delete out.timezone;
|
|
157
|
+
}
|
|
152
158
|
writeStoredPreferences(storageKey, out);
|
|
153
159
|
}, [persistedAxes, storageKey]);
|
|
154
160
|
React.useEffect(() => {
|
|
@@ -166,6 +172,10 @@ function AppProvider({
|
|
|
166
172
|
);
|
|
167
173
|
if (derivable) delete stored.timeFormat;
|
|
168
174
|
}
|
|
175
|
+
if (!raw.timezoneChosen && stored.timezone === LEGACY_GODX_PRESET_TIMEZONE) {
|
|
176
|
+
delete stored.timezone;
|
|
177
|
+
}
|
|
178
|
+
timezoneChosenRef.current = stored.timezone !== void 0;
|
|
169
179
|
formatChosenRef.current = {
|
|
170
180
|
dateFormat: stored.dateFormat !== void 0,
|
|
171
181
|
timeFormat: stored.timeFormat !== void 0
|
|
@@ -273,6 +283,7 @@ function AppProvider({
|
|
|
273
283
|
const setTimezone = React.useCallback(
|
|
274
284
|
(next) => {
|
|
275
285
|
prefsRef.current = { ...prefsRef.current, timezone: next };
|
|
286
|
+
timezoneChosenRef.current = true;
|
|
276
287
|
setTimezoneState(next);
|
|
277
288
|
onTimezoneChange?.(next);
|
|
278
289
|
commitPreferences();
|
package/dist/app/storage.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export type StoredAppPreferences = {
|
|
|
20
20
|
*/
|
|
21
21
|
dateFormatChosen?: boolean;
|
|
22
22
|
timeFormatChosen?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The viewer PICKED the stored timezone (setTimezone), as opposed to it being the default. Only a
|
|
25
|
+
* picked zone is stored (gh#1282): storing the default froze it, which is how the godx preset's
|
|
26
|
+
* pinned `Asia/Ho_Chi_Minh` outlived the preset change.
|
|
27
|
+
*/
|
|
28
|
+
timezoneChosen?: boolean;
|
|
23
29
|
};
|
|
24
30
|
/**
|
|
25
31
|
* The axes a viewer's preferences are made of — the keys of {@link StoredAppPreferences}.
|
package/dist/app/storage.js
CHANGED
|
@@ -50,7 +50,8 @@ function readStoredPreferences(storageKey) {
|
|
|
50
50
|
fontSize: isAppFontSize(parsed.fontSize) ? parsed.fontSize : void 0,
|
|
51
51
|
scaling: typeof parsed.scaling === "number" && Number.isFinite(parsed.scaling) ? parsed.scaling : void 0,
|
|
52
52
|
dateFormatChosen: parsed.dateFormatChosen === true ? true : void 0,
|
|
53
|
-
timeFormatChosen: parsed.timeFormatChosen === true ? true : void 0
|
|
53
|
+
timeFormatChosen: parsed.timeFormatChosen === true ? true : void 0,
|
|
54
|
+
timezoneChosen: parsed.timezoneChosen === true ? true : void 0
|
|
54
55
|
};
|
|
55
56
|
} catch {
|
|
56
57
|
return {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { useLayoutEffect } from "@react-aria/utils";
|
|
4
5
|
import {
|
|
5
6
|
Button as AriaButton,
|
|
6
7
|
ButtonContext,
|
|
@@ -631,6 +632,11 @@ function DataSelect(props) {
|
|
|
631
632
|
"aria-label": props["aria-label"],
|
|
632
633
|
"aria-labelledby": props["aria-labelledby"]
|
|
633
634
|
});
|
|
635
|
+
const [hasNativeLabel, setHasNativeLabel] = React.useState(false);
|
|
636
|
+
useLayoutEffect(() => {
|
|
637
|
+
const labelled = typeof document !== "undefined" && props.id !== void 0 && document.querySelector(`label[for="${CSS.escape(props.id)}"]`) !== null;
|
|
638
|
+
setHasNativeLabel(labelled);
|
|
639
|
+
}, [props.id]);
|
|
634
640
|
if (props.mode !== void 0 || searchable) {
|
|
635
641
|
const antdFilter = showSearchConfig?.filterOption;
|
|
636
642
|
const filterOption = antdFilter === void 0 ? props.filterOption : antdFilter === false ? () => true : antdFilter === true ? void 0 : (option, query) => antdFilter(query, option);
|
|
@@ -696,7 +702,7 @@ function DataSelect(props) {
|
|
|
696
702
|
Object.entries(rest).filter(([key]) => key.startsWith("aria-"))
|
|
697
703
|
);
|
|
698
704
|
const placeholderName = typeof placeholder === "string" ? placeholder : void 0;
|
|
699
|
-
const nameFromPlaceholder = ariaProps["aria-label"] === void 0 && ariaProps["aria-labelledby"] === void 0 && inheritedName["aria-label"] === void 0 && inheritedName["aria-labelledby"] === void 0 ? placeholderName : void 0;
|
|
705
|
+
const nameFromPlaceholder = ariaProps["aria-label"] === void 0 && ariaProps["aria-labelledby"] === void 0 && inheritedName["aria-label"] === void 0 && inheritedName["aria-labelledby"] === void 0 && !hasNativeLabel ? placeholderName : void 0;
|
|
700
706
|
const optionTestId = (optionValue) => dataTestId ? `${dataTestId}-option-${optionValue}` : void 0;
|
|
701
707
|
let flatIndex = 0;
|
|
702
708
|
const renderItem = (option) => {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
15
15
|
import { formatBytes } from "../../lib/format.js";
|
|
16
16
|
import { cn } from "../../lib/utils.js";
|
|
17
|
-
import { mergeAriaIds, pickFieldA11y } from "../../lib/field-a11y.js";
|
|
17
|
+
import { FieldNameContext, mergeAriaIds, pickFieldA11y } from "../../lib/field-a11y.js";
|
|
18
18
|
import { Button } from "../general/button.js";
|
|
19
19
|
import { Icon } from "../general/icon.js";
|
|
20
20
|
import { UploadCropDialog } from "./upload-crop-dialog.js";
|
|
@@ -41,6 +41,13 @@ function defaultMaxCount(variant) {
|
|
|
41
41
|
if (variant === "avatar" || variant === "avatar-crop" || variant === "picture") return 1;
|
|
42
42
|
return void 0;
|
|
43
43
|
}
|
|
44
|
+
const SINGLE_STATUS_KEY = {
|
|
45
|
+
idle: "dataEntry.upload.fileSelected",
|
|
46
|
+
uploading: "dataEntry.upload.fileUploading",
|
|
47
|
+
done: "dataEntry.upload.fileUploaded",
|
|
48
|
+
error: "dataEntry.upload.fileFailed",
|
|
49
|
+
removed: null
|
|
50
|
+
};
|
|
44
51
|
function fileMatchesAccept(file, accept) {
|
|
45
52
|
if (!accept) return true;
|
|
46
53
|
return accept.split(",").some((rule) => {
|
|
@@ -109,10 +116,12 @@ function Upload({
|
|
|
109
116
|
const disabled = disabledProp || readOnly;
|
|
110
117
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
111
118
|
const fieldLabelledBy = fieldA11y["aria-labelledby"];
|
|
119
|
+
const fieldName = React.useContext(FieldNameContext);
|
|
112
120
|
const triggerId = React.useId();
|
|
113
121
|
const selectionId = React.useId();
|
|
114
122
|
const triggerRef = React.useRef(null);
|
|
115
|
-
const
|
|
123
|
+
const ownAriaLabel = fieldA11y["aria-label"];
|
|
124
|
+
const callerAriaLabel = !fieldLabelledBy || fieldName && ownAriaLabel !== fieldName.label ? ownAriaLabel : void 0;
|
|
116
125
|
const setTriggerRef = (element) => {
|
|
117
126
|
triggerRef.current = element;
|
|
118
127
|
};
|
|
@@ -126,9 +135,9 @@ function Upload({
|
|
|
126
135
|
const [previewItem, setPreviewItem] = React.useState(null);
|
|
127
136
|
const [items, setItems] = useUploadList(value, defaultValue, onValueChange);
|
|
128
137
|
const triggerIsGroup = variant === "dropzone" && !openFileDialogOnClick;
|
|
129
|
-
const triggerA11y = {
|
|
138
|
+
const triggerA11y = (action2) => ({
|
|
130
139
|
id: triggerId,
|
|
131
|
-
"aria-labelledby": fieldLabelledBy ? `${fieldLabelledBy} ${triggerId}
|
|
140
|
+
"aria-labelledby": !fieldLabelledBy || callerAriaLabel !== void 0 || fieldName?.label && action2?.toLocaleLowerCase().includes(fieldName.label.trim().toLocaleLowerCase()) ? void 0 : `${fieldLabelledBy} ${triggerId}`,
|
|
132
141
|
"aria-describedby": mergeAriaIds(
|
|
133
142
|
fieldA11y["aria-describedby"],
|
|
134
143
|
triggerIsGroup && fieldA11y["aria-invalid"] ? fieldA11y["aria-errormessage"] : void 0,
|
|
@@ -136,7 +145,7 @@ function Upload({
|
|
|
136
145
|
),
|
|
137
146
|
"aria-errormessage": triggerIsGroup ? void 0 : fieldA11y["aria-errormessage"],
|
|
138
147
|
"aria-invalid": triggerIsGroup ? void 0 : fieldA11y["aria-invalid"]
|
|
139
|
-
};
|
|
148
|
+
});
|
|
140
149
|
const isSingleAvatar = variant === "avatar" || variant === "avatar-crop" || variant === "picture" && maxCount === 1;
|
|
141
150
|
const singleItem = isSingleAvatar ? items[0] ?? null : null;
|
|
142
151
|
const draft = useUploadDraft({
|
|
@@ -435,6 +444,7 @@ function Upload({
|
|
|
435
444
|
const uploadingCount = items.filter((it) => it.status === "uploading").length;
|
|
436
445
|
const doneCount = items.filter((it) => it.status === "done").length;
|
|
437
446
|
const errorCount = items.filter((it) => it.status === "error").length;
|
|
447
|
+
const singleFile = !multiple || maxCount === 1;
|
|
438
448
|
const liveRegion = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
439
449
|
/* @__PURE__ */ jsx(
|
|
440
450
|
Dialog,
|
|
@@ -456,7 +466,10 @@ function Upload({
|
|
|
456
466
|
] })
|
|
457
467
|
}
|
|
458
468
|
),
|
|
459
|
-
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "upload-status", children:
|
|
469
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "upload-status", children: singleFile && items[0] ? (() => {
|
|
470
|
+
const key = SINGLE_STATUS_KEY[items[0].status];
|
|
471
|
+
return key ? t(key, { name: items[0].name }) : "";
|
|
472
|
+
})() : errorCount > 0 ? t("dataEntry.upload.statusFailed", { count: errorCount }) : uploadingCount > 0 ? t("dataEntry.upload.statusUploading", { count: uploadingCount }) : items.length > 0 ? t("dataEntry.upload.statusReady", { count: items.length, done: doneCount }) : "" }),
|
|
460
473
|
items.length === 0 ? /* @__PURE__ */ jsx("span", { id: selectionId, className: "sr-only", "data-slot": "upload-selection", children: t("dataEntry.upload.noFileSelected") }) : null
|
|
461
474
|
] });
|
|
462
475
|
const openPicker = () => {
|
|
@@ -474,7 +487,7 @@ function Upload({
|
|
|
474
487
|
role: openFileDialogOnClick ? "button" : "group",
|
|
475
488
|
tabIndex: disabled ? -1 : 0,
|
|
476
489
|
"aria-disabled": disabled,
|
|
477
|
-
...triggerA11y,
|
|
490
|
+
...triggerA11y(t("dataEntry.upload.dropzoneLabel")),
|
|
478
491
|
"aria-label": callerAriaLabel ?? t("dataEntry.upload.dropzoneLabel"),
|
|
479
492
|
onClick: openPicker,
|
|
480
493
|
onKeyDown: (e) => {
|
|
@@ -527,7 +540,9 @@ function Upload({
|
|
|
527
540
|
disabled,
|
|
528
541
|
onClick: openPicker,
|
|
529
542
|
ref: setTriggerRef,
|
|
530
|
-
...triggerA11y
|
|
543
|
+
...triggerA11y(
|
|
544
|
+
iconOnly ? triggerAriaLabel : typeof label === "string" ? label : void 0
|
|
545
|
+
),
|
|
531
546
|
"aria-label": triggerAriaLabel,
|
|
532
547
|
className: "ui-upload-trigger",
|
|
533
548
|
children: [
|
|
@@ -573,7 +588,7 @@ function Upload({
|
|
|
573
588
|
"data-disabled": disabled ? "" : void 0,
|
|
574
589
|
className: "ui-upload-tile-add",
|
|
575
590
|
ref: setTriggerRef,
|
|
576
|
-
...triggerA11y,
|
|
591
|
+
...triggerA11y(t("dataEntry.upload.addImage")),
|
|
577
592
|
"aria-label": callerAriaLabel ?? t("dataEntry.upload.addImage"),
|
|
578
593
|
children: [
|
|
579
594
|
/* @__PURE__ */ jsx(ImagePlus, { className: "ui-upload-tile-add-icon", "aria-hidden": "true" }),
|
|
@@ -584,6 +599,7 @@ function Upload({
|
|
|
584
599
|
] });
|
|
585
600
|
}
|
|
586
601
|
if (variant === "picture" && !isSingleAvatar) {
|
|
602
|
+
const label = children ?? t("dataEntry.upload.addImage");
|
|
587
603
|
return /* @__PURE__ */ jsxs("div", { ...pasteProps, className: cn("ui-stack-sm", className), children: [
|
|
588
604
|
hiddenInput,
|
|
589
605
|
liveRegion,
|
|
@@ -596,10 +612,10 @@ function Upload({
|
|
|
596
612
|
variant: triggerVariant,
|
|
597
613
|
onClick: openPicker,
|
|
598
614
|
ref: setTriggerRef,
|
|
599
|
-
...triggerA11y,
|
|
615
|
+
...triggerA11y(typeof label === "string" ? label : void 0),
|
|
600
616
|
"aria-label": callerAriaLabel,
|
|
601
617
|
className: "ui-upload-trigger",
|
|
602
|
-
children:
|
|
618
|
+
children: label
|
|
603
619
|
}
|
|
604
620
|
),
|
|
605
621
|
list
|
|
@@ -622,7 +638,7 @@ function Upload({
|
|
|
622
638
|
disabled,
|
|
623
639
|
onClick: openPicker,
|
|
624
640
|
ref: setTriggerRef,
|
|
625
|
-
...triggerA11y,
|
|
641
|
+
...triggerA11y(t("dataEntry.upload.addImage")),
|
|
626
642
|
"aria-label": callerAriaLabel,
|
|
627
643
|
className: "ui-upload-picture-empty",
|
|
628
644
|
children: [
|
|
@@ -675,7 +691,7 @@ function Upload({
|
|
|
675
691
|
"data-disabled": disabled ? "" : void 0,
|
|
676
692
|
className: "ui-upload-avatar ui-focus-ring",
|
|
677
693
|
ref: setTriggerRef,
|
|
678
|
-
...triggerA11y,
|
|
694
|
+
...triggerA11y(t("dataEntry.upload.avatarLabel")),
|
|
679
695
|
"aria-label": callerAriaLabel ?? t("dataEntry.upload.avatarLabel"),
|
|
680
696
|
children: [
|
|
681
697
|
!showPlaceholder && item?.previewUrl ? /* @__PURE__ */ jsx("img", { src: item.previewUrl, alt: "", className: "ui-upload-avatar-image" }) : /* @__PURE__ */ jsx("span", { className: "ui-upload-avatar-placeholder", children: /* @__PURE__ */ jsx(Camera, { className: "ui-upload-avatar-icon", "aria-hidden": "true" }) }),
|
|
@@ -404,6 +404,11 @@ function DialogCancel({ asChild, className, onClick, ...props }) {
|
|
|
404
404
|
DialogCancel.displayName = "DialogCancel";
|
|
405
405
|
const AlertDialogAction = DialogAction;
|
|
406
406
|
const AlertDialogCancel = DialogCancel;
|
|
407
|
+
function sameWordIgnoringMarks(a, b) {
|
|
408
|
+
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
409
|
+
const base = (text) => text.normalize("NFD").replace(/\p{M}/gu, "").replace(/đ/g, "d").replace(/Đ/g, "D").trim().toLowerCase();
|
|
410
|
+
return base(a) === base(b);
|
|
411
|
+
}
|
|
407
412
|
function AlertDialog({
|
|
408
413
|
open,
|
|
409
414
|
onOpenChange,
|
|
@@ -431,7 +436,13 @@ function AlertDialog({
|
|
|
431
436
|
const effectiveVariant = needsPhrase ? "destructive" : variant;
|
|
432
437
|
const showDangerIcon = effectiveVariant === "destructive";
|
|
433
438
|
const resolvedConfirm = confirmLabel ?? (needsPhrase ? t("common.delete") : t("common.continue"));
|
|
434
|
-
const resolvedCancel = cancelLabel ?? t("
|
|
439
|
+
const resolvedCancel = cancelLabel ?? t("feedback.alert.cancel");
|
|
440
|
+
React.useEffect(() => {
|
|
441
|
+
if (!isDevelopment() || !sameWordIgnoringMarks(resolvedConfirm, resolvedCancel)) return;
|
|
442
|
+
console.warn(
|
|
443
|
+
`[@godxjp/ui] AlertDialog: confirmLabel "${String(resolvedConfirm)}" and the cancel label "${String(resolvedCancel)}" are the same word once tone marks are ignored \u2014 a reader tells them apart by colour alone and a screen reader not at all. Rename one (gh#1284).`
|
|
444
|
+
);
|
|
445
|
+
}, [resolvedConfirm, resolvedCancel]);
|
|
435
446
|
const busy = pending || verifying;
|
|
436
447
|
const confirmLabelText = verifying ? t("feedback.alert.verifying") : pending ? t("common.working") : resolvedConfirm;
|
|
437
448
|
const reset = () => {
|
|
@@ -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": "32.4.
|
|
3
|
+
"version": "32.4.2",
|
|
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,
|
|
@@ -93,6 +93,10 @@
|
|
|
93
93
|
"statusUploading": "Uploading {count} file(s)…",
|
|
94
94
|
"statusReady": "{count} file(s), {done} uploaded",
|
|
95
95
|
"statusFailed": "{count} upload(s) failed",
|
|
96
|
+
"fileSelected": "{name} selected",
|
|
97
|
+
"fileUploading": "Uploading {name}…",
|
|
98
|
+
"fileUploaded": "{name} uploaded",
|
|
99
|
+
"fileFailed": "{name} failed to upload",
|
|
96
100
|
"removeImage": "Remove image",
|
|
97
101
|
"removeFile": "Remove {name}",
|
|
98
102
|
"cropDescription": "Drag the zoom slider, then save to crop your photo to a square.",
|
|
@@ -284,7 +288,8 @@
|
|
|
284
288
|
"alert": {
|
|
285
289
|
"dismiss": "Dismiss",
|
|
286
290
|
"verifying": "Verifying…",
|
|
287
|
-
"stepUpFailed": "Re-authentication failed. Please try again."
|
|
291
|
+
"stepUpFailed": "Re-authentication failed. Please try again.",
|
|
292
|
+
"cancel": "Cancel"
|
|
288
293
|
},
|
|
289
294
|
"toast": {
|
|
290
295
|
"close": "Close notification"
|
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
"statusUploading": "{count} 件をアップロード中…",
|
|
93
93
|
"statusReady": "{count} 件中 {done} 件アップロード済み",
|
|
94
94
|
"statusFailed": "{count} 件のアップロードに失敗",
|
|
95
|
+
"fileSelected": "{name} を選択済み",
|
|
96
|
+
"fileUploading": "{name} をアップロード中…",
|
|
97
|
+
"fileUploaded": "{name} をアップロード済み",
|
|
98
|
+
"fileFailed": "{name} のアップロードに失敗",
|
|
95
99
|
"removeImage": "画像を削除",
|
|
96
100
|
"removeFile": "{name} を削除",
|
|
97
101
|
"cropDescription": "ズームスライダーを調整し、保存すると写真を正方形に切り抜きます。",
|
|
@@ -279,7 +283,8 @@
|
|
|
279
283
|
"alert": {
|
|
280
284
|
"dismiss": "閉じる",
|
|
281
285
|
"verifying": "確認中…",
|
|
282
|
-
"stepUpFailed": "再認証に失敗しました。もう一度お試しください。"
|
|
286
|
+
"stepUpFailed": "再認証に失敗しました。もう一度お試しください。",
|
|
287
|
+
"cancel": "キャンセル"
|
|
283
288
|
},
|
|
284
289
|
"toast": {
|
|
285
290
|
"close": "通知を閉じる"
|
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
"statusUploading": "Đang tải lên {count} tệp…",
|
|
93
93
|
"statusReady": "{count} tệp, đã tải lên {done}",
|
|
94
94
|
"statusFailed": "{count} tệp tải lên thất bại",
|
|
95
|
+
"fileSelected": "Đã chọn {name}",
|
|
96
|
+
"fileUploading": "Đang tải lên {name}…",
|
|
97
|
+
"fileUploaded": "Đã tải lên {name}",
|
|
98
|
+
"fileFailed": "Tải lên {name} thất bại",
|
|
95
99
|
"removeImage": "Xóa ảnh",
|
|
96
100
|
"removeFile": "Xóa {name}",
|
|
97
101
|
"cropDescription": "Kéo thanh thu phóng rồi lưu để cắt ảnh thành hình vuông.",
|
|
@@ -99,7 +103,7 @@
|
|
|
99
103
|
"download": "Tải xuống",
|
|
100
104
|
"retry": "Thử lại",
|
|
101
105
|
"start": "Tải lên",
|
|
102
|
-
"cancel": "
|
|
106
|
+
"cancel": "Hủy",
|
|
103
107
|
"rejectAccept": "{name}: loại tệp không được phép.",
|
|
104
108
|
"rejectSize": "{name}: tệp quá lớn (tối đa {max}).",
|
|
105
109
|
"rejectCount": "{name}: đã đủ số lượng tệp.",
|
|
@@ -216,7 +220,7 @@
|
|
|
216
220
|
"empty": "Không có mục nào khớp",
|
|
217
221
|
"error": "Không tải được",
|
|
218
222
|
"confirm": "Xác nhận",
|
|
219
|
-
"cancel": "
|
|
223
|
+
"cancel": "Hủy",
|
|
220
224
|
"clear": "Bỏ chọn",
|
|
221
225
|
"more": "Tải thêm",
|
|
222
226
|
"openDialog": "Chọn từ danh sách",
|
|
@@ -280,7 +284,8 @@
|
|
|
280
284
|
"alert": {
|
|
281
285
|
"dismiss": "Đóng",
|
|
282
286
|
"verifying": "Đang xác minh…",
|
|
283
|
-
"stepUpFailed": "Xác thực lại thất bại. Vui lòng thử lại."
|
|
287
|
+
"stepUpFailed": "Xác thực lại thất bại. Vui lòng thử lại.",
|
|
288
|
+
"cancel": "Quay lại"
|
|
284
289
|
},
|
|
285
290
|
"toast": {
|
|
286
291
|
"close": "Đóng thông báo"
|
package/dist/styles/layers.json
CHANGED
package/dist/themes/godx.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ import type { AppPreset } from "../app/preset.js";
|
|
|
11
11
|
* A shadow root that embeds `@godxjp/ui/styles/core` adds `@godxjp/ui/themes/godx-tokens.css` (the
|
|
12
12
|
* same colours without the fonts) instead.
|
|
13
13
|
*
|
|
14
|
+
* NO `timeZone` (gh#1282): a preset must not pin a country's zone. 32.x shipped
|
|
15
|
+
* `"Asia/Ho_Chi_Minh"` here, so every godx app showed Vietnam time to its users in Japan; the zone
|
|
16
|
+
* now falls back to the viewer's browser, and an app that needs a fixed one passes `defaultTimezone`.
|
|
17
|
+
*
|
|
14
18
|
* Core never imports this module, which is what keeps the GoDX artwork out of a neutral bundle.
|
|
15
19
|
*/
|
|
16
20
|
export declare const godxPreset: AppPreset;
|
package/dist/themes/godx.js
CHANGED