@godxjp/ui 23.2.1 → 23.4.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/app/timezones.js +2 -1
- package/dist/components/charts/chart-cartesian.js +2 -4
- package/dist/components/charts/chart-frame.js +2 -1
- package/dist/components/data-display/card.d.ts +87 -1
- package/dist/components/data-display/card.js +88 -15
- package/dist/components/data-display/index.d.ts +5 -1
- package/dist/components/data-display/index.js +5 -0
- package/dist/components/data-display/thought-chain.d.ts +42 -0
- package/dist/components/data-display/thought-chain.js +177 -0
- package/dist/components/data-display/welcome.d.ts +26 -0
- package/dist/components/data-display/welcome.js +37 -0
- package/dist/components/data-entry/attachments.d.ts +19 -0
- package/dist/components/data-entry/attachments.js +322 -0
- package/dist/components/data-entry/date-picker.js +12 -14
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/number-input.js +2 -1
- package/dist/components/feedback/dialog.d.ts +10 -7
- package/dist/components/feedback/dialog.js +40 -17
- package/dist/components/feedback/index.d.ts +3 -3
- package/dist/components/feedback/index.js +2 -0
- package/dist/components/feedback/skeleton.d.ts +11 -2
- package/dist/components/feedback/skeleton.js +8 -0
- package/dist/components/general/actions.d.ts +59 -0
- package/dist/components/general/actions.js +253 -0
- package/dist/components/general/activity.js +1 -0
- package/dist/components/general/button.js +2 -1
- package/dist/components/general/float-button.d.ts +41 -0
- package/dist/components/general/float-button.js +336 -0
- package/dist/components/general/index.d.ts +6 -2
- package/dist/components/general/index.js +12 -1
- package/dist/components/general/typography.d.ts +79 -4
- package/dist/components/general/typography.js +598 -54
- package/dist/components/layout/draggable-panel.d.ts +18 -0
- package/dist/components/layout/draggable-panel.js +189 -0
- package/dist/components/layout/error-surface.js +3 -2
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/navigation/conversations.d.ts +39 -0
- package/dist/components/navigation/conversations.js +371 -0
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/ui/card.js +1 -0
- package/dist/components/ui/toggle.js +2 -1
- package/dist/i18n/messages/en.json +53 -0
- package/dist/i18n/messages/ja.json +53 -0
- package/dist/i18n/messages/vi.json +53 -0
- package/dist/i18n/translate.js +3 -2
- package/dist/lib/datetime/picker-format.js +2 -1
- package/dist/lib/format.js +3 -2
- package/dist/lib/intl-cache.d.ts +32 -0
- package/dist/lib/intl-cache.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +143 -1
- package/dist/props/components/data-entry.prop.d.ts +87 -0
- package/dist/props/components/feedback.prop.d.ts +53 -1
- package/dist/props/components/general.prop.d.ts +440 -3
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +63 -1
- package/dist/props/components/navigation.prop.d.ts +150 -0
- package/dist/props/registry.d.ts +431 -0
- package/dist/props/registry.js +551 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +113 -0
- package/dist/styles/alert-layout.css +22 -0
- package/dist/styles/card-layout.css +34 -0
- package/dist/styles/core.css +1 -0
- package/dist/styles/data-display-layout.css +236 -0
- package/dist/styles/float-button-layout.css +168 -0
- package/dist/styles/form-layout.css +11 -1
- package/dist/styles/index.css +1 -0
- package/dist/styles/layout.css +196 -0
- package/dist/styles/navigation-layout.css +139 -0
- package/dist/styles/text-layout.css +156 -0
- package/dist/tokens/axes.css +5 -0
- package/dist/tokens/base.css +7 -0
- package/dist/tokens/components/actions.css +14 -0
- package/dist/tokens/components/attachments.css +15 -0
- package/dist/tokens/components/conversations.css +28 -0
- package/dist/tokens/components/draggable-panel.css +25 -0
- package/dist/tokens/components/float-button.css +39 -0
- package/dist/tokens/components/text.css +13 -0
- package/dist/tokens/components/thought-chain.css +31 -0
- package/dist/tokens/components/welcome.css +19 -0
- package/dist/tokens/foundation.css +6 -4
- package/docs/DESIGN-AUTHORITY.md +133 -20
- package/docs/FRAME-COVERAGE-REPORT.md +5 -2
- package/docs/data-display/card/examples/tab-list.tsx +97 -0
- package/docs/data-display/thought-chain.tsx +176 -0
- package/docs/data-display/welcome.tsx +115 -0
- package/docs/data-entry/attachments.tsx +160 -0
- package/docs/feedback/dialog.tsx +50 -0
- package/docs/general/actions.tsx +175 -0
- package/docs/general/float-button.tsx +133 -0
- package/docs/general/typography.tsx +63 -1
- package/docs/layout/draggable-panel.tsx +127 -0
- package/docs/navigation/conversations.tsx +176 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +17 -16
- package/package.json +20 -4
- package/scripts/_agent-setup.mjs +36 -5
- package/scripts/guinea-pig-skill.md +14 -0
- package/scripts/ui-audit.mjs +69 -9
|
@@ -1,13 +1,379 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1
3
|
import * as React from "react";
|
|
4
|
+
import { Check, Copy, LoaderCircle, Pencil } from "lucide-react";
|
|
2
5
|
import { cn } from "../../lib/utils.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
8
|
+
import { Textarea } from "../data-entry/textarea.js";
|
|
9
|
+
const COPIED_RESET_MS = 3e3;
|
|
10
|
+
const DECORATION_ELEMENTS = [
|
|
11
|
+
["strong", "strong"],
|
|
12
|
+
["underline", "u"],
|
|
13
|
+
["delete", "del"],
|
|
14
|
+
["code", "code"],
|
|
15
|
+
["mark", "mark"],
|
|
16
|
+
["keyboard", "kbd"],
|
|
17
|
+
["italic", "i"]
|
|
18
|
+
];
|
|
19
|
+
function toList(value) {
|
|
20
|
+
if (value === void 0) return [];
|
|
21
|
+
return Array.isArray(value) ? value : [value];
|
|
22
|
+
}
|
|
23
|
+
function getNode(node, fallback, needDom = false) {
|
|
24
|
+
if (node === true || node === void 0) return fallback;
|
|
25
|
+
if (node === false) return needDom ? fallback : null;
|
|
26
|
+
return node || (needDom ? fallback : null);
|
|
27
|
+
}
|
|
28
|
+
function toCopyConfigList(value) {
|
|
29
|
+
if (value === false) return [false, false];
|
|
30
|
+
return toList(value);
|
|
31
|
+
}
|
|
32
|
+
function useMergedConfig(propConfig, template) {
|
|
33
|
+
const support = Boolean(propConfig);
|
|
34
|
+
return React.useMemo(() => {
|
|
35
|
+
const isObject = propConfig !== null && typeof propConfig === "object";
|
|
36
|
+
return [support, { ...template, ...support && isObject ? propConfig : null }];
|
|
37
|
+
}, [support, propConfig]);
|
|
38
|
+
}
|
|
39
|
+
function useControlledFlag(initial, controlled) {
|
|
40
|
+
const [internal, setInternal] = React.useState(initial);
|
|
41
|
+
return [controlled ?? internal, setInternal];
|
|
42
|
+
}
|
|
43
|
+
const TYPE_TO_TONE = {
|
|
44
|
+
secondary: "muted",
|
|
45
|
+
success: "success",
|
|
46
|
+
warning: "warning",
|
|
47
|
+
danger: "destructive"
|
|
48
|
+
};
|
|
49
|
+
async function writeClipboard(text, format) {
|
|
50
|
+
if (format === "text/html" && typeof ClipboardItem !== "undefined") {
|
|
51
|
+
await navigator.clipboard.write([
|
|
52
|
+
new ClipboardItem({
|
|
53
|
+
"text/html": new Blob([text], { type: "text/html" }),
|
|
54
|
+
"text/plain": new Blob([text], { type: "text/plain" })
|
|
55
|
+
})
|
|
56
|
+
]);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await navigator.clipboard.writeText(text);
|
|
60
|
+
}
|
|
61
|
+
function childrenToText(children) {
|
|
62
|
+
return React.Children.toArray(children).map((node) => typeof node === "string" || typeof node === "number" ? String(node) : "").join("");
|
|
63
|
+
}
|
|
64
|
+
function ActionTooltip({ title, children }) {
|
|
65
|
+
if (title === null || title === void 0 || title === false || title === "") return children;
|
|
66
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children }),
|
|
68
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: title })
|
|
69
|
+
] });
|
|
70
|
+
}
|
|
71
|
+
const TypographyAction = React.forwardRef(({ slot, className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
ref,
|
|
75
|
+
type: "button",
|
|
76
|
+
"data-slot": slot,
|
|
77
|
+
className: cn("ui-typography-action", className),
|
|
78
|
+
...props
|
|
79
|
+
}
|
|
80
|
+
));
|
|
81
|
+
TypographyAction.displayName = "TypographyAction";
|
|
82
|
+
function TypographyEditor({
|
|
83
|
+
value,
|
|
84
|
+
ariaLabel,
|
|
85
|
+
maxLength,
|
|
86
|
+
autoSize = true,
|
|
87
|
+
enterIcon,
|
|
88
|
+
className,
|
|
89
|
+
style,
|
|
90
|
+
onSave,
|
|
91
|
+
onCancel,
|
|
92
|
+
onEnd
|
|
93
|
+
}) {
|
|
94
|
+
const ref = React.useRef(null);
|
|
95
|
+
const inComposition = React.useRef(false);
|
|
96
|
+
const lastKey = React.useRef(null);
|
|
97
|
+
const [current, setCurrent] = React.useState(value);
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
setCurrent(value);
|
|
100
|
+
}, [value]);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
const node = ref.current;
|
|
103
|
+
if (!node) return;
|
|
104
|
+
node.focus();
|
|
105
|
+
node.setSelectionRange(node.value.length, node.value.length);
|
|
106
|
+
}, []);
|
|
107
|
+
const confirm = () => {
|
|
108
|
+
onSave(current.trim());
|
|
109
|
+
};
|
|
110
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "typography-edit", className: cn("ui-typography-edit", className), style, children: [
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
Textarea,
|
|
113
|
+
{
|
|
114
|
+
ref,
|
|
115
|
+
variant: "borderless",
|
|
116
|
+
className: "ui-typography-edit-textarea",
|
|
117
|
+
rows: 1,
|
|
118
|
+
autoSize,
|
|
119
|
+
maxLength,
|
|
120
|
+
"aria-label": ariaLabel,
|
|
121
|
+
value: current,
|
|
122
|
+
onChange: (event) => {
|
|
123
|
+
setCurrent(event.target.value.replace(/[\n\r]/g, ""));
|
|
124
|
+
},
|
|
125
|
+
onCompositionStart: () => {
|
|
126
|
+
inComposition.current = true;
|
|
127
|
+
},
|
|
128
|
+
onCompositionEnd: () => {
|
|
129
|
+
inComposition.current = false;
|
|
130
|
+
},
|
|
131
|
+
onKeyDown: (event) => {
|
|
132
|
+
if (inComposition.current) return;
|
|
133
|
+
lastKey.current = event.key;
|
|
134
|
+
},
|
|
135
|
+
onKeyUp: (event) => {
|
|
136
|
+
if (lastKey.current !== event.key || inComposition.current || event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (event.key === "Enter") {
|
|
140
|
+
confirm();
|
|
141
|
+
onEnd?.();
|
|
142
|
+
} else if (event.key === "Escape") {
|
|
143
|
+
onCancel();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
onBlur: confirm
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
enterIcon !== null ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "ui-typography-edit-confirm", children: enterIcon ?? "\u23CE" }) : null
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
function useTypographyBlock(props, allowRows) {
|
|
153
|
+
const { t } = useTranslation();
|
|
154
|
+
const { children, copyable, editable, ellipsis, actions, disabled } = props;
|
|
155
|
+
const placement = actions?.placement ?? "end";
|
|
156
|
+
const [enableEdit, editConfig] = useMergedConfig(editable);
|
|
157
|
+
const [editing, setEditing] = useControlledFlag(false, editConfig.editing);
|
|
158
|
+
const triggerType = editConfig.triggerType ?? ["icon"];
|
|
159
|
+
const editButtonRef = React.useRef(null);
|
|
160
|
+
const wasEditing = React.useRef(false);
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
if (!editing && wasEditing.current) editButtonRef.current?.focus();
|
|
163
|
+
wasEditing.current = editing;
|
|
164
|
+
}, [editing]);
|
|
165
|
+
const startEditing = (event) => {
|
|
166
|
+
event?.preventDefault();
|
|
167
|
+
editConfig.onStart?.();
|
|
168
|
+
setEditing(true);
|
|
169
|
+
};
|
|
170
|
+
const [enableCopy, copyConfig] = useMergedConfig(copyable);
|
|
171
|
+
const [copied, setCopied] = React.useState(false);
|
|
172
|
+
const [copyLoading, setCopyLoading] = React.useState(false);
|
|
173
|
+
const copyTimer = React.useRef(null);
|
|
174
|
+
React.useEffect(
|
|
175
|
+
() => () => {
|
|
176
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
177
|
+
},
|
|
178
|
+
[]
|
|
179
|
+
);
|
|
180
|
+
const onCopyClick = async (event) => {
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
setCopyLoading(true);
|
|
184
|
+
try {
|
|
185
|
+
const configured = typeof copyConfig.text === "function" ? await copyConfig.text() : copyConfig.text;
|
|
186
|
+
await writeClipboard(configured || childrenToText(children) || "", copyConfig.format);
|
|
187
|
+
setCopyLoading(false);
|
|
188
|
+
setCopied(true);
|
|
189
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
190
|
+
copyTimer.current = setTimeout(() => {
|
|
191
|
+
setCopied(false);
|
|
192
|
+
}, COPIED_RESET_MS);
|
|
193
|
+
copyConfig.onCopy?.(event);
|
|
194
|
+
} catch {
|
|
195
|
+
setCopyLoading(false);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const [enableEllipsis, ellipsisConfig] = useMergedConfig(ellipsis);
|
|
199
|
+
const [expanded, setExpanded] = useControlledFlag(
|
|
200
|
+
ellipsisConfig.defaultExpanded ?? false,
|
|
201
|
+
ellipsisConfig.expanded
|
|
202
|
+
);
|
|
203
|
+
const rows = allowRows ? ellipsisConfig.rows ?? 1 : 1;
|
|
204
|
+
const expandable = allowRows ? ellipsisConfig.expandable : void 0;
|
|
205
|
+
const clamping = enableEllipsis && (!expanded || expandable === "collapsible");
|
|
206
|
+
const [isEllipsis, setIsEllipsis] = React.useState(false);
|
|
207
|
+
const elementRef = React.useRef(null);
|
|
208
|
+
const onEllipsisRef = React.useRef(ellipsisConfig.onEllipsis);
|
|
209
|
+
onEllipsisRef.current = ellipsisConfig.onEllipsis;
|
|
210
|
+
const measure = React.useCallback(() => {
|
|
211
|
+
const node = elementRef.current;
|
|
212
|
+
if (!node || !enableEllipsis) return;
|
|
213
|
+
const next = rows > 1 ? node.scrollHeight > node.clientHeight : node.scrollWidth > node.clientWidth;
|
|
214
|
+
setIsEllipsis((prev) => {
|
|
215
|
+
if (prev === next) return prev;
|
|
216
|
+
onEllipsisRef.current?.(next);
|
|
217
|
+
return next;
|
|
218
|
+
});
|
|
219
|
+
}, [enableEllipsis, rows]);
|
|
220
|
+
const measureRef = React.useCallback((node) => {
|
|
221
|
+
elementRef.current = node;
|
|
222
|
+
}, []);
|
|
223
|
+
React.useLayoutEffect(() => {
|
|
224
|
+
measure();
|
|
225
|
+
}, [measure, children, expanded, clamping]);
|
|
226
|
+
React.useEffect(() => {
|
|
227
|
+
const node = elementRef.current;
|
|
228
|
+
if (!node || !enableEllipsis || typeof ResizeObserver === "undefined") return void 0;
|
|
229
|
+
const observer = new ResizeObserver(() => {
|
|
230
|
+
measure();
|
|
231
|
+
});
|
|
232
|
+
observer.observe(node);
|
|
233
|
+
return () => {
|
|
234
|
+
observer.disconnect();
|
|
235
|
+
};
|
|
236
|
+
}, [measure, enableEllipsis]);
|
|
237
|
+
const onExpandClick = (event) => {
|
|
238
|
+
const next = !expanded;
|
|
239
|
+
setExpanded(next);
|
|
240
|
+
ellipsisConfig.onExpand?.(event, { expanded: next });
|
|
241
|
+
};
|
|
242
|
+
const copyLabels = toCopyConfigList(copyConfig.tooltips);
|
|
243
|
+
const copyIcons = toCopyConfigList(copyConfig.icon);
|
|
244
|
+
const systemCopyLabel = copied ? t("ui.typography.copied") : t("ui.typography.copy");
|
|
245
|
+
const copyTitle = getNode(copyLabels[copied ? 1 : 0], systemCopyLabel);
|
|
246
|
+
const copyAriaLabel = typeof copyTitle === "string" ? copyTitle : systemCopyLabel;
|
|
247
|
+
const copyNode = enableCopy ? /* @__PURE__ */ jsx(ActionTooltip, { title: copyTitle, children: /* @__PURE__ */ jsx(
|
|
248
|
+
TypographyAction,
|
|
249
|
+
{
|
|
250
|
+
slot: "typography-copy",
|
|
251
|
+
"data-copied": copied ? "" : void 0,
|
|
252
|
+
"aria-label": copyAriaLabel,
|
|
253
|
+
tabIndex: copyConfig.tabIndex,
|
|
254
|
+
onClick: onCopyClick,
|
|
255
|
+
children: copied ? getNode(copyIcons[1], /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "ui-typography-icon" }), true) : getNode(
|
|
256
|
+
copyIcons[0],
|
|
257
|
+
copyLoading ? /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": true, className: "ui-typography-icon ui-typography-icon-spin" }) : /* @__PURE__ */ jsx(Copy, { "aria-hidden": true, className: "ui-typography-icon" }),
|
|
258
|
+
true
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
) }, "copy") : null;
|
|
262
|
+
const editTitle = toList(editConfig.tooltip)[0] ?? t("ui.typography.edit");
|
|
263
|
+
const editNode = enableEdit && triggerType.includes("icon") ? /* @__PURE__ */ jsx(ActionTooltip, { title: editConfig.tooltip === false ? null : editTitle, children: /* @__PURE__ */ jsx(
|
|
264
|
+
TypographyAction,
|
|
265
|
+
{
|
|
266
|
+
ref: editButtonRef,
|
|
267
|
+
slot: "typography-edit-trigger",
|
|
268
|
+
"aria-label": typeof editTitle === "string" ? editTitle : t("ui.typography.edit"),
|
|
269
|
+
tabIndex: editConfig.tabIndex,
|
|
270
|
+
onClick: startEditing,
|
|
271
|
+
children: editConfig.icon ?? /* @__PURE__ */ jsx(Pencil, { "aria-hidden": true, className: "ui-typography-icon" })
|
|
272
|
+
}
|
|
273
|
+
) }, "edit") : null;
|
|
274
|
+
const showExpand = Boolean(expandable) && (isEllipsis || expanded);
|
|
275
|
+
const expandSymbol = typeof ellipsisConfig.symbol === "function" ? ellipsisConfig.symbol(expanded) : ellipsisConfig.symbol ?? (expanded ? t("ui.typography.collapse") : t("ui.typography.expand"));
|
|
276
|
+
const expandNode = showExpand ? /* @__PURE__ */ jsx(
|
|
277
|
+
TypographyAction,
|
|
278
|
+
{
|
|
279
|
+
slot: expanded ? "typography-collapse" : "typography-expand",
|
|
280
|
+
"aria-expanded": expanded,
|
|
281
|
+
"aria-label": expanded ? t("ui.typography.collapse") : t("ui.typography.expand"),
|
|
282
|
+
onClick: onExpandClick,
|
|
283
|
+
children: expandSymbol
|
|
284
|
+
},
|
|
285
|
+
"expand"
|
|
286
|
+
) : null;
|
|
287
|
+
const hasActions = Boolean(copyNode || editNode || expandNode);
|
|
288
|
+
const actionsNode = hasActions ? /* @__PURE__ */ jsxs(
|
|
289
|
+
"span",
|
|
290
|
+
{
|
|
291
|
+
"data-slot": "typography-actions",
|
|
292
|
+
"data-placement": placement,
|
|
293
|
+
className: "ui-typography-actions",
|
|
294
|
+
children: [
|
|
295
|
+
expandNode,
|
|
296
|
+
editNode,
|
|
297
|
+
copyNode
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
"actions"
|
|
301
|
+
) : null;
|
|
302
|
+
let decorated = children;
|
|
303
|
+
for (const [flag, tag] of DECORATION_ELEMENTS) {
|
|
304
|
+
if (props[flag]) decorated = React.createElement(tag, {}, decorated);
|
|
305
|
+
}
|
|
306
|
+
const suffix = ellipsisConfig.suffix;
|
|
307
|
+
const wrapsContent = clamping && Boolean(hasActions || suffix !== void 0);
|
|
308
|
+
const ellipsisTooltip = ellipsisConfig.tooltip === true ? editConfig.text ?? children : ellipsisConfig.tooltip;
|
|
309
|
+
const attrs = {
|
|
310
|
+
"data-disabled": disabled ? "" : void 0,
|
|
311
|
+
"data-ellipsis": enableEllipsis ? "" : void 0,
|
|
312
|
+
"data-expanded": enableEllipsis && expanded ? "" : void 0
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
editing: enableEdit && editing,
|
|
316
|
+
editor: /* @__PURE__ */ jsx(
|
|
317
|
+
TypographyEditor,
|
|
318
|
+
{
|
|
319
|
+
value: editConfig.text ?? (typeof children === "string" ? children : ""),
|
|
320
|
+
ariaLabel: typeof editConfig.text === "string" ? editConfig.text : void 0,
|
|
321
|
+
maxLength: editConfig.maxLength,
|
|
322
|
+
autoSize: editConfig.autoSize,
|
|
323
|
+
enterIcon: editConfig.enterIcon,
|
|
324
|
+
onSave: (value) => {
|
|
325
|
+
editConfig.onChange?.(value);
|
|
326
|
+
setEditing(false);
|
|
327
|
+
},
|
|
328
|
+
onCancel: () => {
|
|
329
|
+
editConfig.onCancel?.();
|
|
330
|
+
setEditing(false);
|
|
331
|
+
},
|
|
332
|
+
onEnd: editConfig.onEnd
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
leading: placement === "start" ? actionsNode : null,
|
|
336
|
+
text: decorated,
|
|
337
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
338
|
+
suffix,
|
|
339
|
+
placement === "start" ? null : actionsNode
|
|
340
|
+
] }),
|
|
341
|
+
attrs,
|
|
342
|
+
measureRef,
|
|
343
|
+
wrapsContent,
|
|
344
|
+
onTextClick: enableEdit && triggerType.includes("text") ? startEditing : void 0,
|
|
345
|
+
ellipsisTooltip,
|
|
346
|
+
isEllipsis
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function usesBlockBehaviour(props) {
|
|
350
|
+
if (props.copyable || props.editable || props.ellipsis || props.actions || props.disabled) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
return DECORATION_ELEMENTS.some(([flag]) => props[flag]);
|
|
354
|
+
}
|
|
355
|
+
function ellipsisRows(ellipsis) {
|
|
356
|
+
if (!ellipsis || ellipsis === true) return 1;
|
|
357
|
+
const rows = ellipsis.rows;
|
|
358
|
+
return typeof rows === "number" && rows >= 1 ? Math.floor(rows) : 1;
|
|
359
|
+
}
|
|
360
|
+
function composeRefs(measureRef, forwarded) {
|
|
361
|
+
return (node) => {
|
|
362
|
+
measureRef(node);
|
|
363
|
+
if (typeof forwarded === "function") forwarded(node);
|
|
364
|
+
else if (forwarded) forwarded.current = node;
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const TextBase = React.forwardRef((props, ref) => {
|
|
368
|
+
const {
|
|
369
|
+
as,
|
|
370
|
+
component,
|
|
6
371
|
asChild = false,
|
|
7
372
|
size = "sm",
|
|
8
373
|
// `link` is an affordance, not a colour: it only moves the DEFAULT tone, so
|
|
9
374
|
// `link tone="destructive"` is a destructive link rather than an argument between two rules.
|
|
10
375
|
tone,
|
|
376
|
+
type,
|
|
11
377
|
weight = "regular",
|
|
12
378
|
align,
|
|
13
379
|
truncate,
|
|
@@ -21,60 +387,144 @@ const Text = React.forwardRef(
|
|
|
21
387
|
className,
|
|
22
388
|
style,
|
|
23
389
|
children,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
390
|
+
allowRows = false,
|
|
391
|
+
// antd block behaviour — read by `useTypographyBlock`, never spread onto the DOM.
|
|
392
|
+
copyable: _copyable,
|
|
393
|
+
editable: _editable,
|
|
394
|
+
ellipsis,
|
|
395
|
+
actions: _actions,
|
|
396
|
+
disabled,
|
|
397
|
+
code: _code,
|
|
398
|
+
mark: _mark,
|
|
399
|
+
underline: _underline,
|
|
400
|
+
delete: _delete,
|
|
401
|
+
strong: _strong,
|
|
402
|
+
keyboard: _keyboard,
|
|
403
|
+
italic: _italic,
|
|
404
|
+
...rest
|
|
405
|
+
} = props;
|
|
406
|
+
const block = useTypographyBlock(props, allowRows);
|
|
407
|
+
const element = as ?? component ?? "span";
|
|
408
|
+
const clampLines = typeof clamp === "number" && Number.isFinite(clamp) && clamp >= 1 ? Math.floor(clamp) : void 0;
|
|
409
|
+
const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
|
|
410
|
+
const effectiveClamp = ellipsisRowCount > 1 ? ellipsisRowCount : ellipsis ? void 0 : clampLines;
|
|
411
|
+
const truncating = (truncate === true || ellipsisRowCount === 1) && effectiveClamp === void 0;
|
|
412
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
413
|
+
if (clamp !== void 0 && clampLines === void 0) {
|
|
414
|
+
console.warn(`Text: \`clamp\` must be a finite number \u2265 1 (got ${String(clamp)}); ignored.`);
|
|
44
415
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
65
|
-
if (asChild) {
|
|
66
|
-
const child = React.Children.only(children);
|
|
67
|
-
return React.cloneElement(child, {
|
|
68
|
-
...typography,
|
|
69
|
-
...child.props,
|
|
70
|
-
ref,
|
|
71
|
-
className: cn(typography.className, child.props.className)
|
|
72
|
-
});
|
|
416
|
+
if (truncate && clampLines !== void 0) {
|
|
417
|
+
console.warn(
|
|
418
|
+
"Text: `truncate` and `clamp` are mutually exclusive \u2014 `clamp` takes precedence; drop `truncate`."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (whitespace === "pre-wrap" && truncating) {
|
|
422
|
+
console.warn(
|
|
423
|
+
'Text: `truncate` and `whitespace="pre-wrap"` are mutually exclusive \u2014 `truncate` takes precedence (one line, one ellipsis); use `clamp` to keep preserved line breaks and still bound the height.'
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if (ellipsis && (truncate || clamp !== void 0)) {
|
|
427
|
+
console.warn(
|
|
428
|
+
"Text: `ellipsis` (antd) and `truncate`/`clamp` are the same axis \u2014 `ellipsis` takes precedence; drop the other."
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (tone && type) {
|
|
432
|
+
console.warn(
|
|
433
|
+
"Text: `tone` and `type` (antd) are the same axis \u2014 `tone` takes precedence; drop `type`."
|
|
434
|
+
);
|
|
73
435
|
}
|
|
74
|
-
return React.createElement(as, { ref, ...typography }, children);
|
|
75
436
|
}
|
|
76
|
-
);
|
|
437
|
+
const resolvedTone = tone ?? (type ? TYPE_TO_TONE[type] : void 0) ?? (link ? "primary" : "default");
|
|
438
|
+
const clampStyle = effectiveClamp !== void 0 ? { ...style, "--text-clamp": effectiveClamp } : style;
|
|
439
|
+
const typography = {
|
|
440
|
+
"data-slot": "text",
|
|
441
|
+
"data-size": size,
|
|
442
|
+
"data-tone": resolvedTone,
|
|
443
|
+
"data-link": link ? "" : void 0,
|
|
444
|
+
"data-weight": weight,
|
|
445
|
+
"data-align": align,
|
|
446
|
+
// When the action cluster or a suffix is present the clamp moves ONTO the inner wrapper, so the
|
|
447
|
+
// buttons are not eaten by the clamped box. Everything else keeps the attribute where it has
|
|
448
|
+
// always been, on the element itself.
|
|
449
|
+
"data-truncate": truncating && !block.wrapsContent ? "" : void 0,
|
|
450
|
+
"data-clamp": effectiveClamp !== void 0 && !block.wrapsContent ? "" : void 0,
|
|
451
|
+
// Inert default: `normal` is CSS's own behaviour, so it emits no attribute and there is no
|
|
452
|
+
// `[data-whitespace="normal"]` rule to lose a specificity argument with anything.
|
|
453
|
+
"data-whitespace": whitespace === "pre-wrap" && !truncating ? "pre-wrap" : void 0,
|
|
454
|
+
style: clampStyle,
|
|
455
|
+
"data-tabular": tabular ? "" : void 0,
|
|
456
|
+
"data-decoration": decoration,
|
|
457
|
+
"data-chip": chip ? "" : void 0,
|
|
458
|
+
"data-mono": mono ? "" : void 0,
|
|
459
|
+
"aria-disabled": disabled ? true : void 0,
|
|
460
|
+
...block.attrs,
|
|
461
|
+
onClick: block.onTextClick,
|
|
462
|
+
className: cn("ui-text", className),
|
|
463
|
+
...rest
|
|
464
|
+
};
|
|
465
|
+
if (block.editing) return block.editor;
|
|
466
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
467
|
+
block.leading,
|
|
468
|
+
block.wrapsContent ? /* @__PURE__ */ jsx(
|
|
469
|
+
"span",
|
|
470
|
+
{
|
|
471
|
+
"data-slot": "typography-content",
|
|
472
|
+
"data-truncate": truncating ? "" : void 0,
|
|
473
|
+
"data-clamp": effectiveClamp !== void 0 ? "" : void 0,
|
|
474
|
+
className: "ui-typography-content",
|
|
475
|
+
style: clampStyle,
|
|
476
|
+
children: block.text
|
|
477
|
+
}
|
|
478
|
+
) : block.text,
|
|
479
|
+
block.trailing
|
|
480
|
+
] });
|
|
481
|
+
if (asChild) {
|
|
482
|
+
const child = React.Children.only(children);
|
|
483
|
+
const cloned = {
|
|
484
|
+
...typography,
|
|
485
|
+
...child.props,
|
|
486
|
+
ref,
|
|
487
|
+
className: cn(typography.className, child.props.className)
|
|
488
|
+
};
|
|
489
|
+
return usesBlockBehaviour(props) ? React.cloneElement(child, cloned, body) : React.cloneElement(child, cloned);
|
|
490
|
+
}
|
|
491
|
+
return React.createElement(
|
|
492
|
+
element,
|
|
493
|
+
{ ref: composeRefs(block.measureRef, ref), ...typography },
|
|
494
|
+
body
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
TextBase.displayName = "TextBase";
|
|
498
|
+
const Text = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(TextBase, { ref, ...props }));
|
|
77
499
|
Text.displayName = "Text";
|
|
500
|
+
const Paragraph = React.forwardRef(
|
|
501
|
+
({ as = "div", className, ...rest }, ref) => /* @__PURE__ */ jsx(
|
|
502
|
+
TextBase,
|
|
503
|
+
{
|
|
504
|
+
ref,
|
|
505
|
+
as,
|
|
506
|
+
allowRows: true,
|
|
507
|
+
className: cn("ui-typography-paragraph", className),
|
|
508
|
+
...rest
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
);
|
|
512
|
+
Paragraph.displayName = "Paragraph";
|
|
513
|
+
const Link = React.forwardRef(
|
|
514
|
+
({ as = "a", rel, target, className, ...rest }, ref) => /* @__PURE__ */ jsx(
|
|
515
|
+
TextBase,
|
|
516
|
+
{
|
|
517
|
+
ref,
|
|
518
|
+
as,
|
|
519
|
+
link: true,
|
|
520
|
+
target,
|
|
521
|
+
rel: rel === void 0 && target === "_blank" ? "noopener noreferrer" : rel,
|
|
522
|
+
className: cn("ui-typography-link", className),
|
|
523
|
+
...rest
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
Link.displayName = "Link";
|
|
78
528
|
const Heading = React.forwardRef(
|
|
79
529
|
({ level = 2, as, tone = "default", align, truncate, weight = "medium", className, ...props }, ref) => React.createElement(as ?? `h${level}`, {
|
|
80
530
|
ref,
|
|
@@ -89,7 +539,101 @@ const Heading = React.forwardRef(
|
|
|
89
539
|
})
|
|
90
540
|
);
|
|
91
541
|
Heading.displayName = "Heading";
|
|
542
|
+
const Title = React.forwardRef((props, ref) => {
|
|
543
|
+
const {
|
|
544
|
+
level = 1,
|
|
545
|
+
as,
|
|
546
|
+
component,
|
|
547
|
+
tone,
|
|
548
|
+
type,
|
|
549
|
+
align,
|
|
550
|
+
truncate,
|
|
551
|
+
weight = "medium",
|
|
552
|
+
className,
|
|
553
|
+
style,
|
|
554
|
+
children: _children,
|
|
555
|
+
ellipsis,
|
|
556
|
+
copyable: _copyable,
|
|
557
|
+
editable: _editable,
|
|
558
|
+
actions: _actions,
|
|
559
|
+
disabled,
|
|
560
|
+
code: _code,
|
|
561
|
+
mark: _mark,
|
|
562
|
+
underline: _underline,
|
|
563
|
+
delete: _delete,
|
|
564
|
+
keyboard: _keyboard,
|
|
565
|
+
italic: _italic,
|
|
566
|
+
...rest
|
|
567
|
+
} = props;
|
|
568
|
+
const block = useTypographyBlock(props, true);
|
|
569
|
+
const safeLevel = level >= 1 && level <= 5 ? level : 1;
|
|
570
|
+
const element = as ?? component ?? `h${safeLevel}`;
|
|
571
|
+
const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
|
|
572
|
+
const effectiveClamp = ellipsisRowCount > 1 ? ellipsisRowCount : void 0;
|
|
573
|
+
const truncating = (truncate === true || ellipsisRowCount === 1) && effectiveClamp === void 0;
|
|
574
|
+
const clampStyle = effectiveClamp !== void 0 ? { ...style, "--text-clamp": effectiveClamp } : style;
|
|
575
|
+
if (block.editing) return block.editor;
|
|
576
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
577
|
+
block.leading,
|
|
578
|
+
block.wrapsContent ? /* @__PURE__ */ jsx(
|
|
579
|
+
"span",
|
|
580
|
+
{
|
|
581
|
+
"data-slot": "typography-content",
|
|
582
|
+
"data-truncate": truncating ? "" : void 0,
|
|
583
|
+
"data-clamp": effectiveClamp !== void 0 ? "" : void 0,
|
|
584
|
+
className: "ui-typography-content",
|
|
585
|
+
style: clampStyle,
|
|
586
|
+
children: block.text
|
|
587
|
+
}
|
|
588
|
+
) : block.text,
|
|
589
|
+
block.trailing
|
|
590
|
+
] });
|
|
591
|
+
return React.createElement(
|
|
592
|
+
element,
|
|
593
|
+
{
|
|
594
|
+
ref: composeRefs(block.measureRef, ref),
|
|
595
|
+
"data-slot": "heading",
|
|
596
|
+
"data-level": safeLevel,
|
|
597
|
+
"data-tone": tone ?? (type ? TYPE_TO_TONE[type] : void 0) ?? "default",
|
|
598
|
+
"data-align": align,
|
|
599
|
+
"data-weight": weight,
|
|
600
|
+
"data-truncate": truncating && !block.wrapsContent ? "" : void 0,
|
|
601
|
+
"data-clamp": effectiveClamp !== void 0 && !block.wrapsContent ? "" : void 0,
|
|
602
|
+
"aria-disabled": disabled ? true : void 0,
|
|
603
|
+
style: clampStyle,
|
|
604
|
+
...block.attrs,
|
|
605
|
+
onClick: block.onTextClick,
|
|
606
|
+
className: cn("ui-heading", "ui-typography-title", className),
|
|
607
|
+
...rest
|
|
608
|
+
},
|
|
609
|
+
body
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
Title.displayName = "Title";
|
|
613
|
+
const TypographyRoot = React.forwardRef(
|
|
614
|
+
({ as, component, className, children, ...rest }, ref) => React.createElement(
|
|
615
|
+
as ?? component ?? "article",
|
|
616
|
+
{
|
|
617
|
+
ref,
|
|
618
|
+
"data-slot": "typography",
|
|
619
|
+
className: cn("ui-typography", className),
|
|
620
|
+
...rest
|
|
621
|
+
},
|
|
622
|
+
children
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
TypographyRoot.displayName = "Typography";
|
|
626
|
+
const Typography = Object.assign(TypographyRoot, {
|
|
627
|
+
Text,
|
|
628
|
+
Title,
|
|
629
|
+
Paragraph,
|
|
630
|
+
Link
|
|
631
|
+
});
|
|
92
632
|
export {
|
|
93
633
|
Heading,
|
|
94
|
-
|
|
634
|
+
Link,
|
|
635
|
+
Paragraph,
|
|
636
|
+
Text,
|
|
637
|
+
Title,
|
|
638
|
+
Typography
|
|
95
639
|
};
|