@godxjp/ui 31.28.4 → 31.29.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/agent/START-HERE.md +5 -5
- package/agent/components/AppLauncher.json +5 -0
- package/agent/components/Avatar.json +1 -0
- package/agent/components/Dialog.json +3 -1
- package/agent/components/ResponsiveGrid.json +7 -0
- package/agent/components/Sidebar.json +5 -0
- package/agent/components/SortableList.json +78 -0
- package/agent/components/TopbarItem.json +1 -1
- package/agent/components-index.json +5 -0
- package/agent/components.json +100 -2
- package/agent/index.json +6 -6
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +74 -2
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/sortable-list.d.ts +10 -0
- package/dist/components/data-entry/sortable-list.js +237 -0
- package/dist/components/feedback/dialog.d.ts +8 -2
- package/dist/components/feedback/dialog.js +8 -1
- package/dist/components/feedback/sheet.js +1 -1
- package/dist/components/layout/app-launcher.d.ts +1 -1
- package/dist/components/layout/app-launcher.js +18 -14
- package/dist/components/layout/responsive-grid.d.ts +17 -2
- package/dist/components/layout/responsive-grid.js +17 -4
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +3 -0
- package/dist/components/layout/topbar-item.js +8 -2
- package/dist/components/ui/avatar.d.ts +3 -1
- package/dist/components/ui/avatar.js +2 -1
- package/dist/contracts/measurement.json +1 -1
- 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/props/components/data-display.prop.d.ts +9 -0
- package/dist/props/components/data-entry.prop.d.ts +34 -0
- package/dist/props/components/feedback.prop.d.ts +6 -1
- package/dist/props/components/layout.prop.d.ts +22 -0
- package/dist/props/registry.d.ts +33 -2
- package/dist/props/registry.js +44 -1
- package/dist/styles/control.css +50 -0
- package/dist/styles/data-display-layout.css +4 -0
- package/dist/styles/dialog-layout.css +14 -1
- package/dist/styles/layers.json +6 -1
- package/dist/styles/layout.css +29 -7
- package/dist/styles/shell-layout.css +26 -0
- package/dist/tokens/components/control.css +5 -0
- package/dist/tokens/components/feedback.css +6 -0
- package/dist/tokens/components/shell.css +6 -0
- package/dist/tokens/semantic/layout.css +1 -4
- package/docs/FRAME-COVERAGE-REPORT.md +3 -2
- package/docs/data-display/avatar.tsx +32 -0
- package/docs/data-entry/sortable-list.tsx +76 -0
- package/docs/feedback/dialog.tsx +37 -1
- package/docs/i18n/messages/en.json +51 -0
- package/docs/i18n/messages/ja.json +51 -0
- package/docs/i18n/messages/vi.json +51 -0
- package/docs/layout/app-launcher.tsx +17 -1
- package/docs/layout/responsive-grid.tsx +23 -0
- package/docs/layout/sidebar.tsx +24 -0
- package/docs/layout/topbar-item.tsx +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { GripVertical } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { Button } from "../general/button.js";
|
|
8
|
+
const DRAG_THRESHOLD = 4;
|
|
9
|
+
function move(list, from, to) {
|
|
10
|
+
const next = list.slice();
|
|
11
|
+
const [item] = next.splice(from, 1);
|
|
12
|
+
next.splice(to, 0, item);
|
|
13
|
+
return next;
|
|
14
|
+
}
|
|
15
|
+
function reconcile(order, items) {
|
|
16
|
+
const known = new Set(items.map((item) => item.value));
|
|
17
|
+
const kept = order.filter((key) => known.has(key));
|
|
18
|
+
const seen = new Set(kept);
|
|
19
|
+
return [...kept, ...items.map((item) => item.value).filter((key) => !seen.has(key))];
|
|
20
|
+
}
|
|
21
|
+
const SortableList = React.forwardRef(
|
|
22
|
+
function SortableList2({
|
|
23
|
+
items,
|
|
24
|
+
value: valueProp,
|
|
25
|
+
defaultValue,
|
|
26
|
+
onValueChange,
|
|
27
|
+
renderItem,
|
|
28
|
+
layout = "list",
|
|
29
|
+
disabled = false,
|
|
30
|
+
"aria-label": ariaLabel,
|
|
31
|
+
id,
|
|
32
|
+
className
|
|
33
|
+
}, ref) {
|
|
34
|
+
const { t, locale } = useTranslation();
|
|
35
|
+
const hintId = React.useId();
|
|
36
|
+
const [inner, setInner] = React.useState(
|
|
37
|
+
() => defaultValue ?? items.map((item) => item.value)
|
|
38
|
+
);
|
|
39
|
+
const committed = reconcile(valueProp ?? inner, items);
|
|
40
|
+
const [preview, setPreview] = React.useState(null);
|
|
41
|
+
const [held, setHeld] = React.useState(
|
|
42
|
+
null
|
|
43
|
+
);
|
|
44
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
45
|
+
const order = preview ?? committed;
|
|
46
|
+
const byKey = React.useMemo(() => new Map(items.map((item) => [item.value, item])), [items]);
|
|
47
|
+
const listRef = React.useRef(null);
|
|
48
|
+
const number = React.useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
49
|
+
const position = (keys, key) => ({
|
|
50
|
+
label: byKey.get(key)?.label ?? key,
|
|
51
|
+
position: number.format(keys.indexOf(key) + 1),
|
|
52
|
+
count: number.format(keys.length)
|
|
53
|
+
});
|
|
54
|
+
const commit = (keys) => {
|
|
55
|
+
if (keys.join("\0") !== committed.join("\0")) {
|
|
56
|
+
if (valueProp === void 0) setInner(keys);
|
|
57
|
+
onValueChange?.(keys);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const lift = (key, via) => {
|
|
61
|
+
setHeld({ key, via });
|
|
62
|
+
setPreview(committed);
|
|
63
|
+
setAnnouncement(t("dataEntry.sortableList.lifted", position(committed, key)));
|
|
64
|
+
};
|
|
65
|
+
const drop = () => {
|
|
66
|
+
if (!held || !preview) return;
|
|
67
|
+
commit(preview);
|
|
68
|
+
setAnnouncement(t("dataEntry.sortableList.dropped", position(preview, held.key)));
|
|
69
|
+
setHeld(null);
|
|
70
|
+
setPreview(null);
|
|
71
|
+
};
|
|
72
|
+
const cancel = () => {
|
|
73
|
+
if (!held) return;
|
|
74
|
+
setAnnouncement(t("dataEntry.sortableList.cancelled", position(committed, held.key)));
|
|
75
|
+
setHeld(null);
|
|
76
|
+
setPreview(null);
|
|
77
|
+
};
|
|
78
|
+
const moveTo = (key, target) => {
|
|
79
|
+
setPreview((current) => {
|
|
80
|
+
const keys = current ?? committed;
|
|
81
|
+
const from = keys.indexOf(key);
|
|
82
|
+
const to = Math.max(0, Math.min(keys.length - 1, target));
|
|
83
|
+
if (from < 0 || from === to) return keys;
|
|
84
|
+
const next = move(keys, from, to);
|
|
85
|
+
setAnnouncement(t("dataEntry.sortableList.moved", position(next, key)));
|
|
86
|
+
return next;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const onKeyDown = (key) => (event) => {
|
|
90
|
+
if (event.key === " " || event.key === "Enter") {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
if (held?.key === key) drop();
|
|
93
|
+
else if (!held) lift(key, "keyboard");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (held?.key !== key) return;
|
|
97
|
+
const index = order.indexOf(key);
|
|
98
|
+
const step = {
|
|
99
|
+
ArrowUp: -1,
|
|
100
|
+
ArrowLeft: -1,
|
|
101
|
+
ArrowDown: 1,
|
|
102
|
+
ArrowRight: 1
|
|
103
|
+
};
|
|
104
|
+
if (event.key in step) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
moveTo(key, index + (step[event.key] ?? 0));
|
|
107
|
+
} else if (event.key === "Home") {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
moveTo(key, 0);
|
|
110
|
+
} else if (event.key === "End") {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
moveTo(key, order.length - 1);
|
|
113
|
+
} else if (event.key === "Escape") {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
cancel();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const indexAt = (x, y) => {
|
|
119
|
+
const nodes = Array.from(
|
|
120
|
+
listRef.current?.querySelectorAll("[data-sortable-value]") ?? []
|
|
121
|
+
);
|
|
122
|
+
let best = -1;
|
|
123
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
124
|
+
nodes.forEach((node, index) => {
|
|
125
|
+
const box = node.getBoundingClientRect();
|
|
126
|
+
if (x >= box.left && x <= box.right && y >= box.top && y <= box.bottom) {
|
|
127
|
+
best = index;
|
|
128
|
+
bestDistance = -1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (bestDistance < 0) return;
|
|
132
|
+
const distance = Math.hypot(x - (box.left + box.width / 2), y - (box.top + box.height / 2));
|
|
133
|
+
if (distance < bestDistance) {
|
|
134
|
+
bestDistance = distance;
|
|
135
|
+
best = index;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return best;
|
|
139
|
+
};
|
|
140
|
+
const press = React.useRef(
|
|
141
|
+
null
|
|
142
|
+
);
|
|
143
|
+
const latest = React.useRef({ lift, moveTo, drop, cancel, indexAt });
|
|
144
|
+
latest.current = { lift, moveTo, drop, cancel, indexAt };
|
|
145
|
+
const onPointerDown = (key) => (event) => {
|
|
146
|
+
if (event.button !== 0 || held) return;
|
|
147
|
+
press.current = { key, x: event.clientX, y: event.clientY, dragging: false };
|
|
148
|
+
const onMove = (move2) => {
|
|
149
|
+
const current = press.current;
|
|
150
|
+
if (!current) return;
|
|
151
|
+
if (!current.dragging) {
|
|
152
|
+
if (Math.hypot(move2.clientX - current.x, move2.clientY - current.y) < DRAG_THRESHOLD)
|
|
153
|
+
return;
|
|
154
|
+
current.dragging = true;
|
|
155
|
+
latest.current.lift(current.key, "pointer");
|
|
156
|
+
}
|
|
157
|
+
const target = latest.current.indexAt(move2.clientX, move2.clientY);
|
|
158
|
+
if (target >= 0) latest.current.moveTo(current.key, target);
|
|
159
|
+
};
|
|
160
|
+
const end = (cancelled) => () => {
|
|
161
|
+
window.removeEventListener("pointermove", onMove);
|
|
162
|
+
window.removeEventListener("pointerup", onUp);
|
|
163
|
+
window.removeEventListener("pointercancel", onCancel);
|
|
164
|
+
window.removeEventListener("keydown", onKey);
|
|
165
|
+
const current = press.current;
|
|
166
|
+
press.current = null;
|
|
167
|
+
if (!current?.dragging) return;
|
|
168
|
+
if (cancelled) latest.current.cancel();
|
|
169
|
+
else latest.current.drop();
|
|
170
|
+
};
|
|
171
|
+
const onUp = end(false);
|
|
172
|
+
const onCancel = end(true);
|
|
173
|
+
const onKey = (key2) => {
|
|
174
|
+
if (key2.key === "Escape" && press.current?.dragging) end(true)();
|
|
175
|
+
};
|
|
176
|
+
window.addEventListener("pointermove", onMove);
|
|
177
|
+
window.addEventListener("pointerup", onUp);
|
|
178
|
+
window.addEventListener("pointercancel", onCancel);
|
|
179
|
+
window.addEventListener("keydown", onKey);
|
|
180
|
+
};
|
|
181
|
+
return /* @__PURE__ */ jsxs(
|
|
182
|
+
"div",
|
|
183
|
+
{
|
|
184
|
+
ref,
|
|
185
|
+
id,
|
|
186
|
+
className: cn("ui-sortable-list", className),
|
|
187
|
+
"data-layout": layout,
|
|
188
|
+
"data-disabled": disabled || void 0,
|
|
189
|
+
children: [
|
|
190
|
+
/* @__PURE__ */ jsx("ul", { ref: listRef, className: "ui-sortable-list-items", "aria-label": ariaLabel, children: order.map((key, index) => {
|
|
191
|
+
const item = byKey.get(key);
|
|
192
|
+
if (!item) return null;
|
|
193
|
+
const dragging = held?.key === key;
|
|
194
|
+
const itemDisabled = disabled || item.disabled;
|
|
195
|
+
return /* @__PURE__ */ jsxs(
|
|
196
|
+
"li",
|
|
197
|
+
{
|
|
198
|
+
className: "ui-sortable-list-item",
|
|
199
|
+
"data-sortable-value": key,
|
|
200
|
+
"data-dragging": dragging || void 0,
|
|
201
|
+
"data-disabled": itemDisabled || void 0,
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx(
|
|
204
|
+
Button,
|
|
205
|
+
{
|
|
206
|
+
type: "button",
|
|
207
|
+
variant: "ghost",
|
|
208
|
+
size: "icon-sm",
|
|
209
|
+
className: "ui-sortable-list-handle",
|
|
210
|
+
disabled: itemDisabled,
|
|
211
|
+
"aria-label": t("dataEntry.sortableList.handle", { label: item.label }),
|
|
212
|
+
"aria-describedby": hintId,
|
|
213
|
+
"aria-pressed": dragging,
|
|
214
|
+
onKeyDown: onKeyDown(key),
|
|
215
|
+
onBlur: () => {
|
|
216
|
+
if (held?.key === key && held.via === "keyboard") cancel();
|
|
217
|
+
},
|
|
218
|
+
onPointerDown: onPointerDown(key),
|
|
219
|
+
children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
/* @__PURE__ */ jsx("div", { className: "ui-sortable-list-content", children: renderItem ? renderItem(item, { index, dragging }) : item.label })
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
key
|
|
226
|
+
);
|
|
227
|
+
}) }),
|
|
228
|
+
/* @__PURE__ */ jsx("span", { id: hintId, className: "sr-only", children: t("dataEntry.sortableList.hint") }),
|
|
229
|
+
/* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: announcement })
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
export {
|
|
236
|
+
SortableList
|
|
237
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { ToneProp } from "../../props/vocabulary/index.js";
|
|
2
|
+
import type { ToneProp, WidthProp } from "../../props/vocabulary/index.js";
|
|
3
3
|
import type { AlertDialogProp, DialogContentProp, DialogProp } from "../../props/components/feedback.prop.js";
|
|
4
4
|
export type { AlertDialogProp, AlertDialogProp as AlertDialogProps, DialogProp, DialogProp as DialogProps, DialogContentProp, DialogContentProp as DialogContentProps, } from "../../props/components/feedback.prop.js";
|
|
5
5
|
interface OverlayOpenProps {
|
|
@@ -57,8 +57,14 @@ interface DialogContentProps extends Omit<React.ComponentPropsWithRef<"section">
|
|
|
57
57
|
forceMount?: true;
|
|
58
58
|
/** Giữ tên prop của Radix — xem `useCloseAutoFocus`. */
|
|
59
59
|
onCloseAutoFocus?: (event: Event) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Panel width (antd Modal `width`, the same vocabulary as SheetContent `width`). A number is px;
|
|
62
|
+
* a string is any CSS length. Capped at the viewport minus `--dialog-viewport-inset`, so a wide
|
|
63
|
+
* dialog never leaves a phone screen. Omit to keep `--dialog-width-default` (32rem).
|
|
64
|
+
*/
|
|
65
|
+
width?: WidthProp;
|
|
60
66
|
}
|
|
61
|
-
declare function DialogContent({ className, children, showClose, showCloseButton: showCloseButtonProp, overlayClassName, variant: variantProp, forceMount: _forceMount, ...props }: DialogContentProps): React.JSX.Element;
|
|
67
|
+
declare function DialogContent({ className, children, showClose, showCloseButton: showCloseButtonProp, overlayClassName, variant: variantProp, forceMount: _forceMount, width, style, ...props }: DialogContentProps): React.JSX.Element;
|
|
62
68
|
declare namespace DialogContent {
|
|
63
69
|
var displayName: string;
|
|
64
70
|
}
|
|
@@ -45,7 +45,7 @@ const DialogLabelContext = React.createContext(null);
|
|
|
45
45
|
const DialogVariantContext = React.createContext("default");
|
|
46
46
|
const DialogModalContext = React.createContext(true);
|
|
47
47
|
const OVERLAY_CLASS = "ui-dialog-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0";
|
|
48
|
-
const CONTENT_CLASS = "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95
|
|
48
|
+
const CONTENT_CLASS = "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 outline-none";
|
|
49
49
|
function DialogShell({
|
|
50
50
|
role = "dialog",
|
|
51
51
|
className,
|
|
@@ -177,6 +177,8 @@ function DialogContent({
|
|
|
177
177
|
overlayClassName,
|
|
178
178
|
variant: variantProp,
|
|
179
179
|
forceMount: _forceMount,
|
|
180
|
+
width,
|
|
181
|
+
style,
|
|
180
182
|
...props
|
|
181
183
|
}) {
|
|
182
184
|
const { t } = useTranslation();
|
|
@@ -184,6 +186,10 @@ function DialogContent({
|
|
|
184
186
|
const variant = variantProp ?? inheritedVariant;
|
|
185
187
|
const isDestructive = variant === "destructive";
|
|
186
188
|
const showCloseButton = showCloseButtonProp ?? showClose ?? !isDestructive;
|
|
189
|
+
const mergedStyle = width == null ? style : {
|
|
190
|
+
...style,
|
|
191
|
+
["--dialog-width"]: typeof width === "number" ? `${width}px` : width
|
|
192
|
+
};
|
|
187
193
|
return /* @__PURE__ */ jsx(DialogVariantContext.Provider, { value: variant, children: /* @__PURE__ */ jsxs(
|
|
188
194
|
DialogShell,
|
|
189
195
|
{
|
|
@@ -192,6 +198,7 @@ function DialogContent({
|
|
|
192
198
|
"data-variant": variant,
|
|
193
199
|
className,
|
|
194
200
|
overlayClassName,
|
|
201
|
+
style: mergedStyle,
|
|
195
202
|
...props,
|
|
196
203
|
children: [
|
|
197
204
|
children,
|
|
@@ -91,7 +91,7 @@ function SheetOverlay(_props) {
|
|
|
91
91
|
SheetOverlay.displayName = "SheetOverlay";
|
|
92
92
|
const SHEET_OVERLAY_CLASS = "ui-sheet-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed";
|
|
93
93
|
const sheetVariants = cva(
|
|
94
|
-
"ui-sheet-panel fixed flex flex-col gap-[var(--space-chrome-gap)] text-foreground px-[var(--sheet-pad-x)] py-[var(--sheet-pad-y)] transition ease-in-out data-[state=closed]:animate-out data-[state=
|
|
94
|
+
"ui-sheet-panel fixed flex flex-col gap-[var(--space-chrome-gap)] text-foreground px-[var(--sheet-pad-x)] py-[var(--sheet-pad-y)] transition ease-in-out data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
95
95
|
{
|
|
96
96
|
variants: {
|
|
97
97
|
// `side` is a deliberately PHYSICAL API (left/right/top/bottom) — a sheet
|
|
@@ -31,4 +31,4 @@ type DataAttributes = {
|
|
|
31
31
|
* single product where switching workspace is constant enough to be worth permanent screen width
|
|
32
32
|
* (Slack). Shipping both puts one scope in two places and makes neither authoritative.
|
|
33
33
|
*/
|
|
34
|
-
export declare function AppLauncher({ apps, groups, labels, columns, linkComponent, loading, error, onRetry, responsive, appearance, side, align, open, onOpenChange, className, ...rest }: AppLauncherProp & Pick<React.ComponentPropsWithoutRef<"button">, "id"> & DataAttributes): React.JSX.Element;
|
|
34
|
+
export declare function AppLauncher({ apps, groups, labels, columns, linkComponent, loading, error, onRetry, responsive, appearance, side, align, footer, open, onOpenChange, className, ...rest }: AppLauncherProp & Pick<React.ComponentPropsWithoutRef<"button">, "id"> & DataAttributes): React.JSX.Element;
|
|
@@ -165,6 +165,7 @@ function AppLauncher({
|
|
|
165
165
|
appearance = "bar",
|
|
166
166
|
side,
|
|
167
167
|
align,
|
|
168
|
+
footer,
|
|
168
169
|
open,
|
|
169
170
|
onOpenChange,
|
|
170
171
|
className,
|
|
@@ -194,20 +195,23 @@ function AppLauncher({
|
|
|
194
195
|
...rest
|
|
195
196
|
};
|
|
196
197
|
const trigger = appearance === "bar" ? /* @__PURE__ */ jsx(TopbarItem, { ...triggerProps, children: /* @__PURE__ */ jsx(Grip, { "aria-hidden": "true" }) }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", ...triggerProps, children: /* @__PURE__ */ jsx(Grip, { "aria-hidden": "true" }) });
|
|
197
|
-
const panel = /* @__PURE__ */
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
198
|
+
const panel = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
199
|
+
/* @__PURE__ */ jsx(
|
|
200
|
+
AppLauncherPanel,
|
|
201
|
+
{
|
|
202
|
+
apps,
|
|
203
|
+
groups,
|
|
204
|
+
labels,
|
|
205
|
+
columns,
|
|
206
|
+
linkComponent,
|
|
207
|
+
loading,
|
|
208
|
+
error,
|
|
209
|
+
onRetry,
|
|
210
|
+
close
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
footer != null && footer !== false ? /* @__PURE__ */ jsx("div", { "data-slot": "app-launcher-footer", className: "ui-app-launcher-footer", children: footer }) : null
|
|
214
|
+
] });
|
|
211
215
|
if (launchpad) {
|
|
212
216
|
return /* @__PURE__ */ jsxs(Dialog, { open: resolvedOpen, onOpenChange: setOpen, children: [
|
|
213
217
|
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: trigger }),
|
|
@@ -24,15 +24,30 @@ export type ResponsiveGridProps = {
|
|
|
24
24
|
* to strip its old `columns` prop.
|
|
25
25
|
*/
|
|
26
26
|
preset?: ResponsiveGridPresetProp;
|
|
27
|
+
/**
|
|
28
|
+
* Dense packing (`grid-auto-flow: row dense`): a later, smaller cell back-fills the hole a wide
|
|
29
|
+
* or tall cell left earlier in the row — a widget board of 1×1 / 2×1 / 1×2 cells without gaps
|
|
30
|
+
* (gh#1175). It changes VISUAL order only; DOM and focus order stay as written, so use it where
|
|
31
|
+
* the cells are independent (widgets, tiles), not where reading order matters. Ignored under
|
|
32
|
+
* `flow="columns"`, whose sequence is the point.
|
|
33
|
+
*/
|
|
34
|
+
dense?: boolean;
|
|
27
35
|
children: ReactNode;
|
|
28
36
|
};
|
|
29
|
-
export declare function ResponsiveGrid({ columns, flow, align, gap, pad, padRaw, preset, className, children, }: ResponsiveGridProps): import("react").JSX.Element;
|
|
37
|
+
export declare function ResponsiveGrid({ columns, flow, align, gap, pad, padRaw, preset, dense, className, children, }: ResponsiveGridProps): import("react").JSX.Element;
|
|
30
38
|
export declare namespace ResponsiveGrid {
|
|
31
39
|
export { ResponsiveGridItem as Item };
|
|
32
40
|
}
|
|
33
41
|
/** A grid item owns its responsive span; no extra implicit columns on a narrow container. */
|
|
34
|
-
export declare function ResponsiveGridItem({ span, children, className, }: {
|
|
42
|
+
export declare function ResponsiveGridItem({ span, rowSpan, children, className, }: {
|
|
35
43
|
span?: ResponsiveGridColumnsProp;
|
|
44
|
+
/**
|
|
45
|
+
* Rows the cell spans — the tall (1×2) widget of a board (gh#1175). Same shape as `span`: a
|
|
46
|
+
* number spans that many rows from the `sm` step up and one row at `base`, where the grid is a
|
|
47
|
+
* single stacked column and a tall cell would only stretch; an object sets each step. Pair with
|
|
48
|
+
* `ResponsiveGrid dense` so the cells after it fill the space beside it.
|
|
49
|
+
*/
|
|
50
|
+
rowSpan?: ResponsiveGridColumnsProp;
|
|
36
51
|
children?: ReactNode;
|
|
37
52
|
className?: string;
|
|
38
53
|
}): import("react").JSX.Element;
|
|
@@ -36,6 +36,7 @@ function ResponsiveGrid({
|
|
|
36
36
|
pad,
|
|
37
37
|
padRaw,
|
|
38
38
|
preset,
|
|
39
|
+
dense = false,
|
|
39
40
|
className,
|
|
40
41
|
children
|
|
41
42
|
}) {
|
|
@@ -47,21 +48,33 @@ function ResponsiveGrid({
|
|
|
47
48
|
"data-gap": gap,
|
|
48
49
|
"data-flow": flow === "columns" ? "columns" : void 0,
|
|
49
50
|
"data-align": align,
|
|
51
|
+
"data-dense": dense && flow !== "columns" ? "" : void 0,
|
|
50
52
|
style: { ...toStyle(resolved), ...padStyle(pad, padRaw) },
|
|
51
53
|
"data-pad-raw": padRaw === void 0 ? void 0 : "",
|
|
52
54
|
children
|
|
53
55
|
}
|
|
54
56
|
) });
|
|
55
57
|
}
|
|
58
|
+
function resolveSpan(span) {
|
|
59
|
+
return typeof span === "number" ? { base: 1, sm: span, md: span, lg: span } : resolveColumns(span);
|
|
60
|
+
}
|
|
56
61
|
function ResponsiveGridItem({
|
|
57
62
|
span = 1,
|
|
63
|
+
rowSpan,
|
|
58
64
|
children,
|
|
59
65
|
className
|
|
60
66
|
}) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
const style = Object.fromEntries([
|
|
68
|
+
...Object.entries(resolveSpan(span)).map(([step, value]) => [
|
|
69
|
+
`--responsive-grid-item-${step}`,
|
|
70
|
+
value
|
|
71
|
+
]),
|
|
72
|
+
// Only when asked: an item without `rowSpan` renders the style it always had.
|
|
73
|
+
...rowSpan === void 0 ? [] : Object.entries(resolveSpan(rowSpan)).map(([step, value]) => [
|
|
74
|
+
`--responsive-grid-item-row-${step}`,
|
|
75
|
+
value
|
|
76
|
+
])
|
|
77
|
+
]);
|
|
65
78
|
return /* @__PURE__ */ jsx("div", { className: cn("ui-responsive-grid-item", className), style, children });
|
|
66
79
|
}
|
|
67
80
|
ResponsiveGrid.Item = ResponsiveGridItem;
|
|
@@ -47,4 +47,4 @@ export { createSidebarLink } from "./sidebar-link.js";
|
|
|
47
47
|
* Sidebar — data-driven vertical nav rail. Use {@link createSidebarLink} to adapt a router `Link`,
|
|
48
48
|
* or `SidebarItem asChild` when composing rows by hand.
|
|
49
49
|
*/
|
|
50
|
-
export declare function Sidebar({ ariaLabel: ariaLabelCamel, scrollRef, activeId, onSelect, sections, product, onProductClick, brand, collapsed, children, linkComponent, renderItem, footer, "aria-label": ariaLabel, }: SidebarProp): React.JSX.Element;
|
|
50
|
+
export declare function Sidebar({ ariaLabel: ariaLabelCamel, scrollRef, activeId, onSelect, sections, product, onProductClick, brand, action, collapsed, children, linkComponent, renderItem, footer, "aria-label": ariaLabel, }: SidebarProp): React.JSX.Element;
|
|
@@ -301,6 +301,7 @@ function Sidebar({
|
|
|
301
301
|
product,
|
|
302
302
|
onProductClick,
|
|
303
303
|
brand,
|
|
304
|
+
action,
|
|
304
305
|
collapsed = false,
|
|
305
306
|
children,
|
|
306
307
|
linkComponent,
|
|
@@ -314,6 +315,7 @@ function Sidebar({
|
|
|
314
315
|
collapsed = surface === "drawer" ? false : collapsed;
|
|
315
316
|
const brandNode = typeof brand === "function" ? brand(collapsed) : brand;
|
|
316
317
|
const footerNode = typeof footer === "function" ? footer(collapsed) : footer;
|
|
318
|
+
const actionNode = typeof action === "function" ? action(collapsed) : action;
|
|
317
319
|
return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
|
|
318
320
|
brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brandNode }) : product ? (() => {
|
|
319
321
|
const interactive = onProductClick != null;
|
|
@@ -352,6 +354,7 @@ function Sidebar({
|
|
|
352
354
|
meta
|
|
353
355
|
] });
|
|
354
356
|
})() : null,
|
|
357
|
+
actionNode ? /* @__PURE__ */ jsx("div", { className: "sb-action", "data-slot": "sidebar-action", children: actionNode }) : null,
|
|
355
358
|
/* @__PURE__ */ jsx(
|
|
356
359
|
"nav",
|
|
357
360
|
{
|
|
@@ -16,7 +16,7 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
16
16
|
...props
|
|
17
17
|
}, ref) {
|
|
18
18
|
const Comp = asChild ? Slot : "button";
|
|
19
|
-
const showBadge =
|
|
19
|
+
const showBadge = badge !== void 0 && badge !== "";
|
|
20
20
|
const iconNode = icon !== void 0 && icon !== null && icon !== false ? /* @__PURE__ */ jsx(
|
|
21
21
|
"span",
|
|
22
22
|
{
|
|
@@ -35,7 +35,6 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
35
35
|
children: label
|
|
36
36
|
}
|
|
37
37
|
);
|
|
38
|
-
const slotted = asChild && (iconNode !== null || labelHideBelow !== void 0) && React.isValidElement(children) ? React.cloneElement(children, void 0, iconNode, labelOf(children.props.children)) : children;
|
|
39
38
|
const badgeNode = showBadge ? /* @__PURE__ */ jsx(
|
|
40
39
|
"span",
|
|
41
40
|
{
|
|
@@ -45,6 +44,13 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
45
44
|
children: badge
|
|
46
45
|
}
|
|
47
46
|
) : null;
|
|
47
|
+
const slotted = asChild && (iconNode !== null || labelHideBelow !== void 0 || badgeNode !== null) && React.isValidElement(children) ? React.cloneElement(
|
|
48
|
+
children,
|
|
49
|
+
void 0,
|
|
50
|
+
iconNode,
|
|
51
|
+
labelOf(children.props.children),
|
|
52
|
+
badgeNode
|
|
53
|
+
) : children;
|
|
48
54
|
return /* @__PURE__ */ jsx(
|
|
49
55
|
Comp,
|
|
50
56
|
{
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type ImageLoadingStatus } from "../../lib/image-loading-status.js";
|
|
3
|
-
import type { AvatarProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
import type { AvatarImageFitProp, AvatarProp } from "../../props/components/data-display.prop.js";
|
|
4
4
|
export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
5
5
|
asChild?: boolean;
|
|
6
6
|
} & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
|
|
7
7
|
export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & {
|
|
8
8
|
asChild?: boolean;
|
|
9
9
|
onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
|
|
10
|
+
/** `cover` (default) crops to fill; `contain` shows a logo or app icon whole (gh#1176). */
|
|
11
|
+
fit?: AvatarImageFitProp;
|
|
10
12
|
} & React.RefAttributes<HTMLImageElement>>;
|
|
11
13
|
export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
12
14
|
asChild?: boolean;
|
|
@@ -53,7 +53,7 @@ const Avatar = React.forwardRef(
|
|
|
53
53
|
}
|
|
54
54
|
);
|
|
55
55
|
Avatar.displayName = "Avatar";
|
|
56
|
-
const AvatarImage = React.forwardRef(({ className, src, onLoadingStatusChange, asChild = false, ...props }, ref) => {
|
|
56
|
+
const AvatarImage = React.forwardRef(({ className, src, onLoadingStatusChange, asChild = false, fit, ...props }, ref) => {
|
|
57
57
|
const { imageLoadingStatus, setImageLoadingStatus } = useAvatarContext("AvatarImage");
|
|
58
58
|
const status = useImageLoadingStatus(src, {
|
|
59
59
|
referrerPolicy: props.referrerPolicy,
|
|
@@ -78,6 +78,7 @@ const AvatarImage = React.forwardRef(({ className, src, onLoadingStatusChange, a
|
|
|
78
78
|
{
|
|
79
79
|
ref,
|
|
80
80
|
"data-slot": "avatar-image",
|
|
81
|
+
"data-fit": fit === "contain" ? "contain" : void 0,
|
|
81
82
|
className: cn("ui-avatar-image", className),
|
|
82
83
|
...props,
|
|
83
84
|
src
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.29.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
|
@@ -232,6 +232,14 @@
|
|
|
232
232
|
"symbols": "Symbols",
|
|
233
233
|
"flags": "Flags"
|
|
234
234
|
}
|
|
235
|
+
},
|
|
236
|
+
"sortableList": {
|
|
237
|
+
"handle": "Reorder {label}",
|
|
238
|
+
"hint": "Press Space to pick up, the arrow keys to move, Space to drop, Escape to cancel.",
|
|
239
|
+
"lifted": "Picked up {label}. Position {position} of {count}.",
|
|
240
|
+
"moved": "{label}: position {position} of {count}.",
|
|
241
|
+
"dropped": "Dropped {label} at position {position} of {count}.",
|
|
242
|
+
"cancelled": "Move cancelled. {label} is back at position {position} of {count}."
|
|
235
243
|
}
|
|
236
244
|
},
|
|
237
245
|
"feedback": {
|
|
@@ -228,6 +228,14 @@
|
|
|
228
228
|
"symbols": "記号",
|
|
229
229
|
"flags": "旗"
|
|
230
230
|
}
|
|
231
|
+
},
|
|
232
|
+
"sortableList": {
|
|
233
|
+
"handle": "{label} を並べ替え",
|
|
234
|
+
"hint": "Space で持ち上げ、矢印キーで移動、Space で確定、Esc で取り消します。",
|
|
235
|
+
"lifted": "{label} を持ち上げました。{count} 件中 {position} 番目です。",
|
|
236
|
+
"moved": "{label}: {count} 件中 {position} 番目。",
|
|
237
|
+
"dropped": "{label} を {count} 件中 {position} 番目に置きました。",
|
|
238
|
+
"cancelled": "移動を取り消しました。{label} は {count} 件中 {position} 番目に戻りました。"
|
|
231
239
|
}
|
|
232
240
|
},
|
|
233
241
|
"feedback": {
|
|
@@ -229,6 +229,14 @@
|
|
|
229
229
|
"symbols": "Ký hiệu",
|
|
230
230
|
"flags": "Cờ"
|
|
231
231
|
}
|
|
232
|
+
},
|
|
233
|
+
"sortableList": {
|
|
234
|
+
"handle": "Sắp xếp lại {label}",
|
|
235
|
+
"hint": "Nhấn Space để nhấc, phím mũi tên để di chuyển, Space để thả, Esc để huỷ.",
|
|
236
|
+
"lifted": "Đã nhấc {label}. Vị trí {position}/{count}.",
|
|
237
|
+
"moved": "{label}: vị trí {position}/{count}.",
|
|
238
|
+
"dropped": "Đã thả {label} ở vị trí {position}/{count}.",
|
|
239
|
+
"cancelled": "Đã huỷ di chuyển. {label} trở lại vị trí {position}/{count}."
|
|
232
240
|
}
|
|
233
241
|
},
|
|
234
242
|
"feedback": {
|
|
@@ -303,6 +303,15 @@ export type AvatarPresenceProp = "online" | "away" | "busy" | "offline";
|
|
|
303
303
|
* @see Avatar
|
|
304
304
|
*/
|
|
305
305
|
export type AvatarAppearanceProp = "default" | "tinted";
|
|
306
|
+
/**
|
|
307
|
+
* How an `AvatarImage` fills its frame (`object-fit`). `cover` (default) fills the frame and crops
|
|
308
|
+
* — right for a photo of a face. `contain` shows the whole image inside the frame — right for a
|
|
309
|
+
* logo or an app icon that is wide, transparent or not centred, which `cover` would cut off
|
|
310
|
+
* (gh#1176). A full-bleed square icon (the GoDX service icon set) wants `cover`.
|
|
311
|
+
*
|
|
312
|
+
* @see Avatar
|
|
313
|
+
*/
|
|
314
|
+
export type AvatarImageFitProp = "cover" | "contain";
|
|
306
315
|
/** @see Badge */
|
|
307
316
|
export type BadgeProp = {
|
|
308
317
|
/**
|
|
@@ -2361,6 +2361,40 @@ export type AttachmentsRefProp = {
|
|
|
2361
2361
|
multiple?: boolean;
|
|
2362
2362
|
}) => void;
|
|
2363
2363
|
};
|
|
2364
|
+
/** One row of a {@link SortableListProp}: its key (`value`), and the name the announcements speak. */
|
|
2365
|
+
export type SortableListItemProp = {
|
|
2366
|
+
/** Stable key — the order is reported as these keys. */
|
|
2367
|
+
value: string;
|
|
2368
|
+
/** Spoken in the handle's name and the live announcements; rendered when there is no `renderItem`. */
|
|
2369
|
+
label: string;
|
|
2370
|
+
/** This item's grip is inert; other items still move past it. */
|
|
2371
|
+
disabled?: boolean;
|
|
2372
|
+
};
|
|
2373
|
+
/**
|
|
2374
|
+
* @see SortableList — reorder a flat list or a grid of tiles (gh#1173): pointer drag on each
|
|
2375
|
+
* item's grip, or the keyboard (Space/Enter lift, arrows / Home / End move, Space/Enter drop, Esc
|
|
2376
|
+
* cancel), with every step announced politely ("2 / 3"). The order is the value.
|
|
2377
|
+
*/
|
|
2378
|
+
export type SortableListProp = {
|
|
2379
|
+
items: SortableListItemProp[];
|
|
2380
|
+
/** The item keys in order. Controlled with `onValueChange`; keys not in `items` are ignored. */
|
|
2381
|
+
value?: ValueProp<string[]>;
|
|
2382
|
+
defaultValue?: DefaultValueProp<string[]>;
|
|
2383
|
+
/** Called once per drop with the keys in their new order (not on every preview step). */
|
|
2384
|
+
onValueChange?: OnValueChangeProp<string[]>;
|
|
2385
|
+
/** Draws an item's content beside its grip. Default: the item's `label`. */
|
|
2386
|
+
renderItem?: (item: SortableListItemProp, state: {
|
|
2387
|
+
index: number;
|
|
2388
|
+
dragging: boolean;
|
|
2389
|
+
}) => React.ReactNode;
|
|
2390
|
+
/** `list` stacks rows; `grid` flows tiles into auto-filled columns. Default `list`. */
|
|
2391
|
+
layout?: "list" | "grid";
|
|
2392
|
+
disabled?: DisabledProp;
|
|
2393
|
+
/** Accessible name of the list. */
|
|
2394
|
+
"aria-label"?: string;
|
|
2395
|
+
id?: IdProp;
|
|
2396
|
+
className?: ClassNameProp;
|
|
2397
|
+
};
|
|
2364
2398
|
/**
|
|
2365
2399
|
* @see EmojiPicker — a page icon / reaction picker (gh#1164): search by ja / en / vi keywords
|
|
2366
2400
|
* (emojibase-data, loaded for the active locale on first open), categories, per-user recents, a
|