@botiverse/raft-ui 0.82.0 → 0.84.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/CHANGELOG.md +147 -0
- package/dist/components/app-link-card/app-link-card.mjs +19 -37
- package/dist/components/banner/banner.mjs +15 -2
- package/dist/components/dialog/dialog-sheet-context.mjs +7 -2
- package/dist/components/dialog/dialog-sheet.d.mts +3 -2
- package/dist/components/dialog/dialog-sheet.mjs +29 -43
- package/dist/components/dialog/dialog.d.mts +12 -1
- package/dist/components/dialog/dialog.mjs +50 -10
- package/dist/components/drag/drag.recipe.mjs +1 -1
- package/dist/components/fit-toolbar/fit-toolbar.d.mts +165 -0
- package/dist/components/fit-toolbar/fit-toolbar.mjs +736 -0
- package/dist/components/fit-toolbar/fit-toolbar.recipe.mjs +35 -0
- package/dist/components/fit-toolbar/index.d.mts +2 -0
- package/dist/components/lightbox/index.d.mts +2 -2
- package/dist/components/lightbox/lightbox.d.mts +70 -4
- package/dist/components/lightbox/lightbox.mjs +285 -91
- package/dist/components/list-row/list-row.d.mts +14 -1
- package/dist/components/list-row/list-row.mjs +4 -2
- package/dist/components/list-row/list-row.recipe.d.mts +15 -3
- package/dist/components/list-row/list-row.recipe.mjs +18 -1
- package/dist/components/message-action-card/message-action-card-content.recipe.mjs +3 -3
- package/dist/components/message-action-card/message-action-card.mjs +7 -3
- package/dist/components/message-action-card/message-action-card.recipe.mjs +2 -6
- package/dist/components/message-embed/index.d.mts +2 -1
- package/dist/components/message-embed/message-embed.d.mts +8 -1
- package/dist/components/message-embed/message-embed.mjs +2 -2
- package/dist/components/message-embed/message-embed.recipe.d.mts +160 -0
- package/dist/components/message-embed/message-embed.recipe.mjs +49 -10
- package/dist/components/message-forwarded-bundle/message-forwarded-bundle.mjs +1 -0
- package/dist/components/message-forwarded-bundle/message-forwarded-bundle.recipe.mjs +1 -1
- package/dist/components/message-quoted-preview/message-quoted-preview.mjs +2 -0
- package/dist/components/message-replies/message-replies.mjs +1 -1
- package/dist/components/message-replies/message-replies.recipe.mjs +1 -1
- package/dist/components/mobile-nav/mobile-nav-server.d.mts +21 -8
- package/dist/components/mobile-nav/mobile-nav-server.mjs +142 -13
- package/dist/components/popover/popover.d.mts +11 -2
- package/dist/components/popover/popover.mjs +51 -38
- package/dist/components/preview-card/preview-card.d.mts +5 -1
- package/dist/components/preview-card/preview-card.mjs +24 -27
- package/dist/components/save-status/save-status.d.mts +16 -9
- package/dist/components/save-status/save-status.mjs +19 -30
- package/dist/components/segmented-control/segmented-control.d.mts +1 -1
- package/dist/components/segmented-control/segmented-control.mjs +56 -10
- package/dist/components/selection-toolbar/index.d.mts +2 -0
- package/dist/components/selection-toolbar/selection-toolbar.d.mts +100 -0
- package/dist/components/selection-toolbar/selection-toolbar.mjs +217 -0
- package/dist/components/selection-toolbar/selection-toolbar.recipe.mjs +42 -0
- package/dist/components/task-board/task-board.d.mts +5 -0
- package/dist/components/task-board/task-board.mjs +8 -1
- package/dist/components/task-dnd/task-dnd.d.mts +10 -1
- package/dist/components/task-dnd/task-dnd.mjs +2 -1
- package/dist/components/toast/toast.mjs +2 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +4 -2
- package/dist/lib/label-value-fit.mjs +33 -6
- package/dist/lib/laid-out-children.mjs +32 -0
- package/dist/lib/stacked-actions.mjs +7 -4
- package/dist/lib/use-mobile-viewport.mjs +5 -2
- package/dist/lib/use-soft-keyboard.mjs +72 -0
- package/dist/wip.d.mts +8 -3
- package/dist/wip.mjs +5 -3
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tv } from "../../lib/tv.mjs";
|
|
2
|
+
import { scrollToElement } from "../scroll-area/scroll-to-element.mjs";
|
|
2
3
|
import { ScrollArea } from "../scroll-area/scroll-area.mjs";
|
|
3
4
|
import { MobileNavItem } from "./mobile-nav.mjs";
|
|
4
5
|
import "../../lib/touch-drag.mjs";
|
|
@@ -16,9 +17,10 @@ const mobileNavServer = tv({ slots: {
|
|
|
16
17
|
content: [
|
|
17
18
|
"flex flex-col",
|
|
18
19
|
"w-[calc(var(--anchor-width)-16px)] min-w-0",
|
|
19
|
-
"max-h-[min(60dvh,calc(var(--available-height)-env(safe-area-inset-top,0px)))]"
|
|
20
|
+
"max-h-[min(max(60dvh,var(--mobile-nav-server-popover-floor,0px)),calc(var(--available-height)-env(safe-area-inset-top,0px)))]"
|
|
20
21
|
],
|
|
21
|
-
|
|
22
|
+
scroll: ["flex min-h-0 flex-col"],
|
|
23
|
+
list: ["flex min-h-[calc(var(--spacing-option)+var(--text-mono-meta--line-height)+(--spacing(2)))] flex-1 flex-col p-1"],
|
|
22
24
|
group: ["flex shrink-0 flex-col p-1"],
|
|
23
25
|
row: [
|
|
24
26
|
"flex w-full shrink-0 cursor-default select-none items-center",
|
|
@@ -26,7 +28,7 @@ const mobileNavServer = tv({ slots: {
|
|
|
26
28
|
"min-h-option gap-2.5 rounded-chip px-2.5 py-option-inset",
|
|
27
29
|
"font-sans text-callout font-medium text-foreground",
|
|
28
30
|
"hover:bg-brand-soft active:bg-brand-soft focus-visible:bg-brand-soft",
|
|
29
|
-
"aria-
|
|
31
|
+
"aria-current:font-bold",
|
|
30
32
|
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&>svg:not([class*='size-'])]:size-4",
|
|
31
33
|
"[&>svg]:text-foreground-muted",
|
|
32
34
|
"*:data-[slot=avatar]:size-5 *:data-[slot=avatar]:shrink-0"
|
|
@@ -162,21 +164,49 @@ function MobileNavServerItem({ className, id, ref, onClick, onContextMenu, onPoi
|
|
|
162
164
|
}
|
|
163
165
|
/**
|
|
164
166
|
* The popover above the tab bar, 8px from each side of it and 8px above it,
|
|
165
|
-
* as tall as its content and at most 60% of the screen.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
167
|
+
* as tall as its content and at most 60% of the screen. The one exception:
|
|
168
|
+
* the list always keeps one whole server row, so where the parts after it
|
|
169
|
+
* leave less, the popover grows past 60% as far as that row needs (the
|
|
170
|
+
* floor, measured from its children into `--mobile-nav-server-popover-floor`).
|
|
171
|
+
* Where even that doesn't fit, the whole popover scrolls, the footer and the
|
|
172
|
+
* pages with it: the children sit in a ScrollArea
|
|
173
|
+
* (`data-slot="mobile-nav-server-popover-scroll"`) inside the popup. It stops below the top safe area (a notch in a standalone
|
|
174
|
+
* web app): Base UI's collision padding takes only pixels, so the height cap
|
|
175
|
+
* subtracts `env(safe-area-inset-top)`. The bottom needs none: the tab bar
|
|
176
|
+
* under the popover already pads the bottom safe area. A column:
|
|
170
177
|
* `MobileNavServerPopoverList` takes the room left and scrolls, and the
|
|
171
178
|
* destinations and the footer after it stay in view.
|
|
179
|
+
*
|
|
180
|
+
* Focus on open goes to the current server's row (`aria-current="true"` in
|
|
181
|
+
* the list), which the list has already scrolled into view, so focusing it
|
|
182
|
+
* never scrolls the list back to the top. A touch opens it with focus on the
|
|
183
|
+
* popover itself, without scrolling, so no keyboard comes up. `initialFocus`
|
|
184
|
+
* overrides this.
|
|
172
185
|
*/
|
|
173
|
-
function MobileNavServerPopoverContent({ anchor, side = "top", align = "center", sideOffset = 8, collisionPadding = 8, className, ...props }) {
|
|
186
|
+
function MobileNavServerPopoverContent({ anchor, side = "top", align = "center", sideOffset = 8, collisionPadding = 8, initialFocus, className, children, ref, ...props }) {
|
|
174
187
|
const { triggerRef } = useMobileNavServerContext("MobileNavServerPopoverContent");
|
|
175
|
-
const { content } = mobileNavServer();
|
|
188
|
+
const { content, scroll } = mobileNavServer();
|
|
189
|
+
const popupRef = useRef(null);
|
|
176
190
|
const tabBar = useCallback(() => {
|
|
177
191
|
const trigger = triggerRef.current;
|
|
178
192
|
return trigger?.closest("[data-slot=mobile-nav-root]") ?? trigger;
|
|
179
193
|
}, [triggerRef]);
|
|
194
|
+
const setRef = useCallback((node) => {
|
|
195
|
+
popupRef.current = node;
|
|
196
|
+
assignRef(ref, node);
|
|
197
|
+
if (!node) return;
|
|
198
|
+
return () => {
|
|
199
|
+
popupRef.current = null;
|
|
200
|
+
assignRef(ref, null);
|
|
201
|
+
};
|
|
202
|
+
}, [ref]);
|
|
203
|
+
const setScrollRef = useCallback((node) => {
|
|
204
|
+
if (node) return keepOneRow(node);
|
|
205
|
+
}, []);
|
|
206
|
+
const focusCurrent = useCallback((openType) => {
|
|
207
|
+
if (openType === "touch") return true;
|
|
208
|
+
return popupRef.current?.querySelector(CURRENT_ROW) ?? true;
|
|
209
|
+
}, []);
|
|
180
210
|
return /* @__PURE__ */ jsx(PopoverContent, {
|
|
181
211
|
"data-slot": "mobile-nav-server-popover-content",
|
|
182
212
|
anchor: anchor ?? tabBar,
|
|
@@ -184,16 +214,115 @@ function MobileNavServerPopoverContent({ anchor, side = "top", align = "center",
|
|
|
184
214
|
align,
|
|
185
215
|
sideOffset,
|
|
186
216
|
collisionPadding,
|
|
217
|
+
initialFocus: initialFocus ?? focusCurrent,
|
|
187
218
|
className: content({ className }),
|
|
188
|
-
|
|
219
|
+
ref: setRef,
|
|
220
|
+
...props,
|
|
221
|
+
children: /* @__PURE__ */ jsx(ScrollArea, {
|
|
222
|
+
"data-slot": "mobile-nav-server-popover-scroll",
|
|
223
|
+
overscroll: "contain",
|
|
224
|
+
tabIndex: -1,
|
|
225
|
+
ref: setScrollRef,
|
|
226
|
+
className: scroll(),
|
|
227
|
+
children
|
|
228
|
+
})
|
|
189
229
|
});
|
|
190
230
|
}
|
|
231
|
+
const LIST_SLOT = "mobile-nav-server-popover-list";
|
|
232
|
+
const CURRENT_ROW = `[data-slot=${LIST_SLOT}] [aria-current="true"]`;
|
|
233
|
+
const GUTTERS_SLOT = "scroll-area-gutters";
|
|
234
|
+
/** Padding and border along the block axis. */
|
|
235
|
+
function blockFrame(element) {
|
|
236
|
+
const style = getComputedStyle(element);
|
|
237
|
+
return parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Writes the popover's floor onto the popup, its height with the list at its
|
|
241
|
+
* one-row minimum: the list's `min-height` plus the height of every other
|
|
242
|
+
* part in the scroll area, and the popup's and the area's own padding and
|
|
243
|
+
* border. Watched as the parts change.
|
|
244
|
+
*/
|
|
245
|
+
function keepOneRow(area) {
|
|
246
|
+
const popup = area.parentElement;
|
|
247
|
+
const measure = () => {
|
|
248
|
+
if (!popup) return;
|
|
249
|
+
let floor = blockFrame(popup) + blockFrame(area);
|
|
250
|
+
for (const child of area.children) {
|
|
251
|
+
if (!(child instanceof HTMLElement) || child.dataset.slot === GUTTERS_SLOT) continue;
|
|
252
|
+
const style = getComputedStyle(child);
|
|
253
|
+
if (style.display === "none" || style.position === "absolute" || style.position === "fixed") continue;
|
|
254
|
+
floor += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
|
|
255
|
+
floor += child.dataset.slot === LIST_SLOT ? parseFloat(style.minHeight) || 0 : child.getBoundingClientRect().height;
|
|
256
|
+
}
|
|
257
|
+
popup.style.setProperty("--mobile-nav-server-popover-floor", `${floor}px`);
|
|
258
|
+
};
|
|
259
|
+
const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
|
|
260
|
+
const watch = () => {
|
|
261
|
+
resize?.disconnect();
|
|
262
|
+
for (const child of area.children) if (child instanceof HTMLElement && child.dataset.slot !== LIST_SLOT) resize?.observe(child);
|
|
263
|
+
measure();
|
|
264
|
+
};
|
|
265
|
+
const mutation = new MutationObserver(watch);
|
|
266
|
+
mutation.observe(area, { childList: true });
|
|
267
|
+
watch();
|
|
268
|
+
return () => {
|
|
269
|
+
resize?.disconnect();
|
|
270
|
+
mutation.disconnect();
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Opens the list on the current server: scrolls it, and only it, to the row
|
|
275
|
+
* with `aria-current="true"` at once, with no animation, and again as the
|
|
276
|
+
* list takes its final size, until people scroll or press in it themselves.
|
|
277
|
+
*/
|
|
278
|
+
function openOnCurrent(list) {
|
|
279
|
+
let pinned = true;
|
|
280
|
+
const show = () => {
|
|
281
|
+
if (!pinned) return;
|
|
282
|
+
const current = list.querySelector("[aria-current=\"true\"]");
|
|
283
|
+
if (!current) return;
|
|
284
|
+
const offset = parseFloat(getComputedStyle(list).paddingBottom) || 0;
|
|
285
|
+
scrollToElement(list, current, "vertical", {
|
|
286
|
+
block: "nearest",
|
|
287
|
+
offset
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
const release = () => {
|
|
291
|
+
pinned = false;
|
|
292
|
+
};
|
|
293
|
+
const events = [
|
|
294
|
+
"pointerdown",
|
|
295
|
+
"touchstart",
|
|
296
|
+
"wheel",
|
|
297
|
+
"keydown"
|
|
298
|
+
];
|
|
299
|
+
for (const type of events) list.addEventListener(type, release, { passive: true });
|
|
300
|
+
const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(show);
|
|
301
|
+
resize?.observe(list);
|
|
302
|
+
show();
|
|
303
|
+
return () => {
|
|
304
|
+
resize?.disconnect();
|
|
305
|
+
for (const type of events) list.removeEventListener(type, release);
|
|
306
|
+
};
|
|
307
|
+
}
|
|
191
308
|
/** The server list: takes the room left in the popover and scrolls inside it,
|
|
192
|
-
* a ScrollArea (a column with no gap, so its gutters move nothing).
|
|
193
|
-
|
|
309
|
+
* a ScrollArea (a column with no gap, so its gutters move nothing). It is
|
|
310
|
+
* never shorter than one whole two-line row, and it opens scrolled to the
|
|
311
|
+
* current server's row (`aria-current="true"`), with no animation. */
|
|
312
|
+
function MobileNavServerPopoverList({ className, ref, ...props }) {
|
|
194
313
|
const { list } = mobileNavServer();
|
|
314
|
+
const setRef = useCallback((node) => {
|
|
315
|
+
assignRef(ref, node);
|
|
316
|
+
if (!node) return;
|
|
317
|
+
const stop = openOnCurrent(node);
|
|
318
|
+
return () => {
|
|
319
|
+
stop();
|
|
320
|
+
assignRef(ref, null);
|
|
321
|
+
};
|
|
322
|
+
}, [ref]);
|
|
195
323
|
return /* @__PURE__ */ jsx(ScrollArea, {
|
|
196
324
|
"data-slot": "mobile-nav-server-popover-list",
|
|
325
|
+
ref: setRef,
|
|
197
326
|
overscroll: "contain",
|
|
198
327
|
tabIndex: -1,
|
|
199
328
|
...props,
|
|
@@ -13,8 +13,17 @@ export declare function PopoverPopup({ className, ...props }: PopoverPopupProps)
|
|
|
13
13
|
export type PopoverContentProps = StyledProps<Popover.Popup.Props> & Pick<Popover.Positioner.Props, "align" | "alignOffset" | "anchor" | "arrowPadding" | "collisionAvoidance" | "collisionBoundary" | "collisionPadding" | "disableAnchorTracking" | "positionMethod" | "side" | "sideOffset" | "sticky"> & {
|
|
14
14
|
container?: PopoverPortalProps["container"];
|
|
15
15
|
portalProps?: PopoverPortalProps;
|
|
16
|
+
/**
|
|
17
|
+
* `item` for a popover about an app's item, such as the question before
|
|
18
|
+
* its link previews are shown: 400px, and `PopoverBody`, `PopoverTitle`
|
|
19
|
+
* and `PopoverActions` take a card's parts and 12px inset, like the item
|
|
20
|
+
* card (PreviewCard `size="item"`). Choose it by what the popover is
|
|
21
|
+
* about, not by its parts.
|
|
22
|
+
* @default "default"
|
|
23
|
+
*/
|
|
24
|
+
size?: "default" | "item";
|
|
16
25
|
};
|
|
17
|
-
export declare function PopoverContent({ align, alignOffset, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, side, sideOffset, sticky, container, portalProps, className, ...props }: PopoverContentProps): import("react").JSX.Element;
|
|
26
|
+
export declare function PopoverContent({ align, alignOffset, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, side, sideOffset, sticky, container, portalProps, size, className, ...props }: PopoverContentProps): import("react").JSX.Element;
|
|
18
27
|
export type PopoverViewportProps = StyledProps<Popover.Viewport.Props>;
|
|
19
28
|
export declare function PopoverViewport({ className, ...props }: PopoverViewportProps): import("react").JSX.Element;
|
|
20
29
|
export type PopoverHeaderProps = ComponentPropsWithRef<"div">;
|
|
@@ -32,7 +41,7 @@ export declare function PopoverArrow({ className, ...props }: PopoverArrowProps)
|
|
|
32
41
|
export type PopoverBackdropProps = StyledProps<Popover.Backdrop.Props>;
|
|
33
42
|
export declare function PopoverBackdrop({ className, ...props }: PopoverBackdropProps): import("react").JSX.Element;
|
|
34
43
|
export type PopoverBodyProps = ComponentPropsWithRef<"div">;
|
|
35
|
-
/** The panel's text: a `PopoverTitle` and a `PopoverDescription`, 16px in from its edges. */
|
|
44
|
+
/** The panel's text: a `PopoverTitle` and a `PopoverDescription`, 16px in from its edges (12px in a `size="item"` popover). */
|
|
36
45
|
export declare function PopoverBody({ className, ...props }: PopoverBodyProps): import("react").JSX.Element;
|
|
37
46
|
export type PopoverActionsProps = ComponentPropsWithRef<"div">;
|
|
38
47
|
/** The panel's buttons, at its end, under `PopoverBody`: the way out first, then the confirm button. */
|
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
import { tv } from "../../lib/tv.mjs";
|
|
2
2
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
3
3
|
import { usePopupCollision } from "../../lib/frame-insets.mjs";
|
|
4
|
+
import { messageCard } from "../message-embed/message-embed.recipe.mjs";
|
|
5
|
+
import { createContext, useContext } from "react";
|
|
4
6
|
import { jsx } from "react/jsx-runtime";
|
|
5
7
|
import { Popover } from "@base-ui/react/popover";
|
|
6
8
|
//#region src/components/popover/popover.tsx
|
|
7
|
-
const popover = tv({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
9
|
+
const popover = tv({
|
|
10
|
+
slots: {
|
|
11
|
+
trigger: ["select-none"],
|
|
12
|
+
positioner: ["isolate z-50 outline-none"],
|
|
13
|
+
content: [
|
|
14
|
+
"max-h-(--available-height) min-w-[220px] overflow-hidden",
|
|
15
|
+
"outline-none",
|
|
16
|
+
"rounded-container bg-layer-popover text-foreground",
|
|
17
|
+
"shadow-float ring-1 ring-line-seam"
|
|
18
|
+
],
|
|
19
|
+
header: ["flex items-center justify-between px-3", "min-h-11 gap-2 py-1.5 pr-1.5 pl-4"],
|
|
20
|
+
body: ["flex min-w-0 flex-col gap-1.5", "px-4 pt-4 pb-3 last:pb-4"],
|
|
21
|
+
actions: ["flex flex-wrap items-center justify-end gap-2", "px-4 pb-3 last:pb-4"],
|
|
22
|
+
separator: ["border-t", "border-line-hairline"],
|
|
23
|
+
title: ["m-0", "font-sans text-headline font-bold text-foreground"],
|
|
24
|
+
description: ["m-0", "font-sans text-footnote text-foreground-muted"],
|
|
25
|
+
close: [
|
|
26
|
+
"inline-flex items-center justify-center",
|
|
27
|
+
"outline-none transition-colors",
|
|
28
|
+
"h-8 min-w-8 rounded-chip px-1.5",
|
|
29
|
+
"font-sans text-footnote font-bold text-foreground",
|
|
30
|
+
"hover:bg-fill-medium active:bg-fill-medium",
|
|
31
|
+
"focus-visible:shadow-focus",
|
|
32
|
+
"[&_svg]:size-4.5"
|
|
33
|
+
],
|
|
34
|
+
arrow: ["size-2 rotate-45", "border-l border-t border-line-seam bg-layer-popover"],
|
|
35
|
+
backdrop: ["fixed inset-0", "bg-transparent"],
|
|
36
|
+
viewport: ["block"]
|
|
37
|
+
},
|
|
38
|
+
variants: { size: {
|
|
39
|
+
default: {},
|
|
40
|
+
item: { content: messageCard().popover() }
|
|
41
|
+
} }
|
|
42
|
+
});
|
|
43
|
+
const PopoverSizeContext = createContext("default");
|
|
35
44
|
function Popover$1(props) {
|
|
36
45
|
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
37
46
|
"data-slot": "popover",
|
|
@@ -67,8 +76,8 @@ function PopoverFramePositioner({ collisionBoundary, collisionPadding, container
|
|
|
67
76
|
...props
|
|
68
77
|
});
|
|
69
78
|
}
|
|
70
|
-
function PopoverContent({ align = "start", alignOffset = 0, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod = "fixed", side = "bottom", sideOffset = 4, sticky, container, portalProps, className, ...props }) {
|
|
71
|
-
const { positioner, content } = popover();
|
|
79
|
+
function PopoverContent({ align = "start", alignOffset = 0, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod = "fixed", side = "bottom", sideOffset = 4, sticky, container, portalProps, size = "default", className, ...props }) {
|
|
80
|
+
const { positioner, content } = popover({ size });
|
|
72
81
|
const portalContainer = container !== void 0 ? container : portalProps?.container;
|
|
73
82
|
return /* @__PURE__ */ jsx(PopoverPortal, {
|
|
74
83
|
...portalProps,
|
|
@@ -88,10 +97,14 @@ function PopoverContent({ align = "start", alignOffset = 0, anchor, arrowPadding
|
|
|
88
97
|
sideOffset,
|
|
89
98
|
sticky,
|
|
90
99
|
className: positioner(),
|
|
91
|
-
children: /* @__PURE__ */ jsx(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
children: /* @__PURE__ */ jsx(PopoverSizeContext.Provider, {
|
|
101
|
+
value: size,
|
|
102
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
103
|
+
"data-slot": "popover-content",
|
|
104
|
+
"data-size": size,
|
|
105
|
+
className: content({ className }),
|
|
106
|
+
...props
|
|
107
|
+
})
|
|
95
108
|
})
|
|
96
109
|
})
|
|
97
110
|
});
|
|
@@ -123,7 +136,7 @@ function PopoverSeparator({ className, ...props }) {
|
|
|
123
136
|
});
|
|
124
137
|
}
|
|
125
138
|
function PopoverTitle({ className, ...props }) {
|
|
126
|
-
const
|
|
139
|
+
const title = useContext(PopoverSizeContext) === "item" ? messageCard().title : popover().title;
|
|
127
140
|
return /* @__PURE__ */ jsx(Popover.Title, {
|
|
128
141
|
"data-slot": "popover-title",
|
|
129
142
|
className: title({ className }),
|
|
@@ -162,9 +175,9 @@ function PopoverBackdrop({ className, ...props }) {
|
|
|
162
175
|
...props
|
|
163
176
|
});
|
|
164
177
|
}
|
|
165
|
-
/** The panel's text: a `PopoverTitle` and a `PopoverDescription`, 16px in from its edges. */
|
|
178
|
+
/** The panel's text: a `PopoverTitle` and a `PopoverDescription`, 16px in from its edges (12px in a `size="item"` popover). */
|
|
166
179
|
function PopoverBody({ className, ...props }) {
|
|
167
|
-
const
|
|
180
|
+
const body = useContext(PopoverSizeContext) === "item" ? messageCard().body : popover().body;
|
|
168
181
|
return /* @__PURE__ */ jsx("div", {
|
|
169
182
|
"data-slot": "popover-body",
|
|
170
183
|
className: body({ className }),
|
|
@@ -173,7 +186,7 @@ function PopoverBody({ className, ...props }) {
|
|
|
173
186
|
}
|
|
174
187
|
/** The panel's buttons, at its end, under `PopoverBody`: the way out first, then the confirm button. */
|
|
175
188
|
function PopoverActions({ className, ...props }) {
|
|
176
|
-
const
|
|
189
|
+
const actions = useContext(PopoverSizeContext) === "item" ? messageCard().actions : popover().actions;
|
|
177
190
|
return /* @__PURE__ */ jsx("div", {
|
|
178
191
|
"data-slot": "popover-actions",
|
|
179
192
|
className: actions({ className }),
|
|
@@ -28,7 +28,11 @@ export declare function PreviewCardDescription({ className, ...props }: PreviewC
|
|
|
28
28
|
export type PreviewCardArrowProps = StyledProps<PreviewCard.Arrow.Props>;
|
|
29
29
|
export declare function PreviewCardArrow({ className, ...props }: PreviewCardArrowProps): import("react").JSX.Element;
|
|
30
30
|
export type PreviewCardBodyProps = ComponentPropsWithRef<"div">;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The item card's block: the id line, the title and the details row, in that
|
|
33
|
+
* order. Its parts are a card's in a message (AppLinkCard), so the two read
|
|
34
|
+
* the same.
|
|
35
|
+
*/
|
|
32
36
|
export declare function PreviewCardBody({ className, ...props }: PreviewCardBodyProps): import("react").JSX.Element;
|
|
33
37
|
export type PreviewCardEyebrowProps = ComponentPropsWithRef<"p">;
|
|
34
38
|
/** The muted line above the title that identifies the item, such as its id. One line, truncated. */
|
|
@@ -2,6 +2,8 @@ import { tv } from "../../lib/tv.mjs";
|
|
|
2
2
|
import { useTheme } from "../../lib/theme/use-theme.mjs";
|
|
3
3
|
import { resolveThemeScopeProps, useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
4
4
|
import { usePopupCollision } from "../../lib/frame-insets.mjs";
|
|
5
|
+
import { messageCard } from "../message-embed/message-embed.recipe.mjs";
|
|
6
|
+
import { createContext, useContext } from "react";
|
|
5
7
|
import { jsx } from "react/jsx-runtime";
|
|
6
8
|
import { PreviewCard } from "@base-ui/react/preview-card";
|
|
7
9
|
//#region src/components/preview-card/preview-card.tsx
|
|
@@ -24,24 +26,11 @@ const previewCard = tv({
|
|
|
24
26
|
separator: ["border-t", "border-line-hairline"],
|
|
25
27
|
title: ["m-0 truncate", "font-sans text-headline font-bold text-foreground"],
|
|
26
28
|
description: ["m-0 truncate", "px-4 py-3 last:pb-4 font-sans text-callout text-foreground-muted"],
|
|
27
|
-
arrow: ["size-2 rotate-45", "border-l border-t border-line-seam bg-layer-popover"]
|
|
28
|
-
body: [
|
|
29
|
-
"flex min-w-0 flex-col gap-1.5",
|
|
30
|
-
"px-4 pt-4 pb-3 last:pb-4",
|
|
31
|
-
"*:data-[slot=preview-card-title]:line-clamp-2 *:data-[slot=preview-card-title]:whitespace-normal"
|
|
32
|
-
],
|
|
33
|
-
eyebrow: ["m-0 truncate", "font-mono text-mono-meta text-foreground-muted"],
|
|
34
|
-
meta: ["flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", "m-0 font-sans text-callout text-foreground-muted"],
|
|
35
|
-
actions: ["flex flex-wrap items-center justify-end gap-2", "px-4 pb-3 last:pb-4"],
|
|
36
|
-
footer: [
|
|
37
|
-
"m-0 truncate",
|
|
38
|
-
"border-t border-line-hairline px-4 py-2",
|
|
39
|
-
"font-sans text-caption text-foreground-muted"
|
|
40
|
-
]
|
|
29
|
+
arrow: ["size-2 rotate-45", "border-l border-t border-line-seam bg-layer-popover"]
|
|
41
30
|
},
|
|
42
31
|
variants: { size: {
|
|
43
32
|
profile: { popup: "w-[300px]" },
|
|
44
|
-
item: { popup:
|
|
33
|
+
item: { popup: messageCard().popover() }
|
|
45
34
|
} },
|
|
46
35
|
defaultVariants: { size: "profile" }
|
|
47
36
|
});
|
|
@@ -132,8 +121,9 @@ function PreviewCardSeparator({ className, ...props }) {
|
|
|
132
121
|
...props
|
|
133
122
|
});
|
|
134
123
|
}
|
|
124
|
+
const InItemBody = createContext(false);
|
|
135
125
|
function PreviewCardTitle({ className, ...props }) {
|
|
136
|
-
const
|
|
126
|
+
const title = useContext(InItemBody) ? messageCard().title : previewCard().title;
|
|
137
127
|
return /* @__PURE__ */ jsx("p", {
|
|
138
128
|
"data-slot": "preview-card-title",
|
|
139
129
|
className: title({ className }),
|
|
@@ -156,18 +146,25 @@ function PreviewCardArrow({ className, ...props }) {
|
|
|
156
146
|
...props
|
|
157
147
|
});
|
|
158
148
|
}
|
|
159
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* The item card's block: the id line, the title and the details row, in that
|
|
151
|
+
* order. Its parts are a card's in a message (AppLinkCard), so the two read
|
|
152
|
+
* the same.
|
|
153
|
+
*/
|
|
160
154
|
function PreviewCardBody({ className, ...props }) {
|
|
161
|
-
const { body } =
|
|
162
|
-
return /* @__PURE__ */ jsx(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
const { body } = messageCard();
|
|
156
|
+
return /* @__PURE__ */ jsx(InItemBody.Provider, {
|
|
157
|
+
value: true,
|
|
158
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
159
|
+
"data-slot": "preview-card-body",
|
|
160
|
+
className: body({ className }),
|
|
161
|
+
...props
|
|
162
|
+
})
|
|
166
163
|
});
|
|
167
164
|
}
|
|
168
165
|
/** The muted line above the title that identifies the item, such as its id. One line, truncated. */
|
|
169
166
|
function PreviewCardEyebrow({ className, ...props }) {
|
|
170
|
-
const { eyebrow } =
|
|
167
|
+
const { eyebrow } = messageCard();
|
|
171
168
|
return /* @__PURE__ */ jsx("p", {
|
|
172
169
|
"data-slot": "preview-card-eyebrow",
|
|
173
170
|
className: eyebrow({ className }),
|
|
@@ -176,7 +173,7 @@ function PreviewCardEyebrow({ className, ...props }) {
|
|
|
176
173
|
}
|
|
177
174
|
/** The row under the title: one state chip, then muted details. Wraps when it runs out of room. */
|
|
178
175
|
function PreviewCardMeta({ className, ...props }) {
|
|
179
|
-
const { meta } =
|
|
176
|
+
const { meta } = messageCard();
|
|
180
177
|
return /* @__PURE__ */ jsx("div", {
|
|
181
178
|
"data-slot": "preview-card-meta",
|
|
182
179
|
className: meta({ className }),
|
|
@@ -185,7 +182,7 @@ function PreviewCardMeta({ className, ...props }) {
|
|
|
185
182
|
}
|
|
186
183
|
/** The row of at most two buttons. */
|
|
187
184
|
function PreviewCardActions({ className, ...props }) {
|
|
188
|
-
const { actions } =
|
|
185
|
+
const { actions } = messageCard();
|
|
189
186
|
return /* @__PURE__ */ jsx("div", {
|
|
190
187
|
"data-slot": "preview-card-actions",
|
|
191
188
|
className: actions({ className }),
|
|
@@ -194,10 +191,10 @@ function PreviewCardActions({ className, ...props }) {
|
|
|
194
191
|
}
|
|
195
192
|
/** The caption under a hairline at the bottom of the card: where the item comes from. */
|
|
196
193
|
function PreviewCardFooter({ className, ...props }) {
|
|
197
|
-
const {
|
|
194
|
+
const { sourceFooter } = messageCard();
|
|
198
195
|
return /* @__PURE__ */ jsx("p", {
|
|
199
196
|
"data-slot": "preview-card-footer",
|
|
200
|
-
className:
|
|
197
|
+
className: sourceFooter({ className }),
|
|
201
198
|
...props
|
|
202
199
|
});
|
|
203
200
|
}
|
|
@@ -2,22 +2,29 @@ import { ComponentPropsWithRef } from "react";
|
|
|
2
2
|
//#region src/components/save-status/save-status.d.ts
|
|
3
3
|
export type SaveStatusProps = ComponentPropsWithRef<"span">;
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The line under a setting that applies at once. Render it whatever the
|
|
6
|
+
* setting's state, with `SaveStatusError` inside when the change failed and
|
|
7
|
+
* nothing inside otherwise: empty, it is hidden and takes no height. A `span`
|
|
8
|
+
* (shown as a block), like every ListRow part, so it stays valid inside a
|
|
9
|
+
* row's body; give it an `id` for the control's `aria-describedby`.
|
|
8
10
|
*/
|
|
9
11
|
export declare function SaveStatus({ className, ...props }: SaveStatusProps): import("react").JSX.Element;
|
|
10
12
|
export type SaveStatusSavedProps = ComponentPropsWithRef<"span">;
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* @deprecated A setting that applies at once no longer says "Saved": the
|
|
15
|
+
* control showing its new value is the confirmation (Forms, settings that
|
|
16
|
+
* apply at once). Renders nothing, whatever its children, so its
|
|
17
|
+
* `SaveStatus` stays empty and takes no height. Leave it out: render
|
|
18
|
+
* `SaveStatusError` when the change failed and nothing otherwise. It drops
|
|
19
|
+
* every prop, its `id` too: point a control's `aria-describedby` at the
|
|
20
|
+
* `SaveStatus` instead.
|
|
15
21
|
*/
|
|
16
|
-
export declare function SaveStatusSaved(
|
|
22
|
+
export declare function SaveStatusSaved(_props: SaveStatusSavedProps): null;
|
|
17
23
|
export type SaveStatusErrorProps = ComponentPropsWithRef<"span">;
|
|
18
24
|
/**
|
|
19
|
-
* What failed
|
|
20
|
-
*
|
|
25
|
+
* What failed. It is an alert, announced when it appears, and stays until
|
|
26
|
+
* the next change. Its line appears with it, so the setting grows by one
|
|
27
|
+
* line while it shows.
|
|
21
28
|
*/
|
|
22
29
|
export declare function SaveStatusError({ className, role, ...props }: SaveStatusErrorProps): import("react").JSX.Element;
|
|
23
30
|
//#endregion
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { tv } from "../../lib/tv.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
3
|
//#region src/components/save-status/save-status.tsx
|
|
5
4
|
const saveStatus = tv({ slots: {
|
|
6
5
|
root: [
|
|
7
|
-
"block min-
|
|
6
|
+
"block min-w-0 empty:hidden",
|
|
8
7
|
"mt-1 [[data-slot=field]>&]:-mt-0.5 [[data-slot=list-row-body]>&]:mt-0.5",
|
|
9
8
|
"text-footnote"
|
|
10
9
|
],
|
|
11
|
-
saved: [
|
|
12
|
-
"flex min-w-0 items-center gap-1",
|
|
13
|
-
"text-foreground-muted",
|
|
14
|
-
"[&>svg]:size-3 [&>svg]:shrink-0"
|
|
15
|
-
],
|
|
16
10
|
error: ["block wrap-anywhere", "text-danger-strong"]
|
|
17
11
|
} });
|
|
18
12
|
/**
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
13
|
+
* The line under a setting that applies at once. Render it whatever the
|
|
14
|
+
* setting's state, with `SaveStatusError` inside when the change failed and
|
|
15
|
+
* nothing inside otherwise: empty, it is hidden and takes no height. A `span`
|
|
16
|
+
* (shown as a block), like every ListRow part, so it stays valid inside a
|
|
17
|
+
* row's body; give it an `id` for the control's `aria-describedby`.
|
|
22
18
|
*/
|
|
23
19
|
function SaveStatus({ className, ...props }) {
|
|
24
20
|
const { root } = saveStatus();
|
|
@@ -29,28 +25,21 @@ function SaveStatus({ className, ...props }) {
|
|
|
29
25
|
});
|
|
30
26
|
}
|
|
31
27
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* @deprecated A setting that applies at once no longer says "Saved": the
|
|
29
|
+
* control showing its new value is the confirmation (Forms, settings that
|
|
30
|
+
* apply at once). Renders nothing, whatever its children, so its
|
|
31
|
+
* `SaveStatus` stays empty and takes no height. Leave it out: render
|
|
32
|
+
* `SaveStatusError` when the change failed and nothing otherwise. It drops
|
|
33
|
+
* every prop, its `id` too: point a control's `aria-describedby` at the
|
|
34
|
+
* `SaveStatus` instead.
|
|
35
35
|
*/
|
|
36
|
-
function SaveStatusSaved(
|
|
37
|
-
|
|
38
|
-
const hasContent = children !== null && children !== void 0 && children !== false && children !== "";
|
|
39
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
40
|
-
"data-slot": "save-status-saved",
|
|
41
|
-
role: "status",
|
|
42
|
-
"aria-live": "polite",
|
|
43
|
-
className: saved({ className }),
|
|
44
|
-
...props,
|
|
45
|
-
children: [hasContent ? /* @__PURE__ */ jsx(CircleCheck, {
|
|
46
|
-
"aria-hidden": "true",
|
|
47
|
-
"data-slot": "save-status-icon"
|
|
48
|
-
}) : null, children]
|
|
49
|
-
});
|
|
36
|
+
function SaveStatusSaved(_props) {
|
|
37
|
+
return null;
|
|
50
38
|
}
|
|
51
39
|
/**
|
|
52
|
-
* What failed
|
|
53
|
-
*
|
|
40
|
+
* What failed. It is an alert, announced when it appears, and stays until
|
|
41
|
+
* the next change. Its line appears with it, so the setting grows by one
|
|
42
|
+
* line while it shows.
|
|
54
43
|
*/
|
|
55
44
|
function SaveStatusError({ className, role = "alert", ...props }) {
|
|
56
45
|
const { error } = saveStatus();
|
|
@@ -28,7 +28,7 @@ export type SegmentedControlProps<T extends string = string> = StyledProps<Radio
|
|
|
28
28
|
*/
|
|
29
29
|
variant?: "default" | "on-frame";
|
|
30
30
|
};
|
|
31
|
-
export declare function SegmentedControl<T extends string = string>({ disabled, iconsOnlyBelow, variant, className, ref, ...props }: SegmentedControlProps<T>): import("react").JSX.Element;
|
|
31
|
+
export declare function SegmentedControl<T extends string = string>({ disabled, iconsOnlyBelow: iconsOnlyBelowProp, variant, className, ref, ...props }: SegmentedControlProps<T>): import("react").JSX.Element;
|
|
32
32
|
export type SegmentedControlItemProps<T extends string = string> = StyledProps<Radio.Root.Props<T>>;
|
|
33
33
|
export declare function SegmentedControlItem<T extends string = string>({ className, ref, ...props }: SegmentedControlItemProps<T>): import("react").JSX.Element;
|
|
34
34
|
export type SegmentedControlLabelProps = ComponentPropsWithRef<"span">;
|