@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.
- package/dist/components/data-display/card.d.ts +2 -2
- package/dist/components/data-display/collapsible.d.ts +23 -4
- package/dist/components/data-display/collapsible.js +119 -4
- package/dist/components/data-display/index.d.ts +3 -1
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/legend.d.ts +15 -0
- package/dist/components/data-display/legend.js +11 -0
- package/dist/components/data-display/list-row.js +1 -1
- package/dist/components/data-display/popover.d.ts +58 -5
- package/dist/components/data-display/popover.js +206 -27
- package/dist/components/data-display/progress.d.ts +50 -2
- package/dist/components/data-display/progress.js +57 -9
- package/dist/components/data-entry/calendar.d.ts +1 -1
- package/dist/components/data-entry/calendar.js +67 -32
- package/dist/components/data-entry/checkbox.d.ts +19 -6
- package/dist/components/data-entry/checkbox.js +55 -16
- package/dist/components/data-entry/choice-option.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/field.js +0 -1
- package/dist/components/data-entry/form.js +1 -1
- package/dist/components/data-entry/label.d.ts +3 -2
- package/dist/components/data-entry/label.js +23 -10
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +16 -6
- package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +29 -12
- package/dist/components/data-entry/tree-utils.d.ts +1 -1
- package/dist/components/data-entry/upload.js +7 -2
- package/dist/components/feedback/dialog.d.ts +105 -38
- package/dist/components/feedback/dialog.js +272 -194
- package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
- package/dist/components/feedback/overlay-close-focus.js +31 -0
- package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
- package/dist/components/feedback/sheet.d.ts +53 -11
- package/dist/components/feedback/sheet.js +150 -81
- package/dist/components/feedback/tooltip.d.ts +51 -7
- package/dist/components/feedback/tooltip.js +107 -25
- package/dist/components/general/button.js +2 -2
- package/dist/components/general/logo.d.ts +17 -0
- package/dist/components/general/logo.js +22 -16
- package/dist/components/general/typography.d.ts +7 -1
- package/dist/components/general/typography.js +20 -5
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +52 -8
- package/dist/components/layout/aspect-ratio.js +0 -1
- package/dist/components/layout/auth-divider.js +0 -1
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +19 -2
- package/dist/components/layout/nav-surface.d.ts +26 -0
- package/dist/components/layout/nav-surface.js +17 -0
- package/dist/components/layout/org-switcher.d.ts +5 -1
- package/dist/components/layout/org-switcher.js +5 -3
- package/dist/components/layout/separator.js +0 -1
- package/dist/components/layout/sidebar.js +4 -1
- package/dist/components/layout/split-pane.d.ts +14 -1
- package/dist/components/layout/split-pane.js +26 -13
- package/dist/components/layout/topbar-item.js +1 -2
- package/dist/components/navigation/app-setting-picker.js +21 -2
- package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
- package/dist/components/navigation/app-setting-toggle.js +96 -0
- package/dist/components/navigation/dropdown-menu.d.ts +199 -18
- package/dist/components/navigation/dropdown-menu.js +344 -117
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/pagination-utils.d.ts +2 -1
- package/dist/components/navigation/tabs.d.ts +47 -6
- package/dist/components/navigation/tabs.js +152 -90
- package/dist/components/ui/accordion.d.ts +50 -5
- package/dist/components/ui/accordion.js +239 -33
- package/dist/components/ui/aspect-ratio.d.ts +23 -2
- package/dist/components/ui/aspect-ratio.js +15 -13
- package/dist/components/ui/avatar.d.ts +29 -4
- package/dist/components/ui/avatar.js +111 -25
- package/dist/components/ui/hover-card.d.ts +42 -4
- package/dist/components/ui/hover-card.js +183 -27
- package/dist/components/ui/label.js +0 -1
- package/dist/components/ui/segmented.d.ts +3 -3
- package/dist/components/ui/separator.d.ts +8 -2
- package/dist/components/ui/separator.js +17 -9
- package/dist/components/ui/toggle-group.d.ts +50 -5
- package/dist/components/ui/toggle-group.js +79 -20
- package/dist/components/ui/toggle.d.ts +31 -5
- package/dist/components/ui/toggle.js +42 -3
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +1 -1
- package/dist/lib/control-styles.js +1 -1
- package/dist/lib/slot.d.ts +32 -0
- package/dist/lib/slot.js +22 -0
- package/dist/lib/variants.d.ts +22 -3
- package/dist/lib/variants.js +56 -1
- package/dist/props/components/app.prop.d.ts +25 -1
- package/dist/props/components/data-display.prop.d.ts +17 -0
- package/dist/props/components/data-entry.prop.d.ts +32 -4
- package/dist/props/components/general.prop.d.ts +31 -1
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +118 -5
- package/dist/props/components/navigation.prop.d.ts +1 -1
- package/dist/props/registry.d.ts +22 -2
- package/dist/props/registry.js +33 -2
- package/dist/props/vocabulary/content.prop.d.ts +1 -1
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +17 -1
- package/dist/props/vocabulary/layout.prop.d.ts +57 -1
- package/dist/styles/control.css +142 -1
- package/dist/styles/core.css +5 -2
- package/dist/styles/data-display-layout.css +72 -0
- package/dist/styles/focus-ring.css +13 -1
- package/dist/styles/index.css +5 -2
- package/dist/styles/layout.css +106 -0
- package/dist/styles/shell-layout.css +140 -8
- package/dist/styles/text-layout.css +16 -4
- package/dist/tokens/base.css +2 -1
- package/dist/tokens/components/data-display.css +11 -0
- package/dist/tokens/components/shell.css +3 -1
- package/dist/tokens/components/text.css +7 -0
- package/dist/tokens/foundation.css +2 -0
- package/docs/COMPONENTS.md +9 -3
- package/docs/DESIGN-AUTHORITY.md +96 -71
- package/docs/FRAME-COVERAGE-REPORT.md +5 -3
- package/docs/README.md +14 -14
- package/docs/WHAT-BELONGS-HERE.md +179 -0
- package/docs/data-display/legend.tsx +145 -0
- package/docs/data-display/progress.tsx +32 -0
- package/docs/data-entry/segmented.tsx +1 -1
- package/docs/data-entry/select.tsx +2 -2
- package/docs/feedback/sheet.tsx +1 -1
- package/docs/general/typography.tsx +84 -1
- package/docs/layout/app-shell-arrangements.tsx +225 -0
- package/docs/layout/aspect-ratio.tsx +1 -1
- package/docs/navigation/app-setting-picker.tsx +26 -1
- package/docs/navigation/app-setting-toggle.tsx +111 -0
- package/package.json +11 -8
- package/scripts/_agent-setup.mjs +165 -3
- package/scripts/consumer-rule.md +98 -0
- package/scripts/guinea-pig-skill.md +322 -0
- package/scripts/init-guinea-pig.mjs +62 -0
- package/scripts/postinstall.mjs +13 -2
- package/scripts/ui-audit.mjs +115 -14
- /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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 =
|
|
37
|
-
const AvatarImage = React.forwardRef(({ className, ...props }, ref) =>
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
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
|
-
|
|
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,
|
|
8
|
-
|
|
9
|
-
|
|
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 vì 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
|
|
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
|
-
|
|
108
|
+
Comp,
|
|
13
109
|
{
|
|
14
|
-
"data-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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,
|
|
@@ -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.
|
|
34
|
-
* (docs/DESIGN-AUTHORITY.md
|
|
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
|
|
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` (
|
|
6
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
}
|