@godxjp/ui 28.10.0 → 28.13.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.
Files changed (51) hide show
  1. package/agent/START-HERE.md +2 -2
  2. package/agent/components/AppLauncher.json +10 -0
  3. package/agent/components/Attachments.json +27 -1
  4. package/agent/components/BranchScopePicker.json +10 -0
  5. package/agent/components/Cascader.json +5 -0
  6. package/agent/components/Checkbox.json +6 -0
  7. package/agent/components/CredentialReveal.json +21 -0
  8. package/agent/components/InputOTP.json +35 -0
  9. package/agent/components/PermissionMatrix.json +5 -0
  10. package/agent/components/SearchInput.json +5 -0
  11. package/agent/components/ServiceLauncherCard.json +10 -3
  12. package/agent/components/ServiceRolePanel.json +5 -0
  13. package/agent/components/Switch.json +6 -0
  14. package/agent/components/Tabs.json +10 -0
  15. package/agent/components/TimeRangePicker.json +5 -0
  16. package/agent/components/Transfer.json +5 -0
  17. package/agent/components/Upload.json +5 -0
  18. package/agent/components-index.json +1 -1
  19. package/agent/components.json +170 -4
  20. package/agent/index.json +3 -3
  21. package/agent/llms.txt +4 -4
  22. package/agent/tokens.json +30 -25
  23. package/dist/components/data-display/service-launcher-card.d.ts +19 -0
  24. package/dist/components/data-display/service-launcher-card.js +14 -1
  25. package/dist/components/data-entry/attachments.js +77 -33
  26. package/dist/components/ui/avatar.d.ts +1 -18
  27. package/dist/components/ui/avatar.js +1 -36
  28. package/dist/contracts/measurement.json +1 -1
  29. package/dist/i18n/messages/en.json +0 -530
  30. package/dist/i18n/messages/ja.json +0 -524
  31. package/dist/i18n/messages/vi.json +0 -524
  32. package/dist/lib/image-loading-status.d.ts +25 -0
  33. package/dist/lib/image-loading-status.js +41 -0
  34. package/dist/props/registry.d.ts +5 -1
  35. package/dist/props/registry.js +12 -1
  36. package/dist/styles/card-layout.css +6 -0
  37. package/dist/styles/control.css +2 -2
  38. package/dist/styles/data-entry-layout.css +245 -2
  39. package/dist/tokens/components/attachments.css +18 -9
  40. package/dist/tokens/components/segmented.css +2 -2
  41. package/docs/DESIGN-AUTHORITY.md +51 -0
  42. package/docs/assets/service-mark-rose.svg +6 -0
  43. package/docs/assets/service-mark-teal.svg +5 -0
  44. package/docs/data-display/service-launcher-card.tsx +232 -92
  45. package/docs/i18n/messages/en.json +699 -0
  46. package/docs/i18n/messages/ja.json +693 -0
  47. package/docs/i18n/messages/vi.json +693 -0
  48. package/docs/showcase/marketing-page.tsx +3 -2
  49. package/docs/showcase/table-pagination.tsx +18 -7
  50. package/docs/showcase/theme-customization.tsx +2 -1
  51. package/package.json +4 -3
@@ -6,7 +6,6 @@ import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { formatBytes } from "../../lib/format.js";
7
7
  import { cn } from "../../lib/utils.js";
8
8
  import { Button } from "../general/button.js";
9
- import { Text } from "../general/typography.js";
10
9
  import { Progress } from "../data-display/progress.js";
11
10
  import { createUploadItem } from "./upload-types.js";
12
11
  import { readDroppedFiles } from "./upload-files.js";
@@ -30,6 +29,10 @@ function resolvePlaceholder(placeholder, type) {
30
29
  if (!placeholder) return {};
31
30
  return typeof placeholder === "function" ? placeholder(type) : placeholder;
32
31
  }
32
+ function splitFileName(name) {
33
+ const match = /^(.*)\.[^.]+$/.exec(name ?? "");
34
+ return match ? [match[1], (name ?? "").slice(match[1].length)] : [name ?? "", ""];
35
+ }
33
36
  function fileMatchesAccept(file, accept) {
34
37
  if (!accept) return true;
35
38
  return accept.split(",").some((rule) => {
@@ -60,6 +63,7 @@ function PlaceholderUploader({
60
63
  className: cn("ui-attachments-placeholder", className),
61
64
  style,
62
65
  "data-drag-active": dragActive ? "true" : void 0,
66
+ "data-disabled": disabled ? "true" : void 0,
63
67
  "aria-hidden": disabled ? true : void 0,
64
68
  onDragEnter: (event) => {
65
69
  event.preventDefault();
@@ -71,6 +75,7 @@ function PlaceholderUploader({
71
75
  },
72
76
  onDrop: async (event) => {
73
77
  event.preventDefault();
78
+ event.stopPropagation();
74
79
  setDragActive(false);
75
80
  if (disabled) return;
76
81
  const files = await readDroppedFiles(event.dataTransfer, false);
@@ -107,27 +112,55 @@ function AttachmentCard({
107
112
  const { t } = useTranslation();
108
113
  const { disabled } = React.useContext(AttachmentContext);
109
114
  const preview = item.thumbUrl ?? item.url;
115
+ const [namePrefix, nameSuffix] = splitFileName(item.name);
110
116
  const desc = description ?? item.description ?? (item.status === "uploading" ? `${item.percent ?? 0}%` : item.status === "error" ? typeof item.response === "string" ? item.response : t("dataEntry.attachments.error") : item.size ? formatBytes(item.size) : "");
111
- return /* @__PURE__ */ jsxs("li", { className: cn("ui-attachments-card", classNames?.card, classNames?.file), children: [
112
- preview ? /* @__PURE__ */ jsx("img", { src: preview, alt: "", className: "ui-attachments-card-preview", "aria-hidden": "true" }) : /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-placeholder", "aria-hidden": "true", children: /* @__PURE__ */ jsx(FileIcon, {}) }),
113
- (item.status === "uploading" || item.status === "error") && /* @__PURE__ */ jsx("div", { className: "ui-attachments-card-overlay", children: item.status === "uploading" && item.percent != null ? /* @__PURE__ */ jsx(Progress, { value: item.percent, "aria-label": t("dataEntry.attachments.uploading") }) : /* @__PURE__ */ jsx(Text, { size: "xs", children: String(desc) }) }),
114
- desc && item.status !== "uploading" && item.status !== "error" ? /* @__PURE__ */ jsx("div", { className: "ui-attachments-card-meta", children: desc }) : null,
115
- !disabled && onRemove ? /* @__PURE__ */ jsx(
116
- Button,
117
- {
118
- type: "button",
119
- variant: "ghost",
120
- size: "icon-xs",
121
- className: "ui-attachments-card-remove",
122
- "aria-label": t("dataEntry.attachments.remove", { name: item.name }),
123
- onClick: (event) => {
124
- event.stopPropagation();
125
- onRemove();
126
- },
127
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
128
- }
129
- ) : null
130
- ] });
117
+ return /* @__PURE__ */ jsxs(
118
+ "li",
119
+ {
120
+ className: cn("ui-attachments-card", classNames?.card, classNames?.file),
121
+ "data-status": item.status,
122
+ children: [
123
+ preview ? /* @__PURE__ */ jsx("img", { src: preview, alt: "", className: "ui-attachments-card-preview", "aria-hidden": "true" }) : /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-placeholder", "aria-hidden": "true", children: /* @__PURE__ */ jsx(FileIcon, {}) }),
124
+ /* @__PURE__ */ jsxs("span", { className: "ui-attachments-card-content", children: [
125
+ /* @__PURE__ */ jsxs("span", { className: "ui-attachments-card-name", title: item.name, children: [
126
+ /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-name-prefix", children: namePrefix }),
127
+ /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-name-suffix", children: nameSuffix })
128
+ ] }),
129
+ desc ? /* @__PURE__ */ jsx(
130
+ "span",
131
+ {
132
+ className: "ui-attachments-card-meta",
133
+ title: typeof desc === "string" ? desc : void 0,
134
+ children: desc
135
+ }
136
+ ) : null
137
+ ] }),
138
+ item.status === "uploading" && item.percent != null ? /* @__PURE__ */ jsx("span", { className: "ui-attachments-card-progress", children: /* @__PURE__ */ jsx(
139
+ Progress,
140
+ {
141
+ size: "sm",
142
+ value: item.percent,
143
+ "aria-label": t("dataEntry.attachments.uploading")
144
+ }
145
+ ) }) : null,
146
+ !disabled && onRemove ? /* @__PURE__ */ jsx(
147
+ Button,
148
+ {
149
+ type: "button",
150
+ variant: "ghost",
151
+ size: "icon-xs",
152
+ className: "ui-attachments-card-remove",
153
+ "aria-label": t("dataEntry.attachments.remove", { name: item.name }),
154
+ onClick: (event) => {
155
+ event.stopPropagation();
156
+ onRemove();
157
+ },
158
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
159
+ }
160
+ ) : null
161
+ ]
162
+ }
163
+ );
131
164
  }
132
165
  const Attachments = React.forwardRef(
133
166
  ({
@@ -151,6 +184,7 @@ const Attachments = React.forwardRef(
151
184
  const containerRef = React.useRef(null);
152
185
  const inputRef = React.useRef(null);
153
186
  const [internalItems, setInternalItems] = React.useState([]);
187
+ const [dropActive, setDropActive] = React.useState(false);
154
188
  const items = controlledItems ?? internalItems;
155
189
  const setItems = React.useCallback(
156
190
  (next, file) => {
@@ -207,7 +241,7 @@ const Attachments = React.forwardRef(
207
241
  {
208
242
  ref: inputRef,
209
243
  type: "file",
210
- className: "ui-attachments-input",
244
+ className: cn("ui-attachments-input", "sr-only"),
211
245
  accept,
212
246
  multiple: multiple ?? (maxCount == null || maxCount > 1),
213
247
  disabled,
@@ -244,7 +278,7 @@ const Attachments = React.forwardRef(
244
278
  )
245
279
  ] });
246
280
  }
247
- const dropHost = getDropContainer?.() ?? containerRef.current;
281
+ const dropScope = getDropContainer?.() ? "viewport" : "control";
248
282
  return /* @__PURE__ */ jsx(AttachmentContext.Provider, { value: { disabled }, children: /* @__PURE__ */ jsxs(
249
283
  "div",
250
284
  {
@@ -252,6 +286,21 @@ const Attachments = React.forwardRef(
252
286
  className: cn("ui-attachments", className, rootClassName, classNames.root),
253
287
  style: styles.root,
254
288
  dir: "auto",
289
+ onDragEnter: (event) => {
290
+ event.preventDefault();
291
+ if (!disabled) setDropActive(true);
292
+ },
293
+ onDragOver: (event) => event.preventDefault(),
294
+ onDragLeave: (event) => {
295
+ if (!event.currentTarget.contains(event.relatedTarget)) setDropActive(false);
296
+ },
297
+ onDrop: async (event) => {
298
+ event.preventDefault();
299
+ setDropActive(false);
300
+ if (disabled) return;
301
+ const files = await readDroppedFiles(event.dataTransfer, false);
302
+ if (files.length) dispatchFiles(files);
303
+ },
255
304
  children: [
256
305
  fileInput,
257
306
  hasFiles ? /* @__PURE__ */ jsxs(
@@ -297,20 +346,15 @@ const Attachments = React.forwardRef(
297
346
  hidden: hasFiles
298
347
  }
299
348
  ),
300
- dropHost ? /* @__PURE__ */ jsx(
349
+ /* @__PURE__ */ jsx(
301
350
  "div",
302
351
  {
303
352
  className: "ui-attachments-drop-layer",
304
- "data-active": void 0,
305
- "aria-hidden": "true",
306
- onDragOver: (event) => event.preventDefault(),
307
- onDrop: async (event) => {
308
- event.preventDefault();
309
- const files = await readDroppedFiles(event.dataTransfer, false);
310
- dispatchFiles(files);
311
- }
353
+ "data-scope": dropScope,
354
+ "data-active": dropActive ? "true" : void 0,
355
+ "aria-hidden": "true"
312
356
  }
313
- ) : null
357
+ )
314
358
  ]
315
359
  }
316
360
  ) });
@@ -1,22 +1,6 @@
1
1
  import * as React from "react";
2
+ import { type ImageLoadingStatus } from "../../lib/image-loading-status.js";
2
3
  import type { AvatarProp } from "../../props/components/data-display.prop.js";
3
- /**
4
- * Trạng thái tải của ảnh trong avatar — chia sẻ giữa `AvatarImage` và
5
- * `AvatarFallback` qua context, đúng như Radix làm.
6
- *
7
- * React Aria KHÔNG có avatar, nên đây là việc TỰ DỰNG. Nhưng phần cần dựng lại
8
- * không phải cái vỏ `<span>` — mà là logic thật mà `@radix-ui/react-avatar` cầm:
9
- * ảnh chỉ được vẽ khi đã tải XONG, fallback chỉ được vẽ khi ảnh CHƯA xong hoặc
10
- * HỎNG, và `delayMs` giữ fallback im lặng đủ lâu để mạng nhanh không kịp nháy
11
- * một khung initials trước khi ảnh về.
12
- *
13
- * Phép dò là một `new Image()` RỜI, không phải `onLoad`/`onError` trên chính
14
- * thẻ được vẽ: thẻ chỉ tồn tại khi đã `loaded`, nên handler trên nó không bao
15
- * giờ có cơ hội báo "đang tải" hay "hỏng". Đây cũng là lý do một `<img>` hỏng
16
- * không bao giờ nằm trong DOM để trình duyệt vẽ biểu tượng ảnh vỡ lên trên
17
- * fallback.
18
- */
19
- type ImageLoadingStatus = "idle" | "loading" | "loaded" | "error";
20
4
  export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
21
5
  asChild?: boolean;
22
6
  } & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
@@ -28,4 +12,3 @@ export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<React.
28
12
  asChild?: boolean;
29
13
  delayMs?: number;
30
14
  } & React.RefAttributes<HTMLSpanElement>>;
31
- export {};
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffectEvent, useLayoutEffect } from "@react-aria/utils";
4
4
  import * as React from "react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { useImageLoadingStatus } from "../../lib/image-loading-status.js";
6
7
  import { Slot } from "../../lib/slot.js";
7
8
  import { cn } from "../../lib/utils.js";
8
9
  const AvatarContext = React.createContext(null);
@@ -13,42 +14,6 @@ function useAvatarContext(part) {
13
14
  }
14
15
  return context;
15
16
  }
16
- function getImageLoadingStatus(image) {
17
- if (!image.complete) {
18
- return "loading";
19
- }
20
- return image.naturalWidth > 0 ? "loaded" : "error";
21
- }
22
- function useImageLoadingStatus(src, {
23
- loadingStatus,
24
- setLoadingStatus,
25
- referrerPolicy,
26
- crossOrigin
27
- }) {
28
- useLayoutEffect(() => {
29
- if (!src) {
30
- setLoadingStatus("error");
31
- return;
32
- }
33
- const image = new window.Image();
34
- const handleLoad = (event) => setLoadingStatus(getImageLoadingStatus(event.currentTarget));
35
- const handleError = () => setLoadingStatus("error");
36
- image.addEventListener("load", handleLoad);
37
- image.addEventListener("error", handleError);
38
- if (referrerPolicy) {
39
- image.referrerPolicy = referrerPolicy;
40
- }
41
- image.crossOrigin = crossOrigin ?? null;
42
- image.src = src;
43
- setLoadingStatus(getImageLoadingStatus(image));
44
- return () => {
45
- image.removeEventListener("load", handleLoad);
46
- image.removeEventListener("error", handleError);
47
- setLoadingStatus("idle");
48
- };
49
- }, [src, crossOrigin, referrerPolicy, setLoadingStatus]);
50
- return loadingStatus;
51
- }
52
17
  const Avatar = React.forwardRef(
53
18
  ({
54
19
  className,
@@ -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": "28.10.0",
3
+ "version": "28.13.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,