@godxjp/ui 19.5.0 → 20.0.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 (142) hide show
  1. package/dist/components/data-display/card.d.ts +2 -2
  2. package/dist/components/data-display/collapsible.d.ts +23 -4
  3. package/dist/components/data-display/collapsible.js +119 -4
  4. package/dist/components/data-display/index.d.ts +3 -1
  5. package/dist/components/data-display/index.js +2 -0
  6. package/dist/components/data-display/legend.d.ts +15 -0
  7. package/dist/components/data-display/legend.js +11 -0
  8. package/dist/components/data-display/list-row.js +1 -1
  9. package/dist/components/data-display/popover.d.ts +58 -5
  10. package/dist/components/data-display/popover.js +206 -27
  11. package/dist/components/data-display/progress.d.ts +50 -2
  12. package/dist/components/data-display/progress.js +57 -9
  13. package/dist/components/data-entry/calendar.d.ts +1 -1
  14. package/dist/components/data-entry/calendar.js +67 -32
  15. package/dist/components/data-entry/checkbox.d.ts +19 -6
  16. package/dist/components/data-entry/checkbox.js +55 -16
  17. package/dist/components/data-entry/choice-option.d.ts +1 -1
  18. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  19. package/dist/components/data-entry/field.js +0 -1
  20. package/dist/components/data-entry/form.js +1 -1
  21. package/dist/components/data-entry/label.d.ts +3 -2
  22. package/dist/components/data-entry/label.js +23 -10
  23. package/dist/components/data-entry/month-picker.d.ts +1 -1
  24. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  25. package/dist/components/data-entry/transfer.js +16 -6
  26. package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
  27. package/dist/components/data-entry/tree-select.js +29 -12
  28. package/dist/components/data-entry/tree-utils.d.ts +1 -1
  29. package/dist/components/data-entry/upload.js +7 -2
  30. package/dist/components/feedback/dialog.d.ts +105 -38
  31. package/dist/components/feedback/dialog.js +272 -194
  32. package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
  33. package/dist/components/feedback/overlay-close-focus.js +31 -0
  34. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  35. package/dist/components/feedback/sheet.d.ts +53 -11
  36. package/dist/components/feedback/sheet.js +150 -81
  37. package/dist/components/feedback/tooltip.d.ts +51 -7
  38. package/dist/components/feedback/tooltip.js +107 -25
  39. package/dist/components/general/button.js +2 -2
  40. package/dist/components/general/logo.d.ts +17 -0
  41. package/dist/components/general/logo.js +22 -16
  42. package/dist/components/general/typography.d.ts +7 -1
  43. package/dist/components/general/typography.js +20 -5
  44. package/dist/components/layout/app-shell.d.ts +1 -1
  45. package/dist/components/layout/app-shell.js +52 -8
  46. package/dist/components/layout/aspect-ratio.js +0 -1
  47. package/dist/components/layout/auth-divider.js +0 -1
  48. package/dist/components/layout/flex.d.ts +1 -1
  49. package/dist/components/layout/flex.js +19 -2
  50. package/dist/components/layout/nav-surface.d.ts +26 -0
  51. package/dist/components/layout/nav-surface.js +17 -0
  52. package/dist/components/layout/org-switcher.d.ts +5 -1
  53. package/dist/components/layout/org-switcher.js +5 -3
  54. package/dist/components/layout/separator.js +0 -1
  55. package/dist/components/layout/sidebar.js +4 -1
  56. package/dist/components/layout/split-pane.d.ts +14 -1
  57. package/dist/components/layout/split-pane.js +26 -13
  58. package/dist/components/layout/topbar-item.js +1 -2
  59. package/dist/components/navigation/app-setting-picker.js +21 -2
  60. package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
  61. package/dist/components/navigation/app-setting-toggle.js +96 -0
  62. package/dist/components/navigation/dropdown-menu.d.ts +199 -18
  63. package/dist/components/navigation/dropdown-menu.js +344 -117
  64. package/dist/components/navigation/index.d.ts +2 -0
  65. package/dist/components/navigation/index.js +2 -0
  66. package/dist/components/navigation/pagination-utils.d.ts +2 -1
  67. package/dist/components/navigation/tabs.d.ts +47 -6
  68. package/dist/components/navigation/tabs.js +152 -90
  69. package/dist/components/ui/accordion.d.ts +50 -5
  70. package/dist/components/ui/accordion.js +239 -33
  71. package/dist/components/ui/aspect-ratio.d.ts +23 -2
  72. package/dist/components/ui/aspect-ratio.js +15 -13
  73. package/dist/components/ui/avatar.d.ts +29 -4
  74. package/dist/components/ui/avatar.js +111 -25
  75. package/dist/components/ui/hover-card.d.ts +42 -4
  76. package/dist/components/ui/hover-card.js +183 -27
  77. package/dist/components/ui/label.js +0 -1
  78. package/dist/components/ui/segmented.d.ts +3 -3
  79. package/dist/components/ui/separator.d.ts +8 -2
  80. package/dist/components/ui/separator.js +17 -9
  81. package/dist/components/ui/toggle-group.d.ts +50 -5
  82. package/dist/components/ui/toggle-group.js +79 -20
  83. package/dist/components/ui/toggle.d.ts +31 -5
  84. package/dist/components/ui/toggle.js +42 -3
  85. package/dist/i18n/messages/en.json +8 -0
  86. package/dist/i18n/messages/ja.json +8 -0
  87. package/dist/i18n/messages/vi.json +8 -0
  88. package/dist/lib/control-styles.d.ts +1 -1
  89. package/dist/lib/control-styles.js +1 -1
  90. package/dist/lib/slot.d.ts +32 -0
  91. package/dist/lib/slot.js +22 -0
  92. package/dist/lib/variants.d.ts +22 -3
  93. package/dist/lib/variants.js +56 -1
  94. package/dist/props/components/app.prop.d.ts +25 -1
  95. package/dist/props/components/data-display.prop.d.ts +17 -0
  96. package/dist/props/components/data-entry.prop.d.ts +32 -4
  97. package/dist/props/components/general.prop.d.ts +31 -1
  98. package/dist/props/components/index.d.ts +1 -1
  99. package/dist/props/components/layout.prop.d.ts +118 -5
  100. package/dist/props/components/navigation.prop.d.ts +1 -1
  101. package/dist/props/registry.d.ts +22 -2
  102. package/dist/props/registry.js +33 -2
  103. package/dist/props/vocabulary/content.prop.d.ts +1 -1
  104. package/dist/props/vocabulary/index.d.ts +2 -2
  105. package/dist/props/vocabulary/interaction.prop.d.ts +17 -1
  106. package/dist/props/vocabulary/layout.prop.d.ts +57 -1
  107. package/dist/styles/control.css +142 -1
  108. package/dist/styles/core.css +5 -2
  109. package/dist/styles/data-display-layout.css +72 -0
  110. package/dist/styles/focus-ring.css +13 -1
  111. package/dist/styles/index.css +5 -2
  112. package/dist/styles/layout.css +106 -0
  113. package/dist/styles/shell-layout.css +140 -8
  114. package/dist/styles/text-layout.css +16 -4
  115. package/dist/tokens/base.css +2 -1
  116. package/dist/tokens/components/data-display.css +11 -0
  117. package/dist/tokens/components/shell.css +3 -1
  118. package/dist/tokens/components/text.css +7 -0
  119. package/dist/tokens/foundation.css +2 -0
  120. package/docs/COMPONENTS.md +9 -3
  121. package/docs/DESIGN-AUTHORITY.md +96 -71
  122. package/docs/FRAME-COVERAGE-REPORT.md +5 -3
  123. package/docs/README.md +14 -14
  124. package/docs/WHAT-BELONGS-HERE.md +179 -0
  125. package/docs/data-display/legend.tsx +145 -0
  126. package/docs/data-display/progress.tsx +32 -0
  127. package/docs/data-entry/segmented.tsx +1 -1
  128. package/docs/data-entry/select.tsx +2 -2
  129. package/docs/feedback/sheet.tsx +1 -1
  130. package/docs/general/typography.tsx +84 -1
  131. package/docs/layout/app-shell-arrangements.tsx +225 -0
  132. package/docs/layout/aspect-ratio.tsx +1 -1
  133. package/docs/navigation/app-setting-picker.tsx +26 -1
  134. package/docs/navigation/app-setting-toggle.tsx +111 -0
  135. package/package.json +11 -8
  136. package/scripts/_agent-setup.mjs +165 -3
  137. package/scripts/consumer-rule.md +98 -0
  138. package/scripts/guinea-pig-skill.md +322 -0
  139. package/scripts/init-guinea-pig.mjs +62 -0
  140. package/scripts/postinstall.mjs +13 -2
  141. package/scripts/ui-audit.mjs +115 -14
  142. /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
@@ -1,3 +1,24 @@
1
1
  import * as React from "react";
2
- import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
3
- export declare const AspectRatio: React.ForwardRefExoticComponent<Omit<AspectRatioPrimitive.AspectRatioProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
2
+ /**
3
+ * AspectRatio hộp giữ tỉ lệ, dựng bằng thuộc tính CSS `aspect-ratio`.
4
+ *
5
+ * Radix dựng cái này bằng mẹo `padding-bottom` thời chưa có `aspect-ratio`: một
6
+ * div bọc `position: relative; width: 100%; padding-bottom: (100/ratio)%`, rồi
7
+ * div THẬT nằm trong nó ở `position: absolute; inset: 0`. Hai node, hai lớp
8
+ * định vị, chỉ để nói một câu mà CSS ngày nay nói được bằng một khai báo.
9
+ *
10
+ * Nên node bọc biến mất và `.ui-aspect-ratio` giờ LÀ hộp tỉ lệ. Kiểm trước khi
11
+ * đổi: không luật nào trong `src/styles/` bám vào `[data-radix-aspect-ratio-wrapper]`
12
+ * hay vào việc `.ui-aspect-ratio` phải là con của một node khác — luật duy nhất
13
+ * chạm tới nó là `.ui-aspect-ratio { overflow: clip; overflow-clip-margin }`
14
+ * trong `layout.css`, và nó bám vào CHÍNH node này, node vẫn còn.
15
+ *
16
+ * `position: relative` được giữ lại dù không còn cần cho mẹo padding: node bọc
17
+ * trước đây là containing block của mọi con `position: absolute` mà consumer
18
+ * đặt vào (một overlay, một badge góc). Bỏ nó đi thì những con ấy lặng lẽ nhảy
19
+ * ra neo vào tổ tiên định vị gần nhất ở ngoài.
20
+ */
21
+ export declare const AspectRatio: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
22
+ asChild?: boolean;
23
+ ratio?: number;
24
+ } & React.RefAttributes<HTMLDivElement>>;
@@ -1,19 +1,21 @@
1
- "use client";
2
1
  import { jsx } from "react/jsx-runtime";
3
2
  import * as React from "react";
4
- import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
3
+ import { Slot } from "../../lib/slot.js";
5
4
  import { cn } from "../../lib/utils.js";
6
- const AspectRatio = React.forwardRef(({ className, ratio = 16 / 9, ...props }, ref) => /* @__PURE__ */ jsx(
7
- AspectRatioPrimitive.Root,
8
- {
9
- ref,
10
- "data-slot": "aspect-ratio",
11
- ratio,
12
- className: cn("ui-aspect-ratio", className),
13
- ...props
14
- }
15
- ));
16
- AspectRatio.displayName = AspectRatioPrimitive.Root.displayName;
5
+ const AspectRatio = React.forwardRef(({ className, ratio = 16 / 9, style, asChild = false, ...props }, ref) => {
6
+ const Comp = asChild ? Slot : "div";
7
+ return /* @__PURE__ */ jsx(
8
+ Comp,
9
+ {
10
+ ref,
11
+ "data-slot": "aspect-ratio",
12
+ className: cn("ui-aspect-ratio", className),
13
+ style: { ...style, position: "relative", width: "100%", aspectRatio: `${ratio}` },
14
+ ...props
15
+ }
16
+ );
17
+ });
18
+ AspectRatio.displayName = "AspectRatio";
17
19
  export {
18
20
  AspectRatio
19
21
  };
@@ -1,6 +1,31 @@
1
1
  import * as React from "react";
2
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
3
2
  import type { AvatarProp } from "../../props/components/data-display.prop.js";
4
- export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape"> & React.RefAttributes<HTMLSpanElement>>;
5
- export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
6
- export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
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
+ export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
21
+ asChild?: boolean;
22
+ } & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape"> & React.RefAttributes<HTMLSpanElement>>;
23
+ export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & {
24
+ asChild?: boolean;
25
+ onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
26
+ } & React.RefAttributes<HTMLImageElement>>;
27
+ export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
28
+ asChild?: boolean;
29
+ delayMs?: number;
30
+ } & React.RefAttributes<HTMLSpanElement>>;
31
+ export {};
@@ -1,9 +1,54 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffectEvent, useLayoutEffect } from "@react-aria/utils";
3
4
  import * as React from "react";
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { Slot } from "../../lib/slot.js";
6
7
  import { cn } from "../../lib/utils.js";
8
+ const AvatarContext = React.createContext(null);
9
+ function useAvatarContext(part) {
10
+ const context = React.useContext(AvatarContext);
11
+ if (context === null) {
12
+ throw new Error(`\`${part}\` must be used within \`Avatar\``);
13
+ }
14
+ return context;
15
+ }
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
+ }
7
52
  const Avatar = React.forwardRef(
8
53
  ({
9
54
  className,
@@ -11,12 +56,19 @@ const Avatar = React.forwardRef(
11
56
  appearance = "default",
12
57
  presence,
13
58
  presenceLabel,
59
+ asChild = false,
14
60
  children,
15
61
  ...props
16
62
  }, ref) => {
17
63
  const { t } = useTranslation();
18
- return /* @__PURE__ */ jsxs(
19
- AvatarPrimitive.Root,
64
+ const [imageLoadingStatus, setImageLoadingStatus] = React.useState("idle");
65
+ const context = React.useMemo(
66
+ () => ({ imageLoadingStatus, setImageLoadingStatus }),
67
+ [imageLoadingStatus]
68
+ );
69
+ const Comp = asChild ? Slot : "span";
70
+ return /* @__PURE__ */ jsx(AvatarContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
71
+ Comp,
20
72
  {
21
73
  ref,
22
74
  "data-slot": "avatar",
@@ -30,30 +82,64 @@ const Avatar = React.forwardRef(
30
82
  presence !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-presence", "data-presence": presence, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: presenceLabel ?? t(`dataDisplay.avatar.presence.${presence}`) }) }) : null
31
83
  ]
32
84
  }
33
- );
85
+ ) });
34
86
  }
35
87
  );
36
- Avatar.displayName = AvatarPrimitive.Root.displayName;
37
- const AvatarImage = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
38
- AvatarPrimitive.Image,
39
- {
40
- ref,
41
- "data-slot": "avatar-image",
42
- className: cn("ui-avatar-image", className),
43
- ...props
44
- }
45
- ));
46
- AvatarImage.displayName = AvatarPrimitive.Image.displayName;
47
- const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
48
- AvatarPrimitive.Fallback,
49
- {
50
- ref,
51
- "data-slot": "avatar-fallback",
52
- className: cn("ui-avatar-fallback", className),
53
- ...props
54
- }
55
- ));
56
- AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
88
+ Avatar.displayName = "Avatar";
89
+ const AvatarImage = React.forwardRef(({ className, src, onLoadingStatusChange, asChild = false, ...props }, ref) => {
90
+ const { imageLoadingStatus, setImageLoadingStatus } = useAvatarContext("AvatarImage");
91
+ const status = useImageLoadingStatus(src, {
92
+ referrerPolicy: props.referrerPolicy,
93
+ crossOrigin: props.crossOrigin,
94
+ loadingStatus: imageLoadingStatus,
95
+ setLoadingStatus: setImageLoadingStatus
96
+ });
97
+ const handleLoadingStatusChange = useEffectEvent((next) => {
98
+ onLoadingStatusChange?.(next);
99
+ });
100
+ const previousStatus = React.useRef(status);
101
+ useLayoutEffect(() => {
102
+ const previous = previousStatus.current;
103
+ previousStatus.current = status;
104
+ if (status !== previous) {
105
+ handleLoadingStatusChange(status);
106
+ }
107
+ }, [status]);
108
+ const Comp = asChild ? Slot : "img";
109
+ return status === "loaded" ? /* @__PURE__ */ jsx(
110
+ Comp,
111
+ {
112
+ ref,
113
+ "data-slot": "avatar-image",
114
+ className: cn("ui-avatar-image", className),
115
+ ...props,
116
+ src
117
+ }
118
+ ) : null;
119
+ });
120
+ AvatarImage.displayName = "AvatarImage";
121
+ const AvatarFallback = React.forwardRef(({ className, delayMs, asChild = false, ...props }, ref) => {
122
+ const { imageLoadingStatus } = useAvatarContext("AvatarFallback");
123
+ const [canRender, setCanRender] = React.useState(delayMs === void 0);
124
+ React.useEffect(() => {
125
+ if (delayMs === void 0) {
126
+ return;
127
+ }
128
+ const timerId = window.setTimeout(() => setCanRender(true), delayMs);
129
+ return () => window.clearTimeout(timerId);
130
+ }, [delayMs]);
131
+ const Comp = asChild ? Slot : "span";
132
+ return canRender && imageLoadingStatus !== "loaded" ? /* @__PURE__ */ jsx(
133
+ Comp,
134
+ {
135
+ ref,
136
+ "data-slot": "avatar-fallback",
137
+ className: cn("ui-avatar-fallback", className),
138
+ ...props
139
+ }
140
+ ) : null;
141
+ });
142
+ AvatarFallback.displayName = "AvatarFallback";
57
143
  export {
58
144
  Avatar,
59
145
  AvatarFallback,
@@ -1,9 +1,47 @@
1
1
  import * as React from "react";
2
- import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
2
+ type Side = "top" | "right" | "bottom" | "left";
3
+ type Align = "start" | "center" | "end";
4
+ interface HoverCardProps {
5
+ /** Trạng thái mở, có kiểm soát. */
6
+ open?: boolean;
7
+ /** Trạng thái mở ban đầu khi không kiểm soát. */
8
+ defaultOpen?: boolean;
9
+ /** Gọi khi trạng thái mở/đóng đổi. */
10
+ onOpenChange?: (open: boolean) => void;
11
+ /** Thời gian rê chuột trước khi mở, tính bằng ms. */
12
+ openDelay?: number;
13
+ /** Thời gian sau khi con trỏ rời đi trước khi đóng, tính bằng ms. */
14
+ closeDelay?: number;
15
+ }
3
16
  /**
4
17
  * HoverCard root. Defaults to a SNAPPY 200ms open / 100ms close (Radix's raw 700/300 default felt
5
18
  * laggy); consumers can still pass `openDelay`/`closeDelay`.
6
19
  */
7
- export declare function HoverCard({ openDelay, closeDelay, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>): React.JSX.Element;
8
- export declare const HoverCardTrigger: React.ForwardRefExoticComponent<HoverCardPrimitive.HoverCardTriggerProps & React.RefAttributes<HTMLAnchorElement>>;
9
- export declare const HoverCardContent: React.ForwardRefExoticComponent<Omit<HoverCardPrimitive.HoverCardContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
20
+ export declare function HoverCard({ open, defaultOpen, onOpenChange, openDelay, closeDelay, children, }: React.PropsWithChildren<HoverCardProps>): React.JSX.Element;
21
+ interface HoverCardTriggerProps extends React.ComponentPropsWithRef<"a"> {
22
+ /** Mượn thẻ của con thay dựng `<a>` riêng. */
23
+ asChild?: boolean;
24
+ }
25
+ export declare function HoverCardTrigger({ asChild, onPointerEnter, onPointerLeave, onFocus, onBlur, ref, ...props }: HoverCardTriggerProps): React.JSX.Element;
26
+ interface HoverCardContentProps extends React.ComponentPropsWithRef<"div"> {
27
+ /** Cạnh của trigger mà tấm thẻ bám vào. */
28
+ side?: Side;
29
+ /** Canh tấm thẻ theo cạnh của trigger. */
30
+ align?: Align;
31
+ /** Khoảng cách theo trục chính, tính bằng px. */
32
+ sideOffset?: number;
33
+ /** Khoảng cách theo trục phụ, tính bằng px. */
34
+ alignOffset?: number;
35
+ /** Lật tấm thẻ sang cạnh đối diện khi hết chỗ. */
36
+ avoidCollisions?: boolean;
37
+ /** Khoảng chừa với mép khung nhìn. RAC chỉ nhận một số; object lấy cạnh lớn nhất. */
38
+ collisionPadding?: number | Partial<Record<Side, number>>;
39
+ /** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
40
+ sticky?: "partial" | "always";
41
+ /** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
42
+ hideWhenDetached?: boolean;
43
+ /** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
44
+ forceMount?: true;
45
+ }
46
+ export declare function HoverCardContent({ className, style, children, ref, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, onPointerEnter, onPointerLeave, ...props }: HoverCardContentProps): React.JSX.Element;
47
+ export {};
@@ -1,44 +1,200 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
4
+ import { chain, mergeRefs } from "@react-aria/utils";
5
+ import { Popover as AriaPopover } from "react-aria-components";
5
6
  import { cn } from "../../lib/utils.js";
7
+ import { Slot } from "../../lib/slot.js";
8
+ function toPlacement(side, align) {
9
+ if (align === "center") return side;
10
+ if (side === "top" || side === "bottom") return `${side} ${align}`;
11
+ return `${side} ${align === "start" ? "top" : "bottom"}`;
12
+ }
13
+ function toContainerPadding(padding) {
14
+ if (padding == null) return void 0;
15
+ if (typeof padding === "number") return padding;
16
+ const sides = Object.values(padding).filter(
17
+ (value) => typeof value === "number"
18
+ );
19
+ return sides.length ? Math.max(...sides) : void 0;
20
+ }
21
+ const HoverCardContext = React.createContext(null);
22
+ function useHoverCard(component) {
23
+ const context = React.useContext(HoverCardContext);
24
+ if (!context) {
25
+ throw new Error(`\`${component}\` ph\u1EA3i n\u1EB1m trong \`HoverCard\`.`);
26
+ }
27
+ return context;
28
+ }
6
29
  function HoverCard({
30
+ open,
31
+ defaultOpen,
32
+ onOpenChange,
7
33
  openDelay = 200,
8
34
  closeDelay = 100,
35
+ children
36
+ }) {
37
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
38
+ const triggerRef = React.useRef(null);
39
+ const openTimer = React.useRef(null);
40
+ const closeTimer = React.useRef(null);
41
+ const isOpen = open ?? uncontrolledOpen;
42
+ const setOpenImmediately = React.useCallback(
43
+ (next) => {
44
+ if (open === void 0) {
45
+ setUncontrolledOpen(next);
46
+ }
47
+ onOpenChange?.(next);
48
+ },
49
+ [open, onOpenChange]
50
+ );
51
+ const value = React.useMemo(() => {
52
+ const clearOpen = () => {
53
+ if (openTimer.current) clearTimeout(openTimer.current);
54
+ openTimer.current = null;
55
+ };
56
+ const clearClose = () => {
57
+ if (closeTimer.current) clearTimeout(closeTimer.current);
58
+ closeTimer.current = null;
59
+ };
60
+ return {
61
+ open: isOpen,
62
+ triggerRef,
63
+ setOpenImmediately: (next) => {
64
+ clearOpen();
65
+ clearClose();
66
+ setOpenImmediately(next);
67
+ },
68
+ scheduleOpen: () => {
69
+ clearClose();
70
+ if (openTimer.current) return;
71
+ openTimer.current = setTimeout(() => {
72
+ openTimer.current = null;
73
+ setOpenImmediately(true);
74
+ }, openDelay);
75
+ },
76
+ scheduleClose: () => {
77
+ clearOpen();
78
+ if (closeTimer.current) return;
79
+ closeTimer.current = setTimeout(() => {
80
+ closeTimer.current = null;
81
+ setOpenImmediately(false);
82
+ }, closeDelay);
83
+ },
84
+ cancelClose: clearClose
85
+ };
86
+ }, [isOpen, setOpenImmediately, openDelay, closeDelay]);
87
+ React.useEffect(
88
+ () => () => {
89
+ if (openTimer.current) clearTimeout(openTimer.current);
90
+ if (closeTimer.current) clearTimeout(closeTimer.current);
91
+ },
92
+ []
93
+ );
94
+ return /* @__PURE__ */ jsx(HoverCardContext.Provider, { value, children });
95
+ }
96
+ function HoverCardTrigger({
97
+ asChild,
98
+ onPointerEnter,
99
+ onPointerLeave,
100
+ onFocus,
101
+ onBlur,
102
+ ref,
9
103
  ...props
10
104
  }) {
105
+ const card = useHoverCard("HoverCardTrigger");
106
+ const Comp = asChild ? Slot : "a";
11
107
  return /* @__PURE__ */ jsx(
12
- HoverCardPrimitive.Root,
108
+ Comp,
13
109
  {
14
- "data-slot": "hover-card",
15
- openDelay,
16
- closeDelay,
17
- ...props
110
+ "data-state": card.open ? "open" : "closed",
111
+ ...props,
112
+ ref: mergeRefs(ref, card.triggerRef),
113
+ onPointerEnter: chain(onPointerEnter, (event) => {
114
+ if (event.pointerType === "touch") return;
115
+ card.scheduleOpen();
116
+ }),
117
+ onPointerLeave: chain(onPointerLeave, (event) => {
118
+ if (event.pointerType === "touch") return;
119
+ card.scheduleClose();
120
+ }),
121
+ onFocus: chain(onFocus, () => card.setOpenImmediately(true)),
122
+ onBlur: chain(onBlur, () => card.setOpenImmediately(false))
123
+ }
124
+ );
125
+ }
126
+ function HoverCardContent({
127
+ className,
128
+ style,
129
+ children,
130
+ ref,
131
+ side = "bottom",
132
+ align = "center",
133
+ sideOffset = 4,
134
+ alignOffset,
135
+ avoidCollisions = true,
136
+ collisionPadding,
137
+ sticky: _sticky,
138
+ hideWhenDetached: _hideWhenDetached,
139
+ forceMount: _forceMount,
140
+ onPointerEnter,
141
+ onPointerLeave,
142
+ ...props
143
+ }) {
144
+ const card = useHoverCard("HoverCardContent");
145
+ return /* @__PURE__ */ jsx(
146
+ AriaPopover,
147
+ {
148
+ isOpen: card.open,
149
+ onOpenChange: card.setOpenImmediately,
150
+ isNonModal: true,
151
+ triggerRef: card.triggerRef,
152
+ placement: toPlacement(side, align),
153
+ offset: sideOffset,
154
+ crossOffset: alignOffset,
155
+ shouldFlip: avoidCollisions,
156
+ containerPadding: toContainerPadding(collisionPadding),
157
+ render: (racProps, { isExiting }) => {
158
+ const {
159
+ className: _racClassName,
160
+ "data-rac": _rac,
161
+ ref: racRef,
162
+ style: racStyle,
163
+ ...rest
164
+ } = racProps;
165
+ return /* @__PURE__ */ jsx(
166
+ "div",
167
+ {
168
+ ...rest,
169
+ "data-slot": "hover-card-content",
170
+ "data-side": side,
171
+ "data-align": align,
172
+ "data-state": isExiting ? "closed" : "open",
173
+ ...props,
174
+ ref: mergeRefs(ref, racRef),
175
+ onPointerEnter: chain(onPointerEnter, () => card.cancelClose()),
176
+ onPointerLeave: chain(onPointerLeave, () => card.scheduleClose()),
177
+ className: cn(
178
+ // Match Popover/Tooltip: a directional fade+zoom+slide entrance so it doesn't pop abruptly.
179
+ "ui-hover-card-content origin-[var(--radix-hover-card-content-transform-origin)]",
180
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
181
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
182
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
183
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
184
+ className
185
+ ),
186
+ style: {
187
+ ...racStyle,
188
+ "--radix-hover-card-content-transform-origin": "var(--trigger-anchor-point)",
189
+ ...style
190
+ },
191
+ children
192
+ }
193
+ );
194
+ }
18
195
  }
19
196
  );
20
197
  }
21
- const HoverCardTrigger = HoverCardPrimitive.Trigger;
22
- const HoverCardContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(HoverCardPrimitive.Portal, { children: /* @__PURE__ */ jsx(
23
- HoverCardPrimitive.Content,
24
- {
25
- ref,
26
- "data-slot": "hover-card-content",
27
- align,
28
- sideOffset,
29
- className: cn(
30
- // Match Popover/Tooltip: a directional fade+zoom+slide entrance so it doesn't pop abruptly.
31
- "ui-hover-card-content origin-[var(--radix-hover-card-content-transform-origin)]",
32
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
33
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
34
- "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
35
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
36
- className
37
- ),
38
- ...props
39
- }
40
- ) }));
41
- HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
42
198
  export {
43
199
  HoverCard,
44
200
  HoverCardContent,
@@ -1,2 +1 @@
1
- "use client";
2
1
  export * from "../data-entry/label.js";
@@ -30,8 +30,8 @@ export type SegmentedProp = {
30
30
  };
31
31
  export type SegmentedProps = SegmentedProp;
32
32
  /**
33
- * Segmented — one-of-N from a small, closed, always-visible set. antd's `Segmented`
34
- * (docs/DESIGN-AUTHORITY.md names Ant Design the taxonomy authority) drawn on Radix's RadioGroup,
33
+ * Segmented — one-of-N from a small, closed, always-visible set. The established enterprise
34
+ * `Segmented` control (docs/DESIGN-AUTHORITY.md) drawn on Radix's RadioGroup,
35
35
  * which is this repo's authority for behaviour primitives.
36
36
  *
37
37
  * WHY NOT `ToggleGroup`. A ToggleGroup is a row of PRESSED buttons: `aria-pressed`, independently
@@ -42,7 +42,7 @@ export type SegmentedProps = SegmentedProp;
42
42
  * distinction and it is not a skin.
43
43
  *
44
44
  * The primitive gives roving tabindex, arrow-key traversal (RTL-aware), the radiogroup/radio roles
45
- * and the hidden input a native form submit needs. This file adds the antd geometry and nothing
45
+ * and the hidden input a native form submit needs. This file adds that geometry and nothing
46
46
  * else — the focus mark comes from `ui-focus-ring`, the ONE source in styles/focus-ring.css.
47
47
  */
48
48
  export declare const Segmented: React.ForwardRefExoticComponent<SegmentedProp & React.RefAttributes<HTMLDivElement>>;
@@ -2,8 +2,14 @@ import * as React from "react";
2
2
  export type { SeparatorProp, SeparatorProp as SeparatorProps, } from "../../props/components/layout.prop.js";
3
3
  /**
4
4
  * ACCESSIBILITY — the whole point of the labelled form is that the label is ANNOUNCED, exactly
5
- * ONCE. Without a label the rule stays `decorative` (Radix emits `role="none"`), which is right: a
6
- * section rule carries no information.
5
+ * ONCE. Without a label the rule stays `decorative` (`role="none"`), which is right: a section
6
+ * rule carries no information.
7
+ *
8
+ * SUBSTRATE — React Aria Components. Its `Separator` has no `decorative` prop: `useSeparator`
9
+ * emits `role="separator"` unconditionally (and `aria-orientation="vertical"` on the vertical
10
+ * rule), and it drops `children` entirely. Both are handled in the `render` override below, which
11
+ * is RAC's supported escape hatch for owning the element: it hands us the computed DOM props and
12
+ * the ref, and we decide what element carries them.
7
13
  */
8
14
  export declare const Separator: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
9
15
  orientation?: import("../../props/index.js").OrientationProp;
@@ -1,7 +1,6 @@
1
- "use client";
2
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
2
  import * as React from "react";
4
- import * as SeparatorPrimitive from "@radix-ui/react-separator";
3
+ import { Separator as AriaSeparator } from "react-aria-components";
5
4
  import { cn } from "../../lib/utils.js";
6
5
  const Separator = React.forwardRef(
7
6
  ({
@@ -24,24 +23,33 @@ const Separator = React.forwardRef(
24
23
  }
25
24
  const isDecorative = decorative ?? !labelled;
26
25
  return /* @__PURE__ */ jsx(
27
- SeparatorPrimitive.Root,
26
+ AriaSeparator,
28
27
  {
29
28
  ref,
29
+ elementType: "div",
30
30
  "data-slot": "separator",
31
31
  "data-orientation": orientation,
32
32
  "data-tone": tone,
33
33
  "data-labelled": labelled ? "" : void 0,
34
34
  "data-label-align": labelled ? labelAlign : void 0,
35
35
  orientation,
36
- decorative: isDecorative,
37
36
  "aria-label": labelled && !isDecorative ? label : void 0,
38
37
  className: cn("ui-separator", className),
39
38
  ...props,
40
- children: labelled ? /* @__PURE__ */ jsxs(Fragment, { children: [
41
- /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" }),
42
- /* @__PURE__ */ jsx("span", { className: "ui-separator-label", "aria-hidden": isDecorative ? void 0 : true, children: label }),
43
- /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" })
44
- ] }) : null
39
+ render: ({ role, "aria-orientation": ariaOrientation, ...domProps }) => /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ role: isDecorative ? "none" : role,
43
+ "aria-orientation": isDecorative ? void 0 : ariaOrientation,
44
+ ...props,
45
+ ...domProps,
46
+ children: labelled ? /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" }),
48
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-label", "aria-hidden": isDecorative ? void 0 : true, children: label }),
49
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" })
50
+ ] }) : null
51
+ }
52
+ )
45
53
  }
46
54
  );
47
55
  }