@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
|
@@ -87,13 +87,13 @@ export declare const CardContent: React.ForwardRefExoticComponent<React.HTMLAttr
|
|
|
87
87
|
export type CardFooterProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
88
88
|
/** Top border + symmetric action band — form Save/Cancel, table summary. */
|
|
89
89
|
separated?: boolean;
|
|
90
|
-
/** Full-bleed footer (
|
|
90
|
+
/** Full-bleed footer (the conventional `actions` bar). */
|
|
91
91
|
flush?: boolean;
|
|
92
92
|
};
|
|
93
93
|
export declare const CardFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
94
94
|
/** Top border + symmetric action band — form Save/Cancel, table summary. */
|
|
95
95
|
separated?: boolean;
|
|
96
|
-
/** Full-bleed footer (
|
|
96
|
+
/** Full-bleed footer (the conventional `actions` bar). */
|
|
97
97
|
flush?: boolean;
|
|
98
98
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
99
99
|
export type CardBarProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
interface CollapsibleProps extends React.ComponentPropsWithRef<"div"> {
|
|
3
|
+
/** Trạng thái mở, có kiểm soát. */
|
|
4
|
+
open?: boolean;
|
|
5
|
+
/** Trạng thái mở ban đầu khi không kiểm soát. */
|
|
6
|
+
defaultOpen?: boolean;
|
|
7
|
+
/** Gọi khi trạng thái mở/đóng đổi. */
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
/** Khoá trigger; `data-disabled` được phát ra để CSS bám vào. */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function Collapsible({ open, defaultOpen, onOpenChange, disabled, children, ref, ...props }: CollapsibleProps): React.JSX.Element;
|
|
13
|
+
interface CollapsibleTriggerProps extends React.ComponentPropsWithRef<"button"> {
|
|
14
|
+
/** Mượn thẻ của con thay vì dựng `<button>` riêng. */
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function CollapsibleTrigger({ asChild, onClick, disabled, ...props }: CollapsibleTriggerProps): React.JSX.Element;
|
|
18
|
+
interface CollapsibleContentProps extends React.ComponentPropsWithRef<"div"> {
|
|
19
|
+
/** Giữ nội dung trong DOM cả khi đóng. */
|
|
20
|
+
forceMount?: true;
|
|
21
|
+
}
|
|
22
|
+
export declare function CollapsibleContent({ forceMount, children, ref, ...props }: CollapsibleContentProps): React.JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -1,8 +1,123 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { chain, mergeRefs } from "@react-aria/utils";
|
|
5
|
+
import {
|
|
6
|
+
ButtonContext,
|
|
7
|
+
Disclosure,
|
|
8
|
+
DisclosurePanel,
|
|
9
|
+
DisclosureStateContext,
|
|
10
|
+
useSlottedContext
|
|
11
|
+
} from "react-aria-components";
|
|
12
|
+
import { Slot } from "../../lib/slot.js";
|
|
13
|
+
function Collapsible({
|
|
14
|
+
open,
|
|
15
|
+
defaultOpen,
|
|
16
|
+
onOpenChange,
|
|
17
|
+
disabled,
|
|
18
|
+
children,
|
|
19
|
+
ref,
|
|
20
|
+
...props
|
|
21
|
+
}) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
Disclosure,
|
|
24
|
+
{
|
|
25
|
+
isExpanded: open,
|
|
26
|
+
defaultExpanded: defaultOpen,
|
|
27
|
+
onExpandedChange: onOpenChange,
|
|
28
|
+
isDisabled: disabled,
|
|
29
|
+
render: (racProps, { isExpanded }) => {
|
|
30
|
+
const {
|
|
31
|
+
className: _racClass,
|
|
32
|
+
"data-rac": _rac,
|
|
33
|
+
ref: racRef,
|
|
34
|
+
...rest
|
|
35
|
+
} = racProps;
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
"div",
|
|
38
|
+
{
|
|
39
|
+
...props,
|
|
40
|
+
...rest,
|
|
41
|
+
ref: mergeRefs(ref, racRef),
|
|
42
|
+
"data-state": isExpanded ? "open" : "closed",
|
|
43
|
+
"data-disabled": disabled ? "" : void 0,
|
|
44
|
+
children
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function CollapsibleTrigger({
|
|
52
|
+
asChild,
|
|
53
|
+
onClick,
|
|
54
|
+
disabled,
|
|
55
|
+
...props
|
|
56
|
+
}) {
|
|
57
|
+
const trigger = useSlottedContext(ButtonContext, "trigger");
|
|
58
|
+
const state = React.useContext(DisclosureStateContext);
|
|
59
|
+
if (!trigger || !state) {
|
|
60
|
+
throw new Error("`CollapsibleTrigger` ph\u1EA3i n\u1EB1m trong `Collapsible`.");
|
|
61
|
+
}
|
|
62
|
+
const isDisabled = disabled ?? trigger.isDisabled ?? false;
|
|
63
|
+
const Comp = asChild ? Slot : "button";
|
|
64
|
+
return /* @__PURE__ */ jsx(
|
|
65
|
+
Comp,
|
|
66
|
+
{
|
|
67
|
+
type: "button",
|
|
68
|
+
id: trigger.id,
|
|
69
|
+
"aria-controls": state.isExpanded ? trigger["aria-controls"] : void 0,
|
|
70
|
+
"aria-expanded": state.isExpanded,
|
|
71
|
+
"data-state": state.isExpanded ? "open" : "closed",
|
|
72
|
+
"data-disabled": isDisabled ? "" : void 0,
|
|
73
|
+
disabled: isDisabled,
|
|
74
|
+
...props,
|
|
75
|
+
onClick: chain(onClick, () => {
|
|
76
|
+
if (!isDisabled) {
|
|
77
|
+
state.toggle();
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function CollapsibleContent({
|
|
84
|
+
forceMount,
|
|
85
|
+
children,
|
|
86
|
+
ref,
|
|
87
|
+
...props
|
|
88
|
+
}) {
|
|
89
|
+
const trigger = useSlottedContext(ButtonContext, "trigger");
|
|
90
|
+
const state = React.useContext(DisclosureStateContext);
|
|
91
|
+
if (!state) {
|
|
92
|
+
throw new Error("`CollapsibleContent` ph\u1EA3i n\u1EB1m trong `Collapsible`.");
|
|
93
|
+
}
|
|
94
|
+
return /* @__PURE__ */ jsx(
|
|
95
|
+
DisclosurePanel,
|
|
96
|
+
{
|
|
97
|
+
render: (racProps) => {
|
|
98
|
+
const {
|
|
99
|
+
className: _racClass,
|
|
100
|
+
"data-rac": _rac,
|
|
101
|
+
children: _racChildren,
|
|
102
|
+
ref: racRef,
|
|
103
|
+
...rest
|
|
104
|
+
} = racProps;
|
|
105
|
+
return /* @__PURE__ */ jsx(
|
|
106
|
+
"div",
|
|
107
|
+
{
|
|
108
|
+
...props,
|
|
109
|
+
...rest,
|
|
110
|
+
ref: mergeRefs(ref, racRef),
|
|
111
|
+
"data-state": state.isExpanded ? "open" : "closed",
|
|
112
|
+
"data-disabled": trigger?.isDisabled ? "" : void 0,
|
|
113
|
+
children: state.isExpanded || forceMount ? children : null
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
children: null
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
6
121
|
export {
|
|
7
122
|
Collapsible,
|
|
8
123
|
CollapsibleContent,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Badge, StatusBadge } from "./badge.js";
|
|
2
2
|
export type { BadgeProps } from "./badge.js";
|
|
3
|
+
export { Legend } from "./legend.js";
|
|
4
|
+
export type { LegendItemProp, LegendProp, LegendProps } from "./legend.js";
|
|
3
5
|
export { ListRow } from "./list-row.js";
|
|
4
6
|
export type { ListRowProps, ListRowDensity } from "./list-row.js";
|
|
5
7
|
export { CredentialReveal } from "./credential-reveal.js";
|
|
@@ -23,7 +25,7 @@ export { CodeBlock } from "./code-block.js";
|
|
|
23
25
|
export type { CodeBlockProp, CodeBlockProps } from "./code-block.js";
|
|
24
26
|
export { Prose } from "./prose.js";
|
|
25
27
|
export type { ProseProp, ProseProps } from "./prose.js";
|
|
26
|
-
export type { ProgressProps, ProgressTone } from "./progress.js";
|
|
28
|
+
export type { ProgressProps, ProgressSegment, ProgressTone } from "./progress.js";
|
|
27
29
|
export { TreeList } from "./tree-list.js";
|
|
28
30
|
export type { TreeListItem, TreeListProps } from "./tree-list.js";
|
|
29
31
|
export { Timeline } from "./timeline.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Badge, StatusBadge } from "./badge.js";
|
|
2
|
+
import { Legend } from "./legend.js";
|
|
2
3
|
import { ListRow } from "./list-row.js";
|
|
3
4
|
import { CredentialReveal } from "./credential-reveal.js";
|
|
4
5
|
import { QrCode } from "./qr-code.js";
|
|
@@ -88,6 +89,7 @@ export {
|
|
|
88
89
|
HoverCard,
|
|
89
90
|
HoverCardContent,
|
|
90
91
|
HoverCardTrigger,
|
|
92
|
+
Legend,
|
|
91
93
|
ListRow,
|
|
92
94
|
PermissionMatrix,
|
|
93
95
|
Popover,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LegendProp } from "../../props/components/data-display.prop.js";
|
|
2
|
+
export type { LegendItemProp, LegendProp, LegendProp as LegendProps } from "../../props/components/data-display.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* Legend — the KEY for a set of tones: which colour means what.
|
|
5
|
+
*
|
|
6
|
+
* Pair it with anything that encodes meaning as colour and cannot repeat the words on every mark:
|
|
7
|
+
* a `Progress` breakdown, a chart, a status column. It is the reason a colour-coded surface can
|
|
8
|
+
* satisfy WCAG 1.4.1 — the tone is never the only carrier of the meaning, because the key spells
|
|
9
|
+
* it out once in words.
|
|
10
|
+
*
|
|
11
|
+
* The swatch is `aria-hidden`: it is the same information as the label beside it, and a screen
|
|
12
|
+
* reader announcing "square, overdue" twice per key is noise. What reaches assistive tech is a
|
|
13
|
+
* plain list of the labels, which is exactly what the key says.
|
|
14
|
+
*/
|
|
15
|
+
export declare function Legend({ items, className, ...props }: LegendProp): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
function Legend({ items, className, ...props }) {
|
|
4
|
+
return /* @__PURE__ */ jsx("ul", { className: cn("ui-legend", className), ...props, children: items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "ui-legend-item", children: [
|
|
5
|
+
/* @__PURE__ */ jsx("span", { className: "ui-legend-swatch", "data-tone": item.tone, "aria-hidden": "true" }),
|
|
6
|
+
item.label
|
|
7
|
+
] }, index)) });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
Legend
|
|
11
|
+
};
|
|
@@ -1,7 +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 { Slot } from "
|
|
4
|
+
import { Slot } from "../../lib/slot.js";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { Text } from "../general/typography.js";
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
2
|
import type { FlushProp } from "../../props/vocabulary/index.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
type Side = "top" | "right" | "bottom" | "left";
|
|
4
|
+
type Align = "start" | "center" | "end";
|
|
5
|
+
interface PopoverProps {
|
|
6
|
+
/** Trạng thái mở, có kiểm soát. */
|
|
7
|
+
open?: boolean;
|
|
8
|
+
/** Trạng thái mở ban đầu khi không kiểm soát. */
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
/** Gọi khi trạng thái mở/đóng đổi. */
|
|
11
|
+
onOpenChange?: (open: boolean) => void;
|
|
12
|
+
/** Khoá tương tác ngoài panel và giam tiêu điểm, đúng như `modal` của Radix. */
|
|
13
|
+
modal?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function Popover({ open, defaultOpen, onOpenChange, modal, children, }: React.PropsWithChildren<PopoverProps>): React.JSX.Element;
|
|
16
|
+
interface PopoverTriggerProps extends React.ComponentPropsWithRef<"button"> {
|
|
17
|
+
/** Mượn thẻ của con thay vì dựng `<button>` riêng. */
|
|
18
|
+
asChild?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function PopoverTrigger({ asChild, onClick, ref, ...props }: PopoverTriggerProps): React.JSX.Element;
|
|
21
|
+
interface PopoverAnchorProps extends React.ComponentPropsWithRef<"span"> {
|
|
22
|
+
/** Mượn thẻ của con thay vì dựng `<span>` riêng. */
|
|
23
|
+
asChild?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function PopoverAnchor({ asChild, ref, ...props }: PopoverAnchorProps): React.JSX.Element;
|
|
7
26
|
/**
|
|
8
27
|
* The panel's CONTENT owns its inset — a Command list, a menu or a table that must run edge to
|
|
9
28
|
* edge and draw its own separators across the full width. The popover drops its own padding by
|
|
@@ -14,7 +33,41 @@ export declare function PopoverAnchor(props: React.ComponentProps<typeof Popover
|
|
|
14
33
|
type PopoverContentFlush = {
|
|
15
34
|
flush?: FlushProp;
|
|
16
35
|
};
|
|
17
|
-
|
|
36
|
+
interface PopoverContentProps extends React.ComponentPropsWithRef<"div">, PopoverContentFlush {
|
|
37
|
+
/** Cạnh của trigger mà panel bám vào. */
|
|
38
|
+
side?: Side;
|
|
39
|
+
/** Canh panel theo cạnh của trigger. */
|
|
40
|
+
align?: Align;
|
|
41
|
+
/** Khoảng cách theo trục chính, tính bằng px. */
|
|
42
|
+
sideOffset?: number;
|
|
43
|
+
/** Khoảng cách theo trục phụ, tính bằng px. */
|
|
44
|
+
alignOffset?: number;
|
|
45
|
+
/** Lật panel sang cạnh đối diện khi hết chỗ. */
|
|
46
|
+
avoidCollisions?: boolean;
|
|
47
|
+
/** 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. */
|
|
48
|
+
collisionPadding?: number | Partial<Record<Side, number>>;
|
|
49
|
+
/** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
|
|
50
|
+
sticky?: "partial" | "always";
|
|
51
|
+
/** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
|
|
52
|
+
hideWhenDetached?: boolean;
|
|
53
|
+
/** Không còn tác dụng trên nền RAC — giữ trong kiểu vì là API công khai. */
|
|
54
|
+
forceMount?: true;
|
|
55
|
+
/**
|
|
56
|
+
* Chặn việc panel tự lấy tiêu điểm khi mở, bằng `event.preventDefault()`.
|
|
57
|
+
*
|
|
58
|
+
* Năm picker trong kho dựa vào đúng điều này để giữ tiêu điểm ở ô nhập, nên hành vi được chép
|
|
59
|
+
* tay từ Radix chứ không bỏ.
|
|
60
|
+
*/
|
|
61
|
+
onOpenAutoFocus?: (event: Event) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Chặn việc trả tiêu điểm về nơi nó đứng trước khi panel mở, bằng `event.preventDefault()`.
|
|
64
|
+
*
|
|
65
|
+
* Phát ở MỌI đường đóng (chọn một mục, Escape, bấm ra ngoài, Tab ra ngoài), nên consumer đưa
|
|
66
|
+
* được tiêu điểm tới đúng chỗ người dùng cần gõ tiếp thay vì để nó quay về trigger.
|
|
67
|
+
*/
|
|
68
|
+
onCloseAutoFocus?: (event: Event) => void;
|
|
69
|
+
}
|
|
70
|
+
export declare function PopoverContent({ className, style, children, ref, flush, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, onOpenAutoFocus, onCloseAutoFocus, ...props }: PopoverContentProps): React.JSX.Element;
|
|
18
71
|
export declare const PopoverHeader: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
|
|
19
72
|
export declare const PopoverTitle: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
|
|
20
73
|
export declare const PopoverDescription: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => React.JSX.Element;
|
|
@@ -1,38 +1,217 @@
|
|
|
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";
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import { Slot } from "../../lib/slot.js";
|
|
8
|
+
import { useOverlayCloseFocus } from "../feedback/overlay-close-focus.js";
|
|
9
|
+
function toPlacement(side, align) {
|
|
10
|
+
if (align === "center") return side;
|
|
11
|
+
if (side === "top" || side === "bottom") return `${side} ${align}`;
|
|
12
|
+
return `${side} ${align === "start" ? "top" : "bottom"}`;
|
|
8
13
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
14
|
+
function toContainerPadding(padding) {
|
|
15
|
+
if (padding == null) return void 0;
|
|
16
|
+
if (typeof padding === "number") return padding;
|
|
17
|
+
const sides = Object.values(padding).filter(
|
|
18
|
+
(value) => typeof value === "number"
|
|
19
|
+
);
|
|
20
|
+
return sides.length ? Math.max(...sides) : void 0;
|
|
11
21
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
22
|
+
function firstTabbable(container) {
|
|
23
|
+
for (const node of container.querySelectorAll("*")) {
|
|
24
|
+
if (node.tagName === "A") continue;
|
|
25
|
+
if (node.hidden || node.disabled) continue;
|
|
26
|
+
if (node.tagName === "INPUT" && node.type === "hidden") continue;
|
|
27
|
+
if (node.tabIndex >= 0) return node;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
14
30
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"data-flush": flush ? "" : void 0,
|
|
21
|
-
align,
|
|
22
|
-
sideOffset,
|
|
23
|
-
style: flush ? { ...style, "--popover-space-inset": "0" } : style,
|
|
24
|
-
className: cn(
|
|
25
|
-
"ui-popover-content origin-[var(--radix-popover-content-transform-origin)]",
|
|
26
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
27
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
28
|
-
"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
|
29
|
-
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
|
|
30
|
-
className
|
|
31
|
-
),
|
|
32
|
-
...props
|
|
31
|
+
const PopoverRootContext = React.createContext(null);
|
|
32
|
+
function usePopoverRoot(component) {
|
|
33
|
+
const context = React.useContext(PopoverRootContext);
|
|
34
|
+
if (!context) {
|
|
35
|
+
throw new Error(`\`${component}\` ph\u1EA3i n\u1EB1m trong \`Popover\`.`);
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
39
|
+
function Popover({
|
|
40
|
+
open,
|
|
41
|
+
defaultOpen,
|
|
42
|
+
onOpenChange,
|
|
43
|
+
modal = false,
|
|
44
|
+
children
|
|
45
|
+
}) {
|
|
46
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false);
|
|
47
|
+
const [anchored, setAnchored] = React.useState(false);
|
|
48
|
+
const triggerRef = React.useRef(null);
|
|
49
|
+
const anchorRef = React.useRef(null);
|
|
50
|
+
const contentId = React.useId();
|
|
51
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
52
|
+
const setOpen = React.useCallback(
|
|
53
|
+
(next) => {
|
|
54
|
+
if (open === void 0) {
|
|
55
|
+
setUncontrolledOpen(next);
|
|
56
|
+
}
|
|
57
|
+
onOpenChange?.(next);
|
|
58
|
+
},
|
|
59
|
+
[open, onOpenChange]
|
|
60
|
+
);
|
|
61
|
+
const value = React.useMemo(
|
|
62
|
+
() => ({
|
|
63
|
+
open: isOpen,
|
|
64
|
+
setOpen,
|
|
65
|
+
contentId,
|
|
66
|
+
isNonModal: !modal,
|
|
67
|
+
triggerRef,
|
|
68
|
+
anchorRef,
|
|
69
|
+
anchored,
|
|
70
|
+
setAnchored
|
|
71
|
+
}),
|
|
72
|
+
[isOpen, setOpen, contentId, modal, anchored]
|
|
73
|
+
);
|
|
74
|
+
return /* @__PURE__ */ jsx(PopoverRootContext.Provider, { value, children });
|
|
75
|
+
}
|
|
76
|
+
function PopoverTrigger({ asChild, onClick, ref, ...props }) {
|
|
77
|
+
const root = usePopoverRoot("PopoverTrigger");
|
|
78
|
+
const Comp = asChild ? Slot : "button";
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
Comp,
|
|
81
|
+
{
|
|
82
|
+
type: "button",
|
|
83
|
+
"data-slot": "popover-trigger",
|
|
84
|
+
"aria-haspopup": "dialog",
|
|
85
|
+
"aria-expanded": root.open,
|
|
86
|
+
"aria-controls": root.open ? root.contentId : void 0,
|
|
87
|
+
"data-state": root.open ? "open" : "closed",
|
|
88
|
+
...props,
|
|
89
|
+
ref: mergeRefs(ref, root.triggerRef),
|
|
90
|
+
onClick: chain(onClick, () => root.setOpen(!root.open))
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function PopoverAnchor({ asChild, ref, ...props }) {
|
|
95
|
+
const root = usePopoverRoot("PopoverAnchor");
|
|
96
|
+
const Comp = asChild ? Slot : "span";
|
|
97
|
+
const { setAnchored } = root;
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
setAnchored(true);
|
|
100
|
+
return () => setAnchored(false);
|
|
101
|
+
}, [setAnchored]);
|
|
102
|
+
return /* @__PURE__ */ jsx(Comp, { "data-slot": "popover-anchor", ...props, ref: mergeRefs(ref, root.anchorRef) });
|
|
103
|
+
}
|
|
104
|
+
function PopoverContent({
|
|
105
|
+
className,
|
|
106
|
+
style,
|
|
107
|
+
children,
|
|
108
|
+
ref,
|
|
109
|
+
flush,
|
|
110
|
+
side = "bottom",
|
|
111
|
+
align = "center",
|
|
112
|
+
sideOffset = 4,
|
|
113
|
+
alignOffset,
|
|
114
|
+
avoidCollisions = true,
|
|
115
|
+
collisionPadding,
|
|
116
|
+
sticky: _sticky,
|
|
117
|
+
hideWhenDetached: _hideWhenDetached,
|
|
118
|
+
forceMount: _forceMount,
|
|
119
|
+
onOpenAutoFocus,
|
|
120
|
+
onCloseAutoFocus,
|
|
121
|
+
...props
|
|
122
|
+
}) {
|
|
123
|
+
const root = usePopoverRoot("PopoverContent");
|
|
124
|
+
const contentRef = React.useRef(null);
|
|
125
|
+
const openAutoFocusRef = React.useRef(onOpenAutoFocus);
|
|
126
|
+
openAutoFocusRef.current = onOpenAutoFocus;
|
|
127
|
+
useOverlayCloseFocus(root.open, onCloseAutoFocus, contentRef);
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
if (!root.open) return;
|
|
130
|
+
const node = contentRef.current;
|
|
131
|
+
if (!node) return;
|
|
132
|
+
const event = new Event("popover.openAutoFocus", { bubbles: false, cancelable: true });
|
|
133
|
+
openAutoFocusRef.current?.(event);
|
|
134
|
+
if (!event.defaultPrevented) {
|
|
135
|
+
const target = firstTabbable(node);
|
|
136
|
+
target?.focus({ preventScroll: true });
|
|
137
|
+
if (!node.contains(document.activeElement)) {
|
|
138
|
+
node.focus({ preventScroll: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [root.open]);
|
|
142
|
+
const { open, setOpen, triggerRef } = root;
|
|
143
|
+
React.useEffect(() => {
|
|
144
|
+
if (!open) return;
|
|
145
|
+
const onPointerDown = (event) => {
|
|
146
|
+
const target = event.target;
|
|
147
|
+
if (!target) return;
|
|
148
|
+
if (contentRef.current?.contains(target)) return;
|
|
149
|
+
if (triggerRef.current?.contains(target)) return;
|
|
150
|
+
setOpen(false);
|
|
151
|
+
};
|
|
152
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
153
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
154
|
+
}, [open, setOpen, triggerRef]);
|
|
155
|
+
return /* @__PURE__ */ jsx(
|
|
156
|
+
AriaPopover,
|
|
157
|
+
{
|
|
158
|
+
isOpen: root.open,
|
|
159
|
+
onOpenChange: root.setOpen,
|
|
160
|
+
isNonModal: root.isNonModal,
|
|
161
|
+
triggerRef: root.anchored ? root.anchorRef : root.triggerRef,
|
|
162
|
+
placement: toPlacement(side, align),
|
|
163
|
+
offset: sideOffset,
|
|
164
|
+
crossOffset: alignOffset,
|
|
165
|
+
shouldFlip: avoidCollisions,
|
|
166
|
+
containerPadding: toContainerPadding(collisionPadding),
|
|
167
|
+
render: (racProps, { isExiting }) => {
|
|
168
|
+
const {
|
|
169
|
+
className: _racClassName,
|
|
170
|
+
"data-rac": _rac,
|
|
171
|
+
ref: racRef,
|
|
172
|
+
style: racStyle,
|
|
173
|
+
...rest
|
|
174
|
+
} = racProps;
|
|
175
|
+
const availableHeight = racStyle?.maxHeight;
|
|
176
|
+
return /* @__PURE__ */ jsx(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
...rest,
|
|
180
|
+
id: root.contentId,
|
|
181
|
+
role: "dialog",
|
|
182
|
+
tabIndex: -1,
|
|
183
|
+
"data-slot": "popover-content",
|
|
184
|
+
"data-side": side,
|
|
185
|
+
"data-align": align,
|
|
186
|
+
"data-state": isExiting ? "closed" : "open",
|
|
187
|
+
"data-flush": flush ? "" : void 0,
|
|
188
|
+
...props,
|
|
189
|
+
ref: mergeRefs(ref, racRef, contentRef),
|
|
190
|
+
className: cn(
|
|
191
|
+
"ui-popover-content origin-[var(--radix-popover-content-transform-origin)]",
|
|
192
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
193
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
194
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
|
195
|
+
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
|
|
196
|
+
className
|
|
197
|
+
),
|
|
198
|
+
style: {
|
|
199
|
+
...racStyle,
|
|
200
|
+
"--radix-popover-trigger-width": "var(--trigger-width)",
|
|
201
|
+
"--radix-popover-content-transform-origin": "var(--trigger-anchor-point)",
|
|
202
|
+
...availableHeight == null ? null : {
|
|
203
|
+
"--radix-popover-content-available-height": typeof availableHeight === "number" ? `${availableHeight}px` : availableHeight
|
|
204
|
+
},
|
|
205
|
+
...style,
|
|
206
|
+
...flush ? { "--popover-space-inset": "0" } : null
|
|
207
|
+
},
|
|
208
|
+
children
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
36
215
|
const PopoverHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { "data-slot": "popover-header", className: cn("ui-popover-header", className), ...props });
|
|
37
216
|
const PopoverTitle = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { "data-slot": "popover-title", className: cn("font-medium", className), ...props });
|
|
38
217
|
const PopoverDescription = ({
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export type ProgressTone = "success" | "warning" | "destructive";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* One slice of a BREAKDOWN bar — how much of the total this state holds, in the SAME unit as every
|
|
5
|
+
* other slice (counts, bytes, yen). Never a percentage: the bar owns the ratio arithmetic, so the
|
|
6
|
+
* caller never has to round three numbers that must still add up to 100.
|
|
7
|
+
*
|
|
8
|
+
* `label` is not decoration. Colour alone cannot carry the meaning of a slice (WCAG 1.4.1), and it
|
|
9
|
+
* is the only thing a screen reader has to say about it, so it is required.
|
|
10
|
+
*/
|
|
11
|
+
export type ProgressSegment = {
|
|
4
12
|
value: number;
|
|
13
|
+
tone: ProgressTone;
|
|
14
|
+
label: string;
|
|
15
|
+
};
|
|
16
|
+
type ProgressBase = Omit<React.HTMLAttributes<HTMLDivElement>, "role" | "children" | "aria-valuenow" | "aria-valuemin" | "aria-valuemax" | "aria-valuetext"> & {
|
|
17
|
+
/**
|
|
18
|
+
* Visible caption under the bar; it also becomes the bar's accessible name. Omit it and the bar
|
|
19
|
+
* falls back to the catalogue name — or to whatever `aria-label`/`aria-labelledby` the caller
|
|
20
|
+
* passes, which is how a bar gets its name from a heading that is already on screen.
|
|
21
|
+
*/
|
|
5
22
|
label?: string;
|
|
23
|
+
};
|
|
24
|
+
/** A METER: one ratio of one whole. */
|
|
25
|
+
type ProgressMeterProps = ProgressBase & {
|
|
26
|
+
value: number;
|
|
6
27
|
tone?: ProgressTone;
|
|
7
28
|
/**
|
|
8
29
|
* Allow `value` to exceed 100 and render an OVER-CAPACITY fill: the bar caps at 100% width but
|
|
@@ -11,5 +32,32 @@ export type ProgressProps = {
|
|
|
11
32
|
* real ratio.
|
|
12
33
|
*/
|
|
13
34
|
over?: boolean;
|
|
35
|
+
segments?: never;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A BREAKDOWN: one whole split into its states.
|
|
39
|
+
*
|
|
40
|
+
* ## Why this is not a meter with three calls
|
|
41
|
+
*
|
|
42
|
+
* A meter answers "how far along?" and has ONE value, so `role="progressbar"` fits it exactly.
|
|
43
|
+
* A breakdown answers "what is this total made of?" — 2 overdue · 3 due soon · 12 done. Stacking
|
|
44
|
+
* three progressbars says the wrong thing three times over (three separate 0–100 measurements),
|
|
45
|
+
* and no ARIA value pattern models a partition. It is a picture of data, so it names itself once
|
|
46
|
+
* as `role="img"` and reads out every slice with its own label and amount.
|
|
47
|
+
*
|
|
48
|
+
* ## Why it is a taller bar than the meter
|
|
49
|
+
*
|
|
50
|
+
* A 0.5rem pill is legible when it carries one fill against one track. Three abutting fills at
|
|
51
|
+
* that height read as a coloured hairline — the ratios, which are the entire point, stop being
|
|
52
|
+
* comparable. So the breakdown takes its own block size and corner, both retunable
|
|
53
|
+
* (`--progress-breakdown-*`).
|
|
54
|
+
*/
|
|
55
|
+
type ProgressBreakdownProps = ProgressBase & {
|
|
56
|
+
segments: ProgressSegment[];
|
|
57
|
+
value?: never;
|
|
58
|
+
tone?: never;
|
|
59
|
+
over?: never;
|
|
14
60
|
};
|
|
15
|
-
export
|
|
61
|
+
export type ProgressProps = ProgressMeterProps | ProgressBreakdownProps;
|
|
62
|
+
export declare function Progress(props: ProgressProps): React.JSX.Element;
|
|
63
|
+
export {};
|