@godxjp/ui 20.2.1 → 21.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/chat-bubble.d.ts +30 -0
- package/dist/components/data-display/chat-bubble.js +229 -0
- package/dist/components/data-display/descriptions.js +4 -1
- package/dist/components/data-display/index.d.ts +4 -2
- package/dist/components/data-display/index.js +5 -2
- package/dist/components/data-display/popover.js +3 -2
- package/dist/components/data-display/scroll-area.js +26 -1
- package/dist/components/data-display/tree.d.ts +8 -0
- package/dist/components/data-display/tree.js +426 -0
- package/dist/components/data-entry/chat-composer.d.ts +50 -0
- package/dist/components/data-entry/chat-composer.js +163 -0
- package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
- package/dist/components/data-entry/chat-suggestion.js +285 -0
- package/dist/components/data-entry/index.d.ts +4 -0
- package/dist/components/data-entry/index.js +4 -0
- package/dist/components/data-entry/textarea.js +3 -1
- package/dist/components/data-entry/tree-utils.d.ts +10 -48
- package/dist/components/data-entry/tree-utils.js +1 -154
- package/dist/i18n/messages/en.json +45 -0
- package/dist/i18n/messages/ja.json +43 -0
- package/dist/i18n/messages/vi.json +44 -0
- package/dist/lib/tree.d.ts +53 -0
- package/dist/lib/tree.js +155 -0
- package/dist/props/components/data-display.prop.d.ts +187 -1
- package/dist/props/components/data-entry.prop.d.ts +127 -0
- package/dist/props/registry.d.ts +140 -3
- package/dist/props/registry.js +202 -3
- package/dist/styles/control.css +7 -0
- package/dist/styles/data-display-layout.css +309 -55
- package/dist/styles/data-entry-layout.css +64 -0
- package/dist/styles/shell-layout.css +2 -1
- package/dist/tokens/base.css +3 -0
- package/dist/tokens/components/chat-bubble.css +36 -0
- package/dist/tokens/components/chat-composer.css +19 -0
- package/dist/tokens/components/data-display.css +0 -6
- package/dist/tokens/components/descriptions.css +4 -0
- package/dist/tokens/components/shell.css +1 -0
- package/dist/tokens/components/tree.css +27 -0
- package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
- package/docs/FRAME-COVERAGE-REPORT.md +8 -3
- package/docs/data-display/chat-bubble.tsx +397 -0
- package/docs/data-display/timeline.tsx +46 -0
- package/docs/data-display/tree.tsx +394 -0
- package/docs/data-entry/chat-composer.tsx +464 -0
- package/docs/data-entry/chat-suggestion.tsx +301 -0
- package/docs/roadmap/ai-chat-components.md +207 -0
- package/docs/roadmap/antd-parity.md +154 -0
- package/docs/roadmap/badge-tag-chip-count.md +172 -0
- package/docs/roadmap/list-masonry.md +159 -0
- package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
- package/docs/roadmap/parity-audit-data-entry.md +344 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
- package/docs/roadmap/parity-backlog.md +79 -0
- package/docs/roadmap/tree-components.md +151 -0
- package/docs/showcase/table-tree-rows.tsx +4 -4
- package/package.json +5 -3
- package/dist/components/data-display/tree-list.d.ts +0 -13
- package/dist/components/data-display/tree-list.js +0 -26
- package/docs/data-display/tree-list.tsx +0 -107
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { ChatBubbleListProp, ChatBubbleProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
export type { ChatBubbleListProp, ChatBubblePlacementProp, ChatBubbleProp, ChatBubbleToneProp, ChatBubbleTypingProp, ChatBubbleVariantProp, ChatMessageProp, } from "../../props/components/data-display.prop.js";
|
|
4
|
+
export type ChatBubbleProps = ChatBubbleProp & Omit<React.HTMLAttributes<HTMLElement>, keyof ChatBubbleProp>;
|
|
5
|
+
/**
|
|
6
|
+
* ChatBubble — ONE message in a conversation: an optional author mark, a header naming the turn,
|
|
7
|
+
* the message body, and a footer for timestamps or per-message actions.
|
|
8
|
+
*
|
|
9
|
+
* It is an `<article>` inside the feed's `role="log"`, and it deliberately owns NO live region of
|
|
10
|
+
* its own: one live region per bubble floods a screen reader on every token of a stream. The feed
|
|
11
|
+
* (`ChatBubbleList`) owns the single `aria-live="polite"` region; a bubble only ever says "I am
|
|
12
|
+
* still being produced" through `aria-busy`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ChatBubble: React.ForwardRefExoticComponent<ChatBubbleProp & Omit<React.HTMLAttributes<HTMLElement>, keyof ChatBubbleProp> & React.RefAttributes<HTMLElement>>;
|
|
15
|
+
export type ChatBubbleListProps = ChatBubbleListProp & Omit<React.HTMLAttributes<HTMLDivElement>, keyof ChatBubbleListProp>;
|
|
16
|
+
/**
|
|
17
|
+
* ChatBubbleList — the message feed.
|
|
18
|
+
*
|
|
19
|
+
* The behaviour that earns this a component rather than a `ScrollArea` + `map` is STICK-TO-BOTTOM:
|
|
20
|
+
* new messages keep the viewport pinned only while the reader is ALREADY at the bottom. The moment
|
|
21
|
+
* they scroll up to re-read something the pin is revoked and stays revoked until they ask for it
|
|
22
|
+
* back — silently yanking a reader to the newest message mid-sentence is a change of context they
|
|
23
|
+
* did not request (WCAG 3.2.5), and it is the classic chat-UI defect. The way back is a real,
|
|
24
|
+
* focusable button that also says how many messages arrived while they were away.
|
|
25
|
+
*
|
|
26
|
+
* GIVE IT A DEFINITE HEIGHT — `className="h-96"`, or a flex/grid parent that hands it a track.
|
|
27
|
+
* The feed scrolls INSIDE itself; with no height it simply grows to its content, nothing ever
|
|
28
|
+
* overflows, and every behaviour above has nothing to act on.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ChatBubbleList: React.ForwardRefExoticComponent<ChatBubbleListProp & Omit<React.HTMLAttributes<HTMLDivElement>, keyof ChatBubbleListProp> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ArrowDown } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { useMediaQuery } from "../../lib/hooks.js";
|
|
7
|
+
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { Skeleton } from "../feedback/skeleton.js";
|
|
9
|
+
import { Button } from "../general/button.js";
|
|
10
|
+
import { Text } from "../general/typography.js";
|
|
11
|
+
import { VisuallyHidden } from "../general/visually-hidden.js";
|
|
12
|
+
import { ScrollArea } from "./scroll-area.js";
|
|
13
|
+
const BODY_SIZE = {
|
|
14
|
+
xs: "2xs",
|
|
15
|
+
sm: "xs",
|
|
16
|
+
md: "sm",
|
|
17
|
+
lg: "lg"
|
|
18
|
+
};
|
|
19
|
+
const META_SIZE = {
|
|
20
|
+
xs: "2xs",
|
|
21
|
+
sm: "2xs",
|
|
22
|
+
md: "xs",
|
|
23
|
+
lg: "sm"
|
|
24
|
+
};
|
|
25
|
+
const TYPING_STEP = 1;
|
|
26
|
+
const TYPING_INTERVAL = 50;
|
|
27
|
+
function resolveTyping(typing) {
|
|
28
|
+
if (typing === void 0 || typing === false) {
|
|
29
|
+
return { enabled: false, step: TYPING_STEP, interval: TYPING_INTERVAL };
|
|
30
|
+
}
|
|
31
|
+
if (typing === true) return { enabled: true, step: TYPING_STEP, interval: TYPING_INTERVAL };
|
|
32
|
+
return {
|
|
33
|
+
enabled: true,
|
|
34
|
+
// A non-positive step would never reach the end of the string and a non-positive interval
|
|
35
|
+
// would spin the timer — both are "the animation never completes", which is the one outcome
|
|
36
|
+
// a reader can neither read nor dismiss.
|
|
37
|
+
step: typing.step != null && typing.step > 0 ? Math.floor(typing.step) : TYPING_STEP,
|
|
38
|
+
interval: typing.interval != null && typing.interval > 0 ? typing.interval : TYPING_INTERVAL
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function useRevealedLength(text, enabled, step, interval) {
|
|
42
|
+
const full = text?.length ?? 0;
|
|
43
|
+
const [revealed, setRevealed] = React.useState(() => enabled ? 0 : full);
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!enabled || text === void 0) {
|
|
46
|
+
setRevealed(text?.length ?? 0);
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
setRevealed(0);
|
|
50
|
+
let shown = 0;
|
|
51
|
+
const timer = setInterval(() => {
|
|
52
|
+
shown = Math.min(text.length, shown + step);
|
|
53
|
+
setRevealed(shown);
|
|
54
|
+
if (shown >= text.length) clearInterval(timer);
|
|
55
|
+
}, interval);
|
|
56
|
+
return () => {
|
|
57
|
+
clearInterval(timer);
|
|
58
|
+
};
|
|
59
|
+
}, [text, enabled, step, interval]);
|
|
60
|
+
return Math.min(revealed, full);
|
|
61
|
+
}
|
|
62
|
+
const TONE_KEY = {
|
|
63
|
+
info: "chat.bubble.tone.info",
|
|
64
|
+
success: "chat.bubble.tone.success",
|
|
65
|
+
warning: "chat.bubble.tone.warning",
|
|
66
|
+
destructive: "chat.bubble.tone.destructive"
|
|
67
|
+
};
|
|
68
|
+
const ChatBubble = React.forwardRef(
|
|
69
|
+
({
|
|
70
|
+
children,
|
|
71
|
+
placement = "start",
|
|
72
|
+
variant = "filled",
|
|
73
|
+
avatar,
|
|
74
|
+
header,
|
|
75
|
+
footer,
|
|
76
|
+
loading = false,
|
|
77
|
+
typing,
|
|
78
|
+
size = "md",
|
|
79
|
+
tone = "default",
|
|
80
|
+
className,
|
|
81
|
+
id,
|
|
82
|
+
...props
|
|
83
|
+
}, ref) => {
|
|
84
|
+
const { t } = useTranslation();
|
|
85
|
+
const generatedId = React.useId();
|
|
86
|
+
const headerId = `${id ?? generatedId}-header`;
|
|
87
|
+
const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
88
|
+
const text = typeof children === "string" ? children : void 0;
|
|
89
|
+
const { enabled, step, interval } = resolveTyping(typing);
|
|
90
|
+
const animates = enabled && !loading && text !== void 0 && !reduceMotion;
|
|
91
|
+
const revealed = useRevealedLength(text, animates, step, interval);
|
|
92
|
+
const streaming = animates && revealed < (text?.length ?? 0);
|
|
93
|
+
const body = loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-chat-bubble-skeleton", "aria-hidden": "true", children: [
|
|
95
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "ui-chat-bubble-skeleton-line" }),
|
|
96
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "ui-chat-bubble-skeleton-line ui-chat-bubble-skeleton-line--short" })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: t("chat.bubble.loading") })
|
|
99
|
+
] }) : streaming ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
100
|
+
/* @__PURE__ */ jsxs(Text, { as: "div", size: BODY_SIZE[size], whitespace: "pre-wrap", "aria-hidden": "true", children: [
|
|
101
|
+
text?.slice(0, revealed),
|
|
102
|
+
/* @__PURE__ */ jsx("span", { className: "ui-chat-bubble-caret" })
|
|
103
|
+
] }),
|
|
104
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: t("chat.bubble.typing") })
|
|
105
|
+
] }) : /* @__PURE__ */ jsx(
|
|
106
|
+
Text,
|
|
107
|
+
{
|
|
108
|
+
as: "div",
|
|
109
|
+
size: BODY_SIZE[size],
|
|
110
|
+
whitespace: text !== void 0 ? "pre-wrap" : void 0,
|
|
111
|
+
children
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
return /* @__PURE__ */ jsxs(
|
|
115
|
+
"article",
|
|
116
|
+
{
|
|
117
|
+
ref,
|
|
118
|
+
id,
|
|
119
|
+
"data-slot": "chat-bubble",
|
|
120
|
+
"data-placement": placement,
|
|
121
|
+
"data-variant": variant,
|
|
122
|
+
"data-size": size,
|
|
123
|
+
"data-tone": tone,
|
|
124
|
+
"aria-busy": loading || streaming ? true : void 0,
|
|
125
|
+
"aria-labelledby": header != null ? headerId : void 0,
|
|
126
|
+
className: cn("ui-chat-bubble", className),
|
|
127
|
+
...props,
|
|
128
|
+
children: [
|
|
129
|
+
avatar != null ? /* @__PURE__ */ jsx("span", { "data-slot": "chat-bubble-avatar", className: "ui-chat-bubble-avatar", children: avatar }) : null,
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "chat-bubble-column", className: "ui-chat-bubble-column", children: [
|
|
131
|
+
header != null ? /* @__PURE__ */ jsx(
|
|
132
|
+
Text,
|
|
133
|
+
{
|
|
134
|
+
as: "div",
|
|
135
|
+
id: headerId,
|
|
136
|
+
className: "ui-chat-bubble-header",
|
|
137
|
+
size: META_SIZE[size],
|
|
138
|
+
tone: "muted",
|
|
139
|
+
weight: "medium",
|
|
140
|
+
children: header
|
|
141
|
+
}
|
|
142
|
+
) : null,
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "chat-bubble-body", className: "ui-chat-bubble-body", children: [
|
|
144
|
+
tone !== "default" ? /* @__PURE__ */ jsx(VisuallyHidden, { children: t(TONE_KEY[tone]) }) : null,
|
|
145
|
+
body
|
|
146
|
+
] }),
|
|
147
|
+
footer != null ? /* @__PURE__ */ jsx(Text, { as: "div", className: "ui-chat-bubble-footer", size: META_SIZE[size], tone: "muted", children: footer }) : null
|
|
148
|
+
] })
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
ChatBubble.displayName = "ChatBubble";
|
|
155
|
+
const ChatBubbleList = React.forwardRef(
|
|
156
|
+
({ items, roles, autoScroll = true, label, className, id, ...props }, ref) => {
|
|
157
|
+
const { t } = useTranslation();
|
|
158
|
+
const viewportRef = React.useRef(null);
|
|
159
|
+
const [anchored, setAnchored] = React.useState(true);
|
|
160
|
+
const seen = React.useRef(items.length);
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
if (anchored) seen.current = items.length;
|
|
163
|
+
}, [anchored, items.length]);
|
|
164
|
+
const pending = anchored ? 0 : Math.max(0, items.length - seen.current);
|
|
165
|
+
const jumpToLatest = React.useCallback(() => {
|
|
166
|
+
const viewport = viewportRef.current;
|
|
167
|
+
if (viewport) {
|
|
168
|
+
const bottom = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
|
169
|
+
if (typeof viewport.scrollTo === "function") {
|
|
170
|
+
viewport.scrollTo({ top: bottom, behavior: "instant" });
|
|
171
|
+
} else {
|
|
172
|
+
viewport.scrollTop = bottom;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
setAnchored(true);
|
|
176
|
+
}, []);
|
|
177
|
+
return /* @__PURE__ */ jsxs(
|
|
178
|
+
"div",
|
|
179
|
+
{
|
|
180
|
+
ref,
|
|
181
|
+
id,
|
|
182
|
+
"data-slot": "chat-bubble-list",
|
|
183
|
+
"data-anchored": anchored ? "" : void 0,
|
|
184
|
+
className: cn("ui-chat-bubble-list", className),
|
|
185
|
+
...props,
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ jsx(
|
|
188
|
+
ScrollArea,
|
|
189
|
+
{
|
|
190
|
+
className: "ui-chat-bubble-list-scroller",
|
|
191
|
+
anchor: autoScroll ? "bottom" : "none",
|
|
192
|
+
viewportRef,
|
|
193
|
+
onAnchoredChange: setAnchored,
|
|
194
|
+
children: /* @__PURE__ */ jsx(
|
|
195
|
+
"div",
|
|
196
|
+
{
|
|
197
|
+
role: "log",
|
|
198
|
+
"aria-live": "polite",
|
|
199
|
+
"aria-label": label ?? t("chat.list.label"),
|
|
200
|
+
"data-slot": "chat-bubble-list-feed",
|
|
201
|
+
className: "ui-chat-bubble-list-feed",
|
|
202
|
+
children: items.map(({ id: messageId, role, content, ...message }) => /* @__PURE__ */ jsx(
|
|
203
|
+
ChatBubble,
|
|
204
|
+
{
|
|
205
|
+
id: messageId,
|
|
206
|
+
...role != null ? roles?.[role] : void 0,
|
|
207
|
+
...message,
|
|
208
|
+
children: content
|
|
209
|
+
},
|
|
210
|
+
messageId
|
|
211
|
+
))
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
),
|
|
216
|
+
anchored ? null : /* @__PURE__ */ jsx("div", { "data-slot": "chat-bubble-list-jump", className: "ui-chat-bubble-list-jump", children: /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", variant: "secondary", shape: "pill", onClick: jumpToLatest, children: [
|
|
217
|
+
/* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" }),
|
|
218
|
+
pending > 0 ? t("chat.list.newMessages", { count: pending }) : t("chat.list.jumpToLatest")
|
|
219
|
+
] }) })
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
ChatBubbleList.displayName = "ChatBubbleList";
|
|
226
|
+
export {
|
|
227
|
+
ChatBubble,
|
|
228
|
+
ChatBubbleList
|
|
229
|
+
};
|
|
@@ -104,7 +104,10 @@ function DescriptionsItem({ label, mono, span, className, children }) {
|
|
|
104
104
|
"data-slot": "descriptions-label",
|
|
105
105
|
"data-label-align": endAlignedLabel ? "end" : void 0,
|
|
106
106
|
className: cn(
|
|
107
|
-
|
|
107
|
+
// Size comes from the token pair, NOT from `text-xs`: a hard-coded utility here left the
|
|
108
|
+
// label one type step below the value beside it, and out of reach of a service theme.
|
|
109
|
+
// The muted colour is what distinguishes a label from its value.
|
|
110
|
+
"text-muted-foreground text-[length:var(--descriptions-label-font-size)] leading-[var(--descriptions-label-line-height)]",
|
|
108
111
|
// `end`-align only ever applies in horizontal layout — same guard `Form` uses, so a
|
|
109
112
|
endAlignedLabel && "text-end"
|
|
110
113
|
),
|
|
@@ -26,8 +26,6 @@ export type { CodeBlockProp, CodeBlockProps } from "./code-block.js";
|
|
|
26
26
|
export { Prose } from "./prose.js";
|
|
27
27
|
export type { ProseProp, ProseProps } from "./prose.js";
|
|
28
28
|
export type { ProgressProps, ProgressSegment, ProgressTone } from "./progress.js";
|
|
29
|
-
export { TreeList } from "./tree-list.js";
|
|
30
|
-
export type { TreeListItem, TreeListProps } from "./tree-list.js";
|
|
31
29
|
export { Timeline } from "./timeline.js";
|
|
32
30
|
export type { TimelineItem, TimelineProps } from "./timeline.js";
|
|
33
31
|
export { TimelineGrid } from "./timeline-grid.js";
|
|
@@ -43,3 +41,7 @@ export { PermissionMatrix } from "./permission-matrix.js";
|
|
|
43
41
|
export type { PermissionMatrixGrantsProp, PermissionMatrixPermissionProp, PermissionMatrixProp, PermissionMatrixProps, PermissionMatrixRoleProp, } from "./permission-matrix.js";
|
|
44
42
|
export { RangeTimeline } from "./range-timeline.js";
|
|
45
43
|
export type { RangeTimelineProps, RangeTimelineRow } from "./range-timeline.js";
|
|
44
|
+
export { ChatBubble, ChatBubbleList } from "./chat-bubble.js";
|
|
45
|
+
export type { ChatBubbleListProp, ChatBubbleListProps, ChatBubblePlacementProp, ChatBubbleProp, ChatBubbleProps, ChatBubbleToneProp, ChatBubbleTypingProp, ChatBubbleVariantProp, ChatMessageProp, } from "./chat-bubble.js";
|
|
46
|
+
export { Tree } from "./tree.js";
|
|
47
|
+
export type { TreeNodeProp, TreeProp, TreeProps } from "./tree.js";
|
|
@@ -28,7 +28,6 @@ import { EmptyState } from "./empty-state.js";
|
|
|
28
28
|
import { Progress } from "./progress.js";
|
|
29
29
|
import { CodeBlock } from "./code-block.js";
|
|
30
30
|
import { Prose } from "./prose.js";
|
|
31
|
-
import { TreeList } from "./tree-list.js";
|
|
32
31
|
import { Timeline } from "./timeline.js";
|
|
33
32
|
import { TimelineGrid } from "./timeline-grid.js";
|
|
34
33
|
import {
|
|
@@ -55,6 +54,8 @@ import {
|
|
|
55
54
|
} from "./carousel.js";
|
|
56
55
|
import { PermissionMatrix } from "./permission-matrix.js";
|
|
57
56
|
import { RangeTimeline } from "./range-timeline.js";
|
|
57
|
+
import { ChatBubble, ChatBubbleList } from "./chat-bubble.js";
|
|
58
|
+
import { Tree } from "./tree.js";
|
|
58
59
|
export {
|
|
59
60
|
Accordion,
|
|
60
61
|
AccordionContent,
|
|
@@ -79,6 +80,8 @@ export {
|
|
|
79
80
|
CarouselItem,
|
|
80
81
|
CarouselNext,
|
|
81
82
|
CarouselPrevious,
|
|
83
|
+
ChatBubble,
|
|
84
|
+
ChatBubbleList,
|
|
82
85
|
CodeBlock,
|
|
83
86
|
Collapsible,
|
|
84
87
|
CollapsibleContent,
|
|
@@ -119,7 +122,7 @@ export {
|
|
|
119
122
|
TableRow,
|
|
120
123
|
Timeline,
|
|
121
124
|
TimelineGrid,
|
|
122
|
-
|
|
125
|
+
Tree,
|
|
123
126
|
flexRender,
|
|
124
127
|
useCarousel
|
|
125
128
|
};
|
|
@@ -165,7 +165,7 @@ function PopoverContent({
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
}, [root.open]);
|
|
168
|
-
const { open, setOpen, triggerRef } = root;
|
|
168
|
+
const { open, setOpen, triggerRef, anchorRef, anchored } = root;
|
|
169
169
|
React.useEffect(() => {
|
|
170
170
|
if (!open) return;
|
|
171
171
|
const onPointerDown = (event) => {
|
|
@@ -173,11 +173,12 @@ function PopoverContent({
|
|
|
173
173
|
if (!target) return;
|
|
174
174
|
if (contentRef.current?.contains(target)) return;
|
|
175
175
|
if (triggerRef.current?.contains(target)) return;
|
|
176
|
+
if (anchored && anchorRef.current?.contains(target)) return;
|
|
176
177
|
setOpen(false);
|
|
177
178
|
};
|
|
178
179
|
document.addEventListener("pointerdown", onPointerDown);
|
|
179
180
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
180
|
-
}, [open, setOpen, triggerRef]);
|
|
181
|
+
}, [open, setOpen, triggerRef, anchorRef, anchored]);
|
|
181
182
|
const overlayPortalContainer = useOverlayPortalContainer(portalContainer);
|
|
182
183
|
return /* @__PURE__ */ jsx(
|
|
183
184
|
AriaPopover,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLayoutEffect } from "@react-aria/utils";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
5
6
|
import { cn } from "../../lib/utils.js";
|
|
@@ -112,6 +113,18 @@ function useBottomAnchor(viewport, enabled, offset, onAnchoredChange) {
|
|
|
112
113
|
};
|
|
113
114
|
}, [viewport, enabled, offset]);
|
|
114
115
|
}
|
|
116
|
+
function useAmbientDirection(root, provided) {
|
|
117
|
+
const [ambient, setAmbient] = React.useState(void 0);
|
|
118
|
+
useLayoutEffect(() => {
|
|
119
|
+
if (provided !== void 0) return;
|
|
120
|
+
const parent = root?.parentElement;
|
|
121
|
+
if (!parent || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setAmbient(window.getComputedStyle(parent).direction === "rtl" ? "rtl" : "ltr");
|
|
125
|
+
}, [root, provided]);
|
|
126
|
+
return provided ?? ambient;
|
|
127
|
+
}
|
|
115
128
|
const ScrollArea = React.forwardRef(
|
|
116
129
|
({
|
|
117
130
|
className,
|
|
@@ -121,9 +134,20 @@ const ScrollArea = React.forwardRef(
|
|
|
121
134
|
anchorOffset,
|
|
122
135
|
onAnchoredChange,
|
|
123
136
|
orientation = "vertical",
|
|
137
|
+
dir,
|
|
124
138
|
...props
|
|
125
139
|
}, ref) => {
|
|
126
140
|
const [viewport, setViewport] = React.useState(null);
|
|
141
|
+
const [root, setRoot] = React.useState(null);
|
|
142
|
+
const attachRoot = React.useCallback(
|
|
143
|
+
(node) => {
|
|
144
|
+
setRoot(node);
|
|
145
|
+
if (typeof ref === "function") ref(node);
|
|
146
|
+
else if (ref) ref.current = node;
|
|
147
|
+
},
|
|
148
|
+
[ref]
|
|
149
|
+
);
|
|
150
|
+
const direction = useAmbientDirection(root, dir);
|
|
127
151
|
const attachViewport = React.useCallback(
|
|
128
152
|
(node) => {
|
|
129
153
|
setViewport(node);
|
|
@@ -137,7 +161,8 @@ const ScrollArea = React.forwardRef(
|
|
|
137
161
|
return /* @__PURE__ */ jsxs(
|
|
138
162
|
ScrollAreaPrimitive.Root,
|
|
139
163
|
{
|
|
140
|
-
ref,
|
|
164
|
+
ref: attachRoot,
|
|
165
|
+
dir: direction,
|
|
141
166
|
className: cn("relative overflow-hidden", className),
|
|
142
167
|
...props,
|
|
143
168
|
children: [
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TreeProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
export type { TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* `ref` reaches the `role="tree"` container — the element a page scrolls to, measures, or
|
|
6
|
+
* focus-manages. Every unknown prop (`aria-*`, `data-*`) rides `...ariaProps` onto the same node.
|
|
7
|
+
*/
|
|
8
|
+
export declare const Tree: React.ForwardRefExoticComponent<TreeProp & React.RefAttributes<HTMLDivElement>>;
|