@botiverse/raft-ui 0.21.1 → 0.22.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 +60 -0
- package/dist/{inbox-item.recipe-DelZxRmY.mjs → inbox-item.recipe-3j4DHKZ2.mjs} +1572 -389
- package/dist/{index-jTmbN7ov.d.mts → index-DT1Hg5N-.d.mts} +246 -54
- package/dist/index.d.mts +35 -77
- package/dist/index.mjs +171 -466
- package/dist/styles.css +7 -6
- package/dist/wip.d.mts +153 -3
- package/dist/wip.mjs +126 -2
- package/package.json +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { cn } from "./cn.mjs";
|
|
2
2
|
import { Dialog } from "@base-ui/react/dialog";
|
|
3
|
-
import { Check, Minus, X } from "lucide-react";
|
|
3
|
+
import { ArrowLeft, Check, Minus, X } from "lucide-react";
|
|
4
|
+
import { Children, Fragment, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
5
|
import { tv } from "tailwind-variants/lite";
|
|
5
|
-
import { Children, Fragment, createContext, isValidElement, use, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { Button } from "@base-ui/react/button";
|
|
8
8
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
9
9
|
import { useRender } from "@base-ui/react/use-render";
|
|
10
|
+
import { createPortal, flushSync } from "react-dom";
|
|
10
11
|
import { Tooltip } from "@base-ui/react/tooltip";
|
|
11
12
|
import { Checkbox } from "@base-ui/react/checkbox";
|
|
12
13
|
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
13
14
|
import { Avatar } from "@base-ui/react";
|
|
14
15
|
import { Menu } from "@base-ui/react/menu";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { Popover } from "@base-ui/react/popover";
|
|
17
|
+
import { PointerSensor, TouchSensor, useSensor } from "@dnd-kit/core";
|
|
18
|
+
import { PreviewCard } from "@base-ui/react/preview-card";
|
|
18
19
|
import { Toggle } from "@base-ui/react/toggle";
|
|
19
20
|
import { Toolbar } from "@base-ui/react/toolbar";
|
|
20
|
-
import { PointerSensor, TouchSensor, useSensor } from "@dnd-kit/core";
|
|
21
21
|
//#region src/lib/decision-width.ts
|
|
22
22
|
/**
|
|
23
23
|
* The width of every decision dialog (dialog.md#variants): AlertDialog and a
|
|
@@ -56,6 +56,103 @@ const headEdges = ["px-5 max-md:px-4", ...headEdgePull];
|
|
|
56
56
|
* as the arithmetic so it follows the control tokens wherever they switch.
|
|
57
57
|
*/
|
|
58
58
|
const controlIconInset = "[--icon-inset:calc((var(--control-height)-var(--control-icon-size))/2)]";
|
|
59
|
+
/**
|
|
60
|
+
* The row of buttons while `data-stacked` is set: a full-width column. A
|
|
61
|
+
* button alone in a wrapper (a tooltip's trigger) fills the width too.
|
|
62
|
+
*/
|
|
63
|
+
const stackedActions = [
|
|
64
|
+
"data-stacked:flex-col-reverse",
|
|
65
|
+
"data-stacked:items-stretch",
|
|
66
|
+
"data-stacked:*:data-[slot=button]:w-full data-stacked:[&>*>[data-slot=button]:only-child]:w-full",
|
|
67
|
+
...[].map((value) => `data-stacked:${value}`)
|
|
68
|
+
];
|
|
69
|
+
function assignRef$2(ref, value) {
|
|
70
|
+
if (typeof ref === "function") ref(value);
|
|
71
|
+
else if (ref) ref.current = value;
|
|
72
|
+
}
|
|
73
|
+
function contentWidth(element) {
|
|
74
|
+
const style = getComputedStyle(element);
|
|
75
|
+
return element.getBoundingClientRect().width - (Number.parseFloat(style.paddingLeft) || 0) - (Number.parseFloat(style.paddingRight) || 0) - (Number.parseFloat(style.borderLeftWidth) || 0) - (Number.parseFloat(style.borderRightWidth) || 0);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether a row of buttons must stack: laid out as a row (`data-stacked` off
|
|
79
|
+
* for the measure and put back at once, inside one frame, so nothing is
|
|
80
|
+
* painted in between and the layouts never flip back and forth), its children
|
|
81
|
+
* run past the content box of `frame`. Children that may shrink (a label that
|
|
82
|
+
* truncates or wraps) shrink first, so only content that can't, the buttons,
|
|
83
|
+
* counts as not fitting. Undefined while the frame is hidden (a sheet hides
|
|
84
|
+
* its footer), so nothing is decided then.
|
|
85
|
+
*/
|
|
86
|
+
function rowOverflows(row, frame) {
|
|
87
|
+
if (!(frame.getBoundingClientRect().width > 0)) return void 0;
|
|
88
|
+
const wasStacked = row.hasAttribute("data-stacked");
|
|
89
|
+
if (wasStacked) row.removeAttribute("data-stacked");
|
|
90
|
+
const available = contentWidth(frame);
|
|
91
|
+
let start = Number.POSITIVE_INFINITY;
|
|
92
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
93
|
+
for (const child of row.children) {
|
|
94
|
+
const box = child.getBoundingClientRect();
|
|
95
|
+
if (box.width === 0 && box.height === 0) continue;
|
|
96
|
+
const style = getComputedStyle(child);
|
|
97
|
+
start = Math.min(start, box.left - (Number.parseFloat(style.marginLeft) || 0));
|
|
98
|
+
end = Math.max(end, box.right + (Number.parseFloat(style.marginRight) || 0));
|
|
99
|
+
}
|
|
100
|
+
if (wasStacked) row.setAttribute("data-stacked", "");
|
|
101
|
+
return end - start > available + .5;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Sets `data-stacked` on a footer's row of buttons when it doesn't fit on one
|
|
105
|
+
* line (rowOverflows). Pass the returned ref to the row. It must fit its own
|
|
106
|
+
* content box, or with `inParent` (the group of buttons beside a note) its
|
|
107
|
+
* parent's. As BannerAction's `data-wrap`, it measures again when the footer
|
|
108
|
+
* resizes, the buttons change or a web font arrives.
|
|
109
|
+
*/
|
|
110
|
+
function useStackedActions(ref, inParent = false) {
|
|
111
|
+
const [row, setRow] = useState(null);
|
|
112
|
+
const [stacked, setStacked] = useState(false);
|
|
113
|
+
const callbackRef = useCallback((node) => {
|
|
114
|
+
setRow(node);
|
|
115
|
+
assignRef$2(ref, node);
|
|
116
|
+
}, [ref]);
|
|
117
|
+
useLayoutEffect(() => {
|
|
118
|
+
const frame = inParent ? row?.parentElement : row;
|
|
119
|
+
if (!row || !frame || typeof ResizeObserver === "undefined") return;
|
|
120
|
+
const measure = () => {
|
|
121
|
+
const overflows = rowOverflows(row, frame);
|
|
122
|
+
if (overflows !== void 0) setStacked(overflows);
|
|
123
|
+
};
|
|
124
|
+
let raf = 0;
|
|
125
|
+
const schedule = () => {
|
|
126
|
+
cancelAnimationFrame(raf);
|
|
127
|
+
raf = requestAnimationFrame(measure);
|
|
128
|
+
};
|
|
129
|
+
measure();
|
|
130
|
+
const resize = new ResizeObserver(schedule);
|
|
131
|
+
resize.observe(frame);
|
|
132
|
+
resize.observe(row);
|
|
133
|
+
for (const child of row.children) resize.observe(child);
|
|
134
|
+
const mutation = new MutationObserver(schedule);
|
|
135
|
+
mutation.observe(row, {
|
|
136
|
+
childList: true,
|
|
137
|
+
characterData: true,
|
|
138
|
+
subtree: true
|
|
139
|
+
});
|
|
140
|
+
const fonts = typeof document.fonts === "undefined" ? void 0 : document.fonts;
|
|
141
|
+
let live = true;
|
|
142
|
+
fonts?.ready.then(() => {
|
|
143
|
+
if (live) schedule();
|
|
144
|
+
});
|
|
145
|
+
fonts?.addEventListener("loadingdone", schedule);
|
|
146
|
+
return () => {
|
|
147
|
+
live = false;
|
|
148
|
+
cancelAnimationFrame(raf);
|
|
149
|
+
resize.disconnect();
|
|
150
|
+
mutation.disconnect();
|
|
151
|
+
fonts?.removeEventListener("loadingdone", schedule);
|
|
152
|
+
};
|
|
153
|
+
}, [row, inParent]);
|
|
154
|
+
return [stacked, callbackRef];
|
|
155
|
+
}
|
|
59
156
|
//#endregion
|
|
60
157
|
//#region src/lib/tv.ts
|
|
61
158
|
const tv$1 = ((options) => {
|
|
@@ -99,6 +196,16 @@ function useThemeScopeProps(props) {
|
|
|
99
196
|
//#endregion
|
|
100
197
|
//#region src/lib/overlay-depth.tsx
|
|
101
198
|
const OverlayStackContext = createContext([]);
|
|
199
|
+
/**
|
|
200
|
+
* Tells everything under it that a modal surface is open, so a raft-ui
|
|
201
|
+
* Dialog, AlertDialog or Drawer opened from inside draws the lighter nested
|
|
202
|
+
* scrim (design/foundations/elevation.md#stacked-overlays). raft-ui's own
|
|
203
|
+
* roots add one by themselves; an app's own modal (its own backdrop, Esc and
|
|
204
|
+
* focus) wraps its scrim and its content in one to join the stack, and reads
|
|
205
|
+
* `useParentOverlayKind` inside it to pick its own scrim. Modal overlays only:
|
|
206
|
+
* a non-modal one passes `modal={false}`, adds nothing, and must not call
|
|
207
|
+
* `useParentOverlayKind` (it would read the layer below as its own).
|
|
208
|
+
*/
|
|
102
209
|
function OverlayLayerProvider({ kind, modal, children }) {
|
|
103
210
|
const stack = useContext(OverlayStackContext);
|
|
104
211
|
const value = useMemo(() => modal ? [...stack, kind] : stack, [
|
|
@@ -113,8 +220,14 @@ function OverlayLayerProvider({ kind, modal, children }) {
|
|
|
113
220
|
}
|
|
114
221
|
/**
|
|
115
222
|
* The modal surface this overlay's own surface was opened from, or null when
|
|
116
|
-
* it is the first.
|
|
117
|
-
*
|
|
223
|
+
* it is the first. Call it inside the surface's own `OverlayLayerProvider`
|
|
224
|
+
* (for raft-ui's overlays, inside their root): the last entry is the surface
|
|
225
|
+
* itself and the one before it is its parent. Not null means the surface is
|
|
226
|
+
* stacked over another and draws `layer-backdrop-nested` instead of
|
|
227
|
+
* `layer-backdrop`. Modal overlays only: under `modal={false}` nothing is
|
|
228
|
+
* added, so the last entry is the parent and this returns the wrong layer. It reads React context, so it sees the surfaces whose
|
|
229
|
+
* tree this one is rendered in, portals included; one opened from outside
|
|
230
|
+
* that tree is not stacked on it here.
|
|
118
231
|
*/
|
|
119
232
|
function useParentOverlayKind() {
|
|
120
233
|
const stack = useContext(OverlayStackContext);
|
|
@@ -412,6 +525,141 @@ function Button$1({ variant, size, loading = false, loadingLabel = "Loading", di
|
|
|
412
525
|
});
|
|
413
526
|
}
|
|
414
527
|
//#endregion
|
|
528
|
+
//#region src/components/dialog/dialog-sheet-context.ts
|
|
529
|
+
const NO_SHEET = {
|
|
530
|
+
sheet: false,
|
|
531
|
+
commitSlot: null,
|
|
532
|
+
setCommitSlot: () => void 0,
|
|
533
|
+
bodyEndSlot: null,
|
|
534
|
+
setBodyEndSlot: () => void 0
|
|
535
|
+
};
|
|
536
|
+
const DialogSheetContext = createContext(NO_SHEET);
|
|
537
|
+
function useDialogSheet() {
|
|
538
|
+
return useContext(DialogSheetContext);
|
|
539
|
+
}
|
|
540
|
+
/** Inside the sheet's head: its × is a pushed page's leading action. */
|
|
541
|
+
const DialogSheetHeadContext = createContext(false);
|
|
542
|
+
function useInDialogSheetHead() {
|
|
543
|
+
return useContext(DialogSheetHeadContext);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Every overlay surface starts outside any sheet: a confirm, a dialog or a
|
|
547
|
+
* drawer opened from inside a sheet keeps its own head, footer and commit
|
|
548
|
+
* button instead of taking the sheet's.
|
|
549
|
+
*/
|
|
550
|
+
function DialogSheetReset({ children }) {
|
|
551
|
+
return createElement(DialogSheetContext.Provider, { value: NO_SHEET }, createElement(DialogSheetHeadContext.Provider, { value: false }, children));
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* A dialog drawn as a phone sheet (design: dialog.md "On a phone"). The
|
|
555
|
+
* surface is DialogContent's frame docked to the bottom, 90% of the screen
|
|
556
|
+
* tall, rounded on its top corners, sliding up as a bottom sheet does; its
|
|
557
|
+
* head is a pushed page's head (× at the start, the title, the commit button
|
|
558
|
+
* at the end); its body keeps the phone's 16px margin and the home indicator's
|
|
559
|
+
* inset. Merged over DialogContent's classes, so these win.
|
|
560
|
+
*/
|
|
561
|
+
const dialogSheet = tv$1({ slots: {
|
|
562
|
+
content: [
|
|
563
|
+
"fixed inset-x-0 top-auto bottom-0 left-0 h-[90dvh] max-h-none w-full max-w-none translate-none",
|
|
564
|
+
"rounded-none rounded-t-dialog",
|
|
565
|
+
"pr-[env(safe-area-inset-right,0px)] pl-[env(safe-area-inset-left,0px)]",
|
|
566
|
+
"transition-transform duration-240 ease-slide data-ending-style:duration-180",
|
|
567
|
+
"data-starting-style:translate-y-full data-ending-style:translate-y-full",
|
|
568
|
+
"motion-reduce:transition-none"
|
|
569
|
+
],
|
|
570
|
+
overlay: [
|
|
571
|
+
"transition-opacity duration-240 ease-slide data-ending-style:duration-180",
|
|
572
|
+
"data-starting-style:opacity-0 data-ending-style:opacity-0",
|
|
573
|
+
"motion-reduce:transition-none"
|
|
574
|
+
],
|
|
575
|
+
header: ["flex h-14 shrink-0 items-center gap-2 px-4 pt-0", "border-b border-line-seam"],
|
|
576
|
+
title: ["min-w-0 flex-1 truncate"],
|
|
577
|
+
commitSlot: ["flex shrink-0 items-center empty:hidden"],
|
|
578
|
+
body: ["min-h-0 content-start", "px-4 pt-4 first:pt-4 pb-[max(16px,env(safe-area-inset-bottom,0px))]"],
|
|
579
|
+
barBody: ["block overflow-y-auto"],
|
|
580
|
+
bodyEnd: ["mt-4 empty:hidden", "text-start font-sans text-footnote text-foreground-muted *:data-[slot=text-link]:text-footnote"]
|
|
581
|
+
} });
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/components/text-link/text-link.tsx
|
|
584
|
+
const textLinkVariants = tv$1({
|
|
585
|
+
base: [
|
|
586
|
+
"rounded-[2px]",
|
|
587
|
+
"decoration-[1.5px] underline-offset-[3px] not-disabled:hover:underline",
|
|
588
|
+
"outline-none focus-visible:shadow-focus",
|
|
589
|
+
"disabled:cursor-not-allowed disabled:text-foreground-disabled"
|
|
590
|
+
],
|
|
591
|
+
variants: { variant: {
|
|
592
|
+
muted: ["font-medium text-foreground-muted", "hover:text-foreground"],
|
|
593
|
+
strong: ["font-semibold text-foreground"]
|
|
594
|
+
} },
|
|
595
|
+
defaultVariants: { variant: "muted" }
|
|
596
|
+
});
|
|
597
|
+
function TextLink({ variant, className, render, ...props }) {
|
|
598
|
+
const defaultProps = {
|
|
599
|
+
className: textLinkVariants({
|
|
600
|
+
variant,
|
|
601
|
+
className
|
|
602
|
+
}),
|
|
603
|
+
...render ? {} : { type: "button" }
|
|
604
|
+
};
|
|
605
|
+
return useRender({
|
|
606
|
+
defaultTagName: "button",
|
|
607
|
+
render,
|
|
608
|
+
props: mergeProps(defaultProps, props),
|
|
609
|
+
state: { slot: "text-link" }
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/components/panel/panel-action.recipe.ts
|
|
614
|
+
const panelActionBase = tv$1({ base: [
|
|
615
|
+
"relative inline-flex w-auto shrink-0 items-center justify-center",
|
|
616
|
+
"overflow-visible font-sans leading-none tabular-nums select-none outline-none",
|
|
617
|
+
"transition-all duration-100",
|
|
618
|
+
"[&>svg]:pointer-events-none [&>svg]:shrink-0",
|
|
619
|
+
"h-control min-w-control rounded-control border-[1.5px] border-transparent bg-transparent py-0",
|
|
620
|
+
"px-2 md:pointer-fine:px-[5px]",
|
|
621
|
+
"after:absolute after:-inset-control-hit",
|
|
622
|
+
"text-foreground",
|
|
623
|
+
"hover:bg-fill-medium active:bg-fill-medium",
|
|
624
|
+
"focus-visible:shadow-focus",
|
|
625
|
+
"[&>svg]:size-control-icon",
|
|
626
|
+
controlIconInset
|
|
627
|
+
] });
|
|
628
|
+
const panelAction = tv$1({
|
|
629
|
+
extend: panelActionBase,
|
|
630
|
+
base: [
|
|
631
|
+
"whitespace-nowrap",
|
|
632
|
+
"disabled:pointer-events-none disabled:transform-none",
|
|
633
|
+
"gap-1.5 text-control font-bold",
|
|
634
|
+
"disabled:bg-transparent disabled:text-foreground-disabled disabled:opacity-100",
|
|
635
|
+
"aria-pressed:border-sticker-ink aria-pressed:bg-brand aria-pressed:text-sticker-ink aria-pressed:ring-[1.5px] aria-pressed:ring-sticker-edge",
|
|
636
|
+
"aria-pressed:active:overlay-sticker-medium"
|
|
637
|
+
]
|
|
638
|
+
});
|
|
639
|
+
const panelToggleAction = tv$1({
|
|
640
|
+
extend: panelActionBase,
|
|
641
|
+
base: [
|
|
642
|
+
"data-disabled:pointer-events-none data-disabled:transform-none",
|
|
643
|
+
"gap-1.5 text-control font-bold",
|
|
644
|
+
"data-disabled:bg-transparent data-disabled:text-foreground-disabled data-disabled:opacity-100",
|
|
645
|
+
"data-pressed:border-sticker-ink data-pressed:bg-brand data-pressed:text-sticker-ink data-pressed:ring-[1.5px] data-pressed:ring-sticker-edge",
|
|
646
|
+
"data-pressed:active:overlay-sticker-medium"
|
|
647
|
+
]
|
|
648
|
+
});
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/components/panel/panel-action.tsx
|
|
651
|
+
function PanelAction({ className, render, nativeButton, ...props }) {
|
|
652
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
653
|
+
...props,
|
|
654
|
+
render,
|
|
655
|
+
nativeButton: nativeButton ?? defaultNativeButton(render),
|
|
656
|
+
type: "button",
|
|
657
|
+
"data-slot": "panel-action",
|
|
658
|
+
"data-icon-inset": "",
|
|
659
|
+
className: panelAction({ className })
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
//#endregion
|
|
415
663
|
//#region src/components/dialog/dialog.tsx
|
|
416
664
|
const dialog = tv$1({
|
|
417
665
|
slots: {
|
|
@@ -432,7 +680,18 @@ const dialog = tv$1({
|
|
|
432
680
|
"px-5 pt-1.5 pb-5 first:pt-5",
|
|
433
681
|
"font-sans text-subhead text-foreground-muted"
|
|
434
682
|
],
|
|
435
|
-
footer: [
|
|
683
|
+
footer: [
|
|
684
|
+
"flex shrink-0 items-center justify-end",
|
|
685
|
+
...stackedActions,
|
|
686
|
+
"gap-2 px-5 pb-5 first:pt-5 [[data-slot=dialog-header]+&]:pt-5",
|
|
687
|
+
"*:data-[slot=text-link]:text-footnote"
|
|
688
|
+
],
|
|
689
|
+
footerNote: ["m-0 md:me-2 md:flex-1", "text-start font-sans text-footnote text-pretty text-foreground-muted md:whitespace-nowrap"],
|
|
690
|
+
footerActions: [
|
|
691
|
+
"ms-auto flex shrink-0 items-center justify-end gap-2",
|
|
692
|
+
...stackedActions,
|
|
693
|
+
"data-stacked:ms-0 data-stacked:w-full"
|
|
694
|
+
],
|
|
436
695
|
title: ["m-0 min-w-0 text-foreground", "font-sans text-headline font-bold"],
|
|
437
696
|
description: ["m-0", "font-sans text-subhead text-foreground-muted"]
|
|
438
697
|
},
|
|
@@ -464,6 +723,10 @@ const dialog = tv$1({
|
|
|
464
723
|
nested: {
|
|
465
724
|
false: {},
|
|
466
725
|
true: { overlay: ["bg-layer-backdrop-nested"] }
|
|
726
|
+
},
|
|
727
|
+
start: {
|
|
728
|
+
false: {},
|
|
729
|
+
true: { footer: ["flex-wrap justify-between"] }
|
|
467
730
|
}
|
|
468
731
|
},
|
|
469
732
|
defaultVariants: {
|
|
@@ -504,6 +767,7 @@ function DialogOverlay({ className, layer, ...props }) {
|
|
|
504
767
|
...props
|
|
505
768
|
});
|
|
506
769
|
}
|
|
770
|
+
const DialogSizeContext = createContext(void 0);
|
|
507
771
|
function DialogContent({ className, children, portalProps, overlay, size, layer, ...props }) {
|
|
508
772
|
const { content } = dialog({
|
|
509
773
|
size,
|
|
@@ -519,11 +783,15 @@ function DialogContent({ className, children, portalProps, overlay, size, layer,
|
|
|
519
783
|
"data-slot": "dialog-content",
|
|
520
784
|
className: content({ className }),
|
|
521
785
|
...props,
|
|
522
|
-
children
|
|
786
|
+
children: /* @__PURE__ */ jsx(DialogSizeContext.Provider, {
|
|
787
|
+
value: size ?? (/(^|\s)max-w-/.test(className ?? "") ? void 0 : "md"),
|
|
788
|
+
children: /* @__PURE__ */ jsx(DialogSheetReset, { children })
|
|
789
|
+
})
|
|
523
790
|
})]
|
|
524
791
|
});
|
|
525
792
|
}
|
|
526
793
|
function DialogClose({ className, variant, size, children, render, "aria-label": ariaLabel, ...props }) {
|
|
794
|
+
const inSheetHead = useInDialogSheetHead();
|
|
527
795
|
if (render) return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
528
796
|
"data-slot": "dialog-close",
|
|
529
797
|
render,
|
|
@@ -531,6 +799,15 @@ function DialogClose({ className, variant, size, children, render, "aria-label":
|
|
|
531
799
|
...props,
|
|
532
800
|
children
|
|
533
801
|
});
|
|
802
|
+
if (inSheetHead) return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
803
|
+
"data-slot": "dialog-close",
|
|
804
|
+
render: /* @__PURE__ */ jsx(PanelAction, {
|
|
805
|
+
"aria-label": ariaLabel ?? "Close",
|
|
806
|
+
className
|
|
807
|
+
}),
|
|
808
|
+
...props,
|
|
809
|
+
children: children ?? /* @__PURE__ */ jsx(X, {})
|
|
810
|
+
});
|
|
534
811
|
return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
535
812
|
"data-slot": "dialog-close",
|
|
536
813
|
render: /* @__PURE__ */ jsx(Button$1, {
|
|
@@ -543,35 +820,149 @@ function DialogClose({ className, variant, size, children, render, "aria-label":
|
|
|
543
820
|
children: children ?? /* @__PURE__ */ jsx(X, { className: "size-4.5" })
|
|
544
821
|
});
|
|
545
822
|
}
|
|
546
|
-
|
|
823
|
+
/**
|
|
824
|
+
* Back, in a later step of a flow drawn as a phone sheet: it takes the head's
|
|
825
|
+
* start in place of × (stdrc, 2026-10-22). The sheet is a stack of steps, and
|
|
826
|
+
* Back undoes one, as on a pushed page; Esc still closes. Put it in
|
|
827
|
+
* `DialogHeader` beside the `DialogClose`, from the second step on; it draws
|
|
828
|
+
* nothing outside a sheet, where Back stays in the footer. Its icon is
|
|
829
|
+
* `arrow-left`, as every Back (Alignment, "The ends of a header").
|
|
830
|
+
*/
|
|
831
|
+
function DialogSheetBack({ className, children, "aria-label": ariaLabel, ...props }) {
|
|
832
|
+
if (!useInDialogSheetHead()) return null;
|
|
833
|
+
return /* @__PURE__ */ jsx(PanelAction, {
|
|
834
|
+
"data-slot": "dialog-sheet-back",
|
|
835
|
+
"aria-label": ariaLabel ?? "Back",
|
|
836
|
+
className,
|
|
837
|
+
...props,
|
|
838
|
+
children: children ?? /* @__PURE__ */ jsx(ArrowLeft, {})
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
function DialogHeader({ className, bar = false, children, ...props }) {
|
|
842
|
+
const { sheet, setCommitSlot } = useDialogSheet();
|
|
843
|
+
const items = sheet ? Children.toArray(children) : [];
|
|
844
|
+
const backs = items.filter((item) => isValidElement(item) && item.type === DialogSheetBack);
|
|
845
|
+
const closes = items.filter((item) => isValidElement(item) && item.type === DialogClose);
|
|
846
|
+
const missingLeading = sheet && backs.length === 0 && closes.length === 0;
|
|
847
|
+
useEffect(() => {
|
|
848
|
+
if (process.env.NODE_ENV === "production" || !missingLeading) return;
|
|
849
|
+
console.warn("raft-ui: a DialogHeader drawn as a phone sheet has no DialogClose (or DialogSheetBack) as a direct child, so the sheet has no × at the start of its head. Put DialogClose directly in DialogHeader, not inside a Tooltip, a Fragment or a component of your own.");
|
|
850
|
+
}, [missingLeading]);
|
|
851
|
+
if (sheet) {
|
|
852
|
+
const rest = items.filter((item) => !backs.includes(item) && !closes.includes(item));
|
|
853
|
+
const head = dialogSheet();
|
|
854
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
855
|
+
"data-slot": "dialog-header",
|
|
856
|
+
"data-sheet": "",
|
|
857
|
+
className: dialog({ bar: false }).header({ className: cn(head.header(), className) }),
|
|
858
|
+
...props,
|
|
859
|
+
children: [/* @__PURE__ */ jsxs(DialogSheetHeadContext.Provider, {
|
|
860
|
+
value: true,
|
|
861
|
+
children: [backs.length > 0 ? backs : closes, rest]
|
|
862
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
863
|
+
ref: setCommitSlot,
|
|
864
|
+
"data-slot": "dialog-commit-slot",
|
|
865
|
+
className: head.commitSlot()
|
|
866
|
+
})]
|
|
867
|
+
});
|
|
868
|
+
}
|
|
547
869
|
const { header } = dialog({ bar });
|
|
548
870
|
return /* @__PURE__ */ jsx("div", {
|
|
549
871
|
"data-slot": "dialog-header",
|
|
550
872
|
className: header({ className }),
|
|
551
|
-
...props
|
|
873
|
+
...props,
|
|
874
|
+
children
|
|
552
875
|
});
|
|
553
876
|
}
|
|
554
|
-
function DialogBody({ className, bar = false, ...props }) {
|
|
877
|
+
function DialogBody({ className, bar = false, children, ...props }) {
|
|
878
|
+
const { sheet, setBodyEndSlot } = useDialogSheet();
|
|
555
879
|
const { body } = dialog({ bar });
|
|
556
|
-
|
|
880
|
+
const sheetBody = sheet ? cn(bar && dialogSheet().barBody(), dialogSheet().body()) : void 0;
|
|
881
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
557
882
|
"data-slot": "dialog-body",
|
|
558
|
-
className: body({ className }),
|
|
559
|
-
...props
|
|
883
|
+
className: body({ className: sheetBody ? cn(sheetBody, className) : className }),
|
|
884
|
+
...props,
|
|
885
|
+
children: [children, sheet ? /* @__PURE__ */ jsx("div", {
|
|
886
|
+
ref: setBodyEndSlot,
|
|
887
|
+
"data-slot": "dialog-sheet-body-end",
|
|
888
|
+
className: dialogSheet().bodyEnd()
|
|
889
|
+
}) : null]
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
function DialogFooter({ className, bar = false, hidden, children, ref, ...props }) {
|
|
893
|
+
const { sheet, bodyEndSlot } = useDialogSheet();
|
|
894
|
+
const items = Children.toArray(children);
|
|
895
|
+
const start = items.find((item) => isValidElement(item) && item.type === DialogFooterNote && hasNoteContent(item.props.children)) ?? (isValidElement(items[0]) && items[0].type === TextLink ? items[0] : void 0);
|
|
896
|
+
const [stacked, rowRef] = useStackedActions(start == null ? ref : void 0, start != null);
|
|
897
|
+
const { footer, footerActions } = dialog({
|
|
898
|
+
bar,
|
|
899
|
+
start: start != null
|
|
900
|
+
});
|
|
901
|
+
const stackedProps = stacked ? { "data-stacked": "" } : {};
|
|
902
|
+
if (start == null) return /* @__PURE__ */ jsx("div", {
|
|
903
|
+
"data-slot": "dialog-footer",
|
|
904
|
+
ref: rowRef,
|
|
905
|
+
className: footer({ className }),
|
|
906
|
+
hidden: sheet || hidden,
|
|
907
|
+
...stackedProps,
|
|
908
|
+
...props,
|
|
909
|
+
children
|
|
560
910
|
});
|
|
561
|
-
|
|
562
|
-
function DialogFooter({ className, bar = false, ...props }) {
|
|
563
|
-
const { footer } = dialog({ bar });
|
|
564
|
-
return /* @__PURE__ */ jsx("div", {
|
|
911
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [sheet && bodyEndSlot ? createPortal(start, bodyEndSlot) : null, /* @__PURE__ */ jsxs("div", {
|
|
565
912
|
"data-slot": "dialog-footer",
|
|
913
|
+
ref,
|
|
566
914
|
className: footer({ className }),
|
|
567
|
-
|
|
915
|
+
hidden: sheet || hidden,
|
|
916
|
+
...props,
|
|
917
|
+
children: [sheet ? null : start, /* @__PURE__ */ jsx("div", {
|
|
918
|
+
"data-slot": "dialog-footer-actions",
|
|
919
|
+
ref: rowRef,
|
|
920
|
+
className: footerActions(),
|
|
921
|
+
...stackedProps,
|
|
922
|
+
children: items.filter((item) => item !== start)
|
|
923
|
+
})]
|
|
924
|
+
})] });
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Whether a note has anything to show: not only null, false or "", but also
|
|
928
|
+
* whitespace and an empty array (`{items.map(...)}` of nothing). Numbers,
|
|
929
|
+
* 0 included, and elements count.
|
|
930
|
+
*/
|
|
931
|
+
function hasNoteContent(children) {
|
|
932
|
+
return Children.toArray(children).some((child) => typeof child !== "string" || child.trim() !== "");
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* One short line at the start of `DialogFooter` that says what the commit
|
|
936
|
+
* button will do ("You'll be billed $8 per member each month"). Muted footnote
|
|
937
|
+
* text, start-aligned and never cut. Only in a `lg` or `wide` dialog, and at
|
|
938
|
+
* most 40 characters: on a desktop it is one line beside the buttons and never
|
|
939
|
+
* wraps; on a phone it goes above the buttons when they don't fit beside it,
|
|
940
|
+
* and may wrap to 2 lines. In a phone sheet, which has no footer, it moves
|
|
941
|
+
* to the end of the body. Longer copy belongs in the body. A footer holds a note or a `TextLink`,
|
|
942
|
+
* never both.
|
|
943
|
+
*/
|
|
944
|
+
function DialogFooterNote({ className, children, ...props }) {
|
|
945
|
+
const { footerNote } = dialog();
|
|
946
|
+
const size = useContext(DialogSizeContext);
|
|
947
|
+
useEffect(() => {
|
|
948
|
+
if (process.env.NODE_ENV === "production") return;
|
|
949
|
+
if (size === "sm" || size === "md") console.warn(`raft-ui: a DialogFooterNote is in a size="${size}" dialog. A footer note fits beside the buttons only in a size="lg" or size="wide" dialog; put the sentence in DialogBody instead.`);
|
|
950
|
+
}, [size]);
|
|
951
|
+
if (!hasNoteContent(children)) return null;
|
|
952
|
+
return /* @__PURE__ */ jsx("p", {
|
|
953
|
+
"data-slot": "dialog-footer-note",
|
|
954
|
+
className: footerNote({ className }),
|
|
955
|
+
...props,
|
|
956
|
+
children
|
|
568
957
|
});
|
|
569
958
|
}
|
|
570
959
|
function DialogTitle({ className, ...props }) {
|
|
960
|
+
const inSheetHead = useInDialogSheetHead();
|
|
571
961
|
const { title } = dialog();
|
|
962
|
+
const classes = inSheetHead ? cn(dialogSheet().title(), className) : className;
|
|
572
963
|
return /* @__PURE__ */ jsx(Dialog.Title, {
|
|
573
964
|
"data-slot": "dialog-title",
|
|
574
|
-
className: title({ className }),
|
|
965
|
+
className: title({ className: classes }),
|
|
575
966
|
...props
|
|
576
967
|
});
|
|
577
968
|
}
|
|
@@ -595,7 +986,7 @@ const dialogSurface = tv$1({ slots: { root: [
|
|
|
595
986
|
* animates itself: the host centres it, and a centred modal has no motion.
|
|
596
987
|
* DialogContent draws the same frame for a Base UI dialog.
|
|
597
988
|
*/
|
|
598
|
-
function DialogSurface({ className, render, ...props }) {
|
|
989
|
+
function DialogSurface({ className, render, children, ...props }) {
|
|
599
990
|
const { root } = dialogSurface();
|
|
600
991
|
return useRender({
|
|
601
992
|
defaultTagName: "div",
|
|
@@ -603,7 +994,7 @@ function DialogSurface({ className, render, ...props }) {
|
|
|
603
994
|
props: mergeProps({
|
|
604
995
|
"data-slot": "dialog-surface",
|
|
605
996
|
className: root({ className })
|
|
606
|
-
}, props)
|
|
997
|
+
}, props, { children: /* @__PURE__ */ jsx(DialogSheetReset, { children }) })
|
|
607
998
|
});
|
|
608
999
|
}
|
|
609
1000
|
const dialogNav = tv$1({ slots: { root: ["flex shrink-0 flex-col overflow-y-auto", "w-52 gap-0.5 border-r border-line-seam p-2 max-md:hidden"] } });
|
|
@@ -620,6 +1011,206 @@ function DialogNav({ className, render, ...props }) {
|
|
|
620
1011
|
});
|
|
621
1012
|
}
|
|
622
1013
|
//#endregion
|
|
1014
|
+
//#region src/lib/use-mobile-viewport.ts
|
|
1015
|
+
const MOBILE_QUERY = "(max-width: 767px)";
|
|
1016
|
+
function subscribeMobileViewport(onStoreChange) {
|
|
1017
|
+
if (typeof window === "undefined") return () => void 0;
|
|
1018
|
+
const media = window.matchMedia(MOBILE_QUERY);
|
|
1019
|
+
media.addEventListener("change", onStoreChange);
|
|
1020
|
+
return () => media.removeEventListener("change", onStoreChange);
|
|
1021
|
+
}
|
|
1022
|
+
function getMobileViewportSnapshot() {
|
|
1023
|
+
return typeof window !== "undefined" && window.matchMedia(MOBILE_QUERY).matches;
|
|
1024
|
+
}
|
|
1025
|
+
function useMobileViewport() {
|
|
1026
|
+
return useSyncExternalStore(subscribeMobileViewport, getMobileViewportSnapshot, () => false);
|
|
1027
|
+
}
|
|
1028
|
+
//#endregion
|
|
1029
|
+
//#region src/components/dialog/dialog-sheet.tsx
|
|
1030
|
+
/**
|
|
1031
|
+
* DialogContent for a dialog with several fields, a long list or steps. On a
|
|
1032
|
+
* desktop and a tablet it is DialogContent, the same DOM and classes. Below
|
|
1033
|
+
* 768px it is a sheet: docked to the bottom, 90% of the screen tall, sliding
|
|
1034
|
+
* up over its scrim; `DialogHeader` becomes a pushed page's head (× at the
|
|
1035
|
+
* start, the title, the `DialogCommit` button at the end), `DialogFooter` is
|
|
1036
|
+
* hidden (× is Cancel) and `DialogBody` scrolls on the phone's 16px margin
|
|
1037
|
+
* above the home indicator. Base UI keeps Esc, focus and close.
|
|
1038
|
+
*/
|
|
1039
|
+
function DialogSheetContent({ className, overlay, style, ref, children, ...props }) {
|
|
1040
|
+
const phone = useMobileViewport();
|
|
1041
|
+
const [commitSlot, setCommitSlot] = useState(null);
|
|
1042
|
+
const [bodyEndSlot, setBodyEndSlot] = useState(null);
|
|
1043
|
+
const [popup, setPopup] = useState(null);
|
|
1044
|
+
const keyboardBox = useSheetKeyboardBox(phone);
|
|
1045
|
+
useRevealNewProblems(phone ? popup : null);
|
|
1046
|
+
const state = useMemo(() => ({
|
|
1047
|
+
sheet: phone,
|
|
1048
|
+
commitSlot: phone ? commitSlot : null,
|
|
1049
|
+
setCommitSlot,
|
|
1050
|
+
bodyEndSlot: phone ? bodyEndSlot : null,
|
|
1051
|
+
setBodyEndSlot
|
|
1052
|
+
}), [
|
|
1053
|
+
phone,
|
|
1054
|
+
commitSlot,
|
|
1055
|
+
bodyEndSlot
|
|
1056
|
+
]);
|
|
1057
|
+
const setRef = useCallback((element) => {
|
|
1058
|
+
setPopup(element);
|
|
1059
|
+
assignRef$1(ref, element);
|
|
1060
|
+
}, [ref]);
|
|
1061
|
+
const sheet = dialogSheet();
|
|
1062
|
+
const sheetOverlay = useMemo(() => overlay === false ? false : {
|
|
1063
|
+
...typeof overlay === "object" ? overlay : null,
|
|
1064
|
+
className: cn(dialogSheet().overlay(), typeof overlay === "object" ? overlay.className : ""),
|
|
1065
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1066
|
+
"aria-hidden": true,
|
|
1067
|
+
className: "absolute inset-0"
|
|
1068
|
+
})
|
|
1069
|
+
}, [overlay]);
|
|
1070
|
+
const sheetStyle = useMemo(() => keyboardBox ? {
|
|
1071
|
+
...style,
|
|
1072
|
+
...keyboardBox
|
|
1073
|
+
} : style, [style, keyboardBox]);
|
|
1074
|
+
return /* @__PURE__ */ jsx(DialogContent, {
|
|
1075
|
+
...props,
|
|
1076
|
+
ref: setRef,
|
|
1077
|
+
overlay: phone ? sheetOverlay : overlay,
|
|
1078
|
+
className: phone ? cn(className, sheet.content()) : className,
|
|
1079
|
+
style: phone ? sheetStyle : style,
|
|
1080
|
+
"data-sheet": phone ? "" : void 0,
|
|
1081
|
+
children: /* @__PURE__ */ jsx(DialogSheetContext.Provider, {
|
|
1082
|
+
value: state,
|
|
1083
|
+
children
|
|
1084
|
+
})
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
function assignRef$1(ref, value) {
|
|
1088
|
+
if (typeof ref === "function") ref(value);
|
|
1089
|
+
else if (ref) ref.current = value;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* The dialog's one commit button: a `Button`, written where it goes on a
|
|
1093
|
+
* desktop (the end of `DialogFooter`). In a `DialogSheetContent` below 768px
|
|
1094
|
+
* it is drawn at the end of the sheet's head instead, the same button (color,
|
|
1095
|
+
* words, disabled and loading state) at `size="md"`. In a flow of steps, write
|
|
1096
|
+
* each step's button as one (Next, then the last step's action).
|
|
1097
|
+
*
|
|
1098
|
+
* A submit button stays its form's default button in the head: it is tied to
|
|
1099
|
+
* the form it was written in by the form's `id` (the `form` attribute), so a
|
|
1100
|
+
* click and Enter in a field submit, and neither does while it is disabled.
|
|
1101
|
+
* Give that form an `id`, or pass `form`. Without one a click still submits
|
|
1102
|
+
* the form, but Enter in a field doesn't.
|
|
1103
|
+
*/
|
|
1104
|
+
function DialogCommit({ size, form, onClick, ...props }) {
|
|
1105
|
+
const { commitSlot } = useDialogSheet();
|
|
1106
|
+
const anchorRef = useRef(null);
|
|
1107
|
+
const [writtenIn, setWrittenIn] = useState(null);
|
|
1108
|
+
useLayoutEffect(() => {
|
|
1109
|
+
if (!commitSlot || form) return;
|
|
1110
|
+
const found = anchorRef.current?.closest("form") ?? null;
|
|
1111
|
+
setWrittenIn(found);
|
|
1112
|
+
if (process.env.NODE_ENV === "production" || props.type !== "submit" || found?.id) return;
|
|
1113
|
+
console.warn(found ? "raft-ui: a submit DialogCommit is drawn in a phone sheet's head, outside its form, and the form has no id, so Enter in a field doesn't submit it (a click still does). Give the form an id, or pass `form` to DialogCommit." : "raft-ui: a submit DialogCommit in a phone sheet isn't inside a form and has no `form`, so it submits nothing. Pass `form` with the form's id.");
|
|
1114
|
+
}, [
|
|
1115
|
+
commitSlot,
|
|
1116
|
+
form,
|
|
1117
|
+
props.type
|
|
1118
|
+
]);
|
|
1119
|
+
const handleClick = useCallback((event) => {
|
|
1120
|
+
onClick?.(event);
|
|
1121
|
+
if (!event.defaultPrevented && props.type === "submit" && writtenIn && !writtenIn.id) {
|
|
1122
|
+
event.preventDefault();
|
|
1123
|
+
writtenIn.requestSubmit();
|
|
1124
|
+
}
|
|
1125
|
+
}, [
|
|
1126
|
+
onClick,
|
|
1127
|
+
props.type,
|
|
1128
|
+
writtenIn
|
|
1129
|
+
]);
|
|
1130
|
+
if (!commitSlot) return /* @__PURE__ */ jsx(Button$1, {
|
|
1131
|
+
size,
|
|
1132
|
+
form,
|
|
1133
|
+
onClick,
|
|
1134
|
+
...props
|
|
1135
|
+
});
|
|
1136
|
+
const linkedForm = form ?? (writtenIn?.id || void 0);
|
|
1137
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1138
|
+
ref: anchorRef,
|
|
1139
|
+
hidden: true
|
|
1140
|
+
}), createPortal(/* @__PURE__ */ jsx(Button$1, {
|
|
1141
|
+
size: "md",
|
|
1142
|
+
form: linkedForm,
|
|
1143
|
+
onClick: handleClick,
|
|
1144
|
+
...props
|
|
1145
|
+
}), commitSlot)] });
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* The sheet's box while the on-screen keyboard is up: docked to the top of the
|
|
1149
|
+
* keyboard, as tall as the visual viewport allows up to its usual 90% of the
|
|
1150
|
+
* window. With the keyboard up the visual viewport is shorter than that, so
|
|
1151
|
+
* the sheet fills it: the head stays in view and the body scrolls above the
|
|
1152
|
+
* keyboard. `undefined` otherwise (the CSS box).
|
|
1153
|
+
*/
|
|
1154
|
+
function useSheetKeyboardBox(active) {
|
|
1155
|
+
const [box, setBox] = useState(void 0);
|
|
1156
|
+
useEffect(() => {
|
|
1157
|
+
const viewport = typeof window === "undefined" ? void 0 : window.visualViewport;
|
|
1158
|
+
if (!active || !viewport) return;
|
|
1159
|
+
const update = () => {
|
|
1160
|
+
const focused = document.activeElement;
|
|
1161
|
+
const keyboardUp = (focused instanceof HTMLInputElement || focused instanceof HTMLTextAreaElement || focused instanceof HTMLElement && focused.isContentEditable) && window.innerHeight - viewport.height > 100;
|
|
1162
|
+
const height = Math.round(Math.min(viewport.height, window.innerHeight * .9));
|
|
1163
|
+
const top = Math.round(Math.max(viewport.offsetTop, 0) + viewport.height - height);
|
|
1164
|
+
setBox((previous) => {
|
|
1165
|
+
if (!keyboardUp) return void 0;
|
|
1166
|
+
return previous?.top === `${top}px` && previous?.height === `${height}px` ? previous : {
|
|
1167
|
+
top: `${top}px`,
|
|
1168
|
+
bottom: "auto",
|
|
1169
|
+
height: `${height}px`
|
|
1170
|
+
};
|
|
1171
|
+
});
|
|
1172
|
+
};
|
|
1173
|
+
viewport.addEventListener("resize", update);
|
|
1174
|
+
viewport.addEventListener("scroll", update, { passive: true });
|
|
1175
|
+
document.addEventListener("focusin", update);
|
|
1176
|
+
document.addEventListener("focusout", update);
|
|
1177
|
+
return () => {
|
|
1178
|
+
viewport.removeEventListener("resize", update);
|
|
1179
|
+
viewport.removeEventListener("scroll", update);
|
|
1180
|
+
document.removeEventListener("focusin", update);
|
|
1181
|
+
document.removeEventListener("focusout", update);
|
|
1182
|
+
};
|
|
1183
|
+
}, [active]);
|
|
1184
|
+
return active ? box : void 0;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* The commit is at the top of a sheet, so an error a failed commit adds near
|
|
1188
|
+
* the bottom of a long form would appear out of sight. A problem added after
|
|
1189
|
+
* the sheet opens (an alert, or a warning or destructive Banner) is scrolled
|
|
1190
|
+
* into view.
|
|
1191
|
+
*/
|
|
1192
|
+
const PROBLEM_SELECTOR = "[role=alert], [data-slot=banner][data-status=warning], [data-slot=banner][data-status=destructive]";
|
|
1193
|
+
function useRevealNewProblems(popup) {
|
|
1194
|
+
useEffect(() => {
|
|
1195
|
+
if (!popup || typeof MutationObserver === "undefined") return;
|
|
1196
|
+
const observer = new MutationObserver((records) => {
|
|
1197
|
+
for (const record of records) for (const node of record.addedNodes) {
|
|
1198
|
+
if (!(node instanceof Element)) continue;
|
|
1199
|
+
const problem = node.matches(PROBLEM_SELECTOR) ? node : node.querySelector(PROBLEM_SELECTOR);
|
|
1200
|
+
if (problem) {
|
|
1201
|
+
problem.scrollIntoView?.({ block: "nearest" });
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
observer.observe(popup, {
|
|
1207
|
+
childList: true,
|
|
1208
|
+
subtree: true
|
|
1209
|
+
});
|
|
1210
|
+
return () => observer.disconnect();
|
|
1211
|
+
}, [popup]);
|
|
1212
|
+
}
|
|
1213
|
+
//#endregion
|
|
623
1214
|
//#region src/components/badge/badge.tsx
|
|
624
1215
|
const badge = tv$1({
|
|
625
1216
|
slots: { root: [
|
|
@@ -801,36 +1392,6 @@ function Experimental(props) {
|
|
|
801
1392
|
}
|
|
802
1393
|
const Badge = Object.assign(BadgeRoot, { Experimental });
|
|
803
1394
|
//#endregion
|
|
804
|
-
//#region src/components/text-link/text-link.tsx
|
|
805
|
-
const textLinkVariants = tv$1({
|
|
806
|
-
base: [
|
|
807
|
-
"rounded-[2px]",
|
|
808
|
-
"decoration-[1.5px] underline-offset-[3px] not-disabled:hover:underline",
|
|
809
|
-
"outline-none focus-visible:shadow-focus",
|
|
810
|
-
"disabled:cursor-not-allowed disabled:text-foreground-disabled"
|
|
811
|
-
],
|
|
812
|
-
variants: { variant: {
|
|
813
|
-
muted: ["font-medium text-foreground-muted", "hover:text-foreground"],
|
|
814
|
-
strong: ["font-semibold text-foreground"]
|
|
815
|
-
} },
|
|
816
|
-
defaultVariants: { variant: "muted" }
|
|
817
|
-
});
|
|
818
|
-
function TextLink({ variant, className, render, ...props }) {
|
|
819
|
-
const defaultProps = {
|
|
820
|
-
className: textLinkVariants({
|
|
821
|
-
variant,
|
|
822
|
-
className
|
|
823
|
-
}),
|
|
824
|
-
...render ? {} : { type: "button" }
|
|
825
|
-
};
|
|
826
|
-
return useRender({
|
|
827
|
-
defaultTagName: "button",
|
|
828
|
-
render,
|
|
829
|
-
props: mergeProps(defaultProps, props),
|
|
830
|
-
state: { slot: "text-link" }
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
//#endregion
|
|
834
1395
|
//#region src/components/tooltip/tooltip.tsx
|
|
835
1396
|
const tooltip = tv$1({
|
|
836
1397
|
slots: {
|
|
@@ -1112,21 +1673,6 @@ function Checkbox$1({ shape = "square", className, ...props }) {
|
|
|
1112
1673
|
});
|
|
1113
1674
|
}
|
|
1114
1675
|
//#endregion
|
|
1115
|
-
//#region src/lib/use-mobile-viewport.ts
|
|
1116
|
-
const MOBILE_QUERY = "(max-width: 767px)";
|
|
1117
|
-
function subscribeMobileViewport(onStoreChange) {
|
|
1118
|
-
if (typeof window === "undefined") return () => void 0;
|
|
1119
|
-
const media = window.matchMedia(MOBILE_QUERY);
|
|
1120
|
-
media.addEventListener("change", onStoreChange);
|
|
1121
|
-
return () => media.removeEventListener("change", onStoreChange);
|
|
1122
|
-
}
|
|
1123
|
-
function getMobileViewportSnapshot() {
|
|
1124
|
-
return typeof window !== "undefined" && window.matchMedia(MOBILE_QUERY).matches;
|
|
1125
|
-
}
|
|
1126
|
-
function useMobileViewport() {
|
|
1127
|
-
return useSyncExternalStore(subscribeMobileViewport, getMobileViewportSnapshot, () => false);
|
|
1128
|
-
}
|
|
1129
|
-
//#endregion
|
|
1130
1676
|
//#region src/components/scroll-area/scroll-area.recipe.ts
|
|
1131
1677
|
const scrollArea = tv$1({ slots: {
|
|
1132
1678
|
root: ["relative min-h-0"],
|
|
@@ -1534,94 +2080,902 @@ function DropdownMenuSeparator({ className, ...props }) {
|
|
|
1534
2080
|
...props
|
|
1535
2081
|
});
|
|
1536
2082
|
}
|
|
1537
|
-
function DropdownMenuSubmenuChevronIcon() {
|
|
1538
|
-
return /* @__PURE__ */ jsx("svg", {
|
|
1539
|
-
width: "12",
|
|
1540
|
-
height: "12",
|
|
1541
|
-
viewBox: "0 0 12 12",
|
|
1542
|
-
"aria-hidden": true,
|
|
1543
|
-
className: "size-3 shrink-0",
|
|
1544
|
-
children: /* @__PURE__ */ jsx("g", {
|
|
1545
|
-
fill: "none",
|
|
1546
|
-
strokeLinecap: "round",
|
|
1547
|
-
strokeLinejoin: "round",
|
|
1548
|
-
strokeWidth: "1",
|
|
1549
|
-
stroke: "currentColor",
|
|
1550
|
-
children: /* @__PURE__ */ jsx("path", { d: "M4.25 2.5L7.75 6l-3.5 3.5" })
|
|
1551
|
-
})
|
|
2083
|
+
function DropdownMenuSubmenuChevronIcon() {
|
|
2084
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
2085
|
+
width: "12",
|
|
2086
|
+
height: "12",
|
|
2087
|
+
viewBox: "0 0 12 12",
|
|
2088
|
+
"aria-hidden": true,
|
|
2089
|
+
className: "size-3 shrink-0",
|
|
2090
|
+
children: /* @__PURE__ */ jsx("g", {
|
|
2091
|
+
fill: "none",
|
|
2092
|
+
strokeLinecap: "round",
|
|
2093
|
+
strokeLinejoin: "round",
|
|
2094
|
+
strokeWidth: "1",
|
|
2095
|
+
stroke: "currentColor",
|
|
2096
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4.25 2.5L7.75 6l-3.5 3.5" })
|
|
2097
|
+
})
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
function DropdownMenuCheckIcon() {
|
|
2101
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
2102
|
+
width: "12",
|
|
2103
|
+
height: "12",
|
|
2104
|
+
viewBox: "0 0 12 12",
|
|
2105
|
+
"aria-hidden": true,
|
|
2106
|
+
className: "size-3 shrink-0",
|
|
2107
|
+
children: /* @__PURE__ */ jsx("g", {
|
|
2108
|
+
fill: "none",
|
|
2109
|
+
strokeLinecap: "round",
|
|
2110
|
+
strokeLinejoin: "round",
|
|
2111
|
+
strokeWidth: "1",
|
|
2112
|
+
stroke: "currentColor",
|
|
2113
|
+
children: /* @__PURE__ */ jsx("path", { d: "M1.75 6.41L4.4 9.25L10.25 2.99" })
|
|
2114
|
+
})
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
function DropdownMenuRadioGroup({ ...props }) {
|
|
2118
|
+
return /* @__PURE__ */ jsx(Menu.RadioGroup, {
|
|
2119
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
2120
|
+
...props
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function DropdownMenuRadioItem({ className, children, ...props }) {
|
|
2124
|
+
const { radioItem, radioLabel, indicator } = dropdownMenu();
|
|
2125
|
+
return /* @__PURE__ */ jsxs(Menu.RadioItem, {
|
|
2126
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
2127
|
+
className: radioItem({ className }),
|
|
2128
|
+
...props,
|
|
2129
|
+
children: [renderRadioItemChildren(children, radioLabel()), /* @__PURE__ */ jsx("span", {
|
|
2130
|
+
className: indicator(),
|
|
2131
|
+
children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(DropdownMenuCheckIcon, {}) })
|
|
2132
|
+
})]
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
function renderRadioItemChildren(children, className) {
|
|
2136
|
+
if (typeof children === "string" || typeof children === "number") return /* @__PURE__ */ jsx("span", {
|
|
2137
|
+
"data-slot": "dropdown-menu-radio-item-label",
|
|
2138
|
+
className,
|
|
2139
|
+
children
|
|
2140
|
+
});
|
|
2141
|
+
if (Array.isArray(children) && children.every((child) => typeof child === "string")) return /* @__PURE__ */ jsx("span", {
|
|
2142
|
+
"data-slot": "dropdown-menu-radio-item-label",
|
|
2143
|
+
className,
|
|
2144
|
+
children
|
|
2145
|
+
});
|
|
2146
|
+
return children;
|
|
2147
|
+
}
|
|
2148
|
+
function DropdownMenuRadioItemIndicator({ className, ...props }) {
|
|
2149
|
+
return /* @__PURE__ */ jsx(Menu.RadioItemIndicator, {
|
|
2150
|
+
"data-slot": "dropdown-menu-radio-item-indicator",
|
|
2151
|
+
className,
|
|
2152
|
+
...props
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
function DropdownMenuCheckboxItem({ className, children, ...props }) {
|
|
2156
|
+
const { item, indicator } = dropdownMenu();
|
|
2157
|
+
return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
|
|
2158
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
2159
|
+
className: item({ className }),
|
|
2160
|
+
...props,
|
|
2161
|
+
children: [children, /* @__PURE__ */ jsx("span", {
|
|
2162
|
+
className: indicator(),
|
|
2163
|
+
children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(DropdownMenuCheckIcon, {}) })
|
|
2164
|
+
})]
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
function DropdownMenuCheckboxItemIndicator({ className, ...props }) {
|
|
2168
|
+
return /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, {
|
|
2169
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
2170
|
+
className,
|
|
2171
|
+
...props
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* One choice that is another page, such as the same page in another
|
|
2176
|
+
* language: a link, so it opens in a new tab and can be copied. Its
|
|
2177
|
+
* `href` goes on the item; `current` marks the page being shown.
|
|
2178
|
+
*/
|
|
2179
|
+
function DropdownMenuLinkItem({ current = false, className, children, ...props }) {
|
|
2180
|
+
const { radioItem, radioLabel, indicator } = dropdownMenu();
|
|
2181
|
+
return /* @__PURE__ */ jsxs(Menu.LinkItem, {
|
|
2182
|
+
"data-slot": "dropdown-menu-link-item",
|
|
2183
|
+
"aria-current": current ? "page" : void 0,
|
|
2184
|
+
"data-checked": current ? "" : void 0,
|
|
2185
|
+
className: radioItem({ className }),
|
|
2186
|
+
...props,
|
|
2187
|
+
children: [renderRadioItemChildren(children, radioLabel()), current ? /* @__PURE__ */ jsx("span", {
|
|
2188
|
+
className: indicator(),
|
|
2189
|
+
children: /* @__PURE__ */ jsx(DropdownMenuCheckIcon, {})
|
|
2190
|
+
}) : null]
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
//#endregion
|
|
2194
|
+
//#region src/components/mobile-nav/mobile-nav.tsx
|
|
2195
|
+
const mobileNav = tv$1({ slots: {
|
|
2196
|
+
root: [
|
|
2197
|
+
"grid w-full flex-none grid-flow-col auto-cols-fr items-start gap-0",
|
|
2198
|
+
"border-t border-line-seam bg-layer-card px-2 pt-1.5 text-foreground-hint",
|
|
2199
|
+
"pb-[max(8px,min(env(safe-area-inset-bottom,0px),34px))]"
|
|
2200
|
+
],
|
|
2201
|
+
item: [
|
|
2202
|
+
"relative inline-flex shrink-0 items-center outline-none",
|
|
2203
|
+
"disabled:pointer-events-none disabled:opacity-45",
|
|
2204
|
+
"[&_svg]:shrink-0",
|
|
2205
|
+
"isolate min-w-0 flex-col justify-start gap-[7px] border-0 px-0 pt-1 pb-0",
|
|
2206
|
+
"font-sans text-[10px]/3 font-semibold text-foreground-hint",
|
|
2207
|
+
"*:relative *:z-10 [&_svg]:size-[22px] [&_svg]:stroke-2",
|
|
2208
|
+
"before:pointer-events-none before:absolute before:top-0 before:left-1/2 before:z-0 before:-translate-x-1/2",
|
|
2209
|
+
"before:h-[30px] before:w-14 before:rounded-control before:border-[1.5px] before:border-transparent before:content-['']",
|
|
2210
|
+
"active:before:bg-fill-medium",
|
|
2211
|
+
"data-[selected=true]:font-bold data-[selected=true]:text-foreground",
|
|
2212
|
+
"data-[selected=true]:[&_svg]:text-sticker-ink",
|
|
2213
|
+
"data-[selected=true]:before:border-sticker-ink data-[selected=true]:before:bg-brand data-[selected=true]:before:ring-[1.5px] data-[selected=true]:before:ring-sticker-edge",
|
|
2214
|
+
"data-[selected=true]:active:before:overlay-sticker-medium",
|
|
2215
|
+
"rounded-control focus-visible:shadow-focus"
|
|
2216
|
+
],
|
|
2217
|
+
itemLabel: ["min-w-0 truncate", "max-w-full"]
|
|
2218
|
+
} });
|
|
2219
|
+
function MobileNavRoot({ className, render, ...props }) {
|
|
2220
|
+
const { root } = mobileNav();
|
|
2221
|
+
const defaultProps = {
|
|
2222
|
+
"data-slot": "mobile-nav-root",
|
|
2223
|
+
className: root({ className })
|
|
2224
|
+
};
|
|
2225
|
+
return useRender({
|
|
2226
|
+
defaultTagName: "nav",
|
|
2227
|
+
render,
|
|
2228
|
+
props: mergeProps(defaultProps, props)
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
function MobileNavItem({ nativeButton = true, selected, className, render, ...props }) {
|
|
2232
|
+
const { item } = mobileNav();
|
|
2233
|
+
const defaultProps = {
|
|
2234
|
+
type: nativeButton ? "button" : void 0,
|
|
2235
|
+
"aria-pressed": nativeButton ? selected : void 0,
|
|
2236
|
+
"data-slot": "mobile-nav-item",
|
|
2237
|
+
"data-selected": selected ? "true" : void 0,
|
|
2238
|
+
className: item({ className })
|
|
2239
|
+
};
|
|
2240
|
+
return useRender({
|
|
2241
|
+
render: render ?? /* @__PURE__ */ jsx(Button, {}),
|
|
2242
|
+
props: mergeProps(defaultProps, props)
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
function MobileNavLabel({ className, render, ...props }) {
|
|
2246
|
+
const { itemLabel } = mobileNav();
|
|
2247
|
+
const defaultProps = {
|
|
2248
|
+
"data-slot": "mobile-nav-item-label",
|
|
2249
|
+
className: itemLabel({ className })
|
|
2250
|
+
};
|
|
2251
|
+
return useRender({
|
|
2252
|
+
defaultTagName: "span",
|
|
2253
|
+
render,
|
|
2254
|
+
props: mergeProps(defaultProps, props)
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
const holds = /* @__PURE__ */ new WeakMap();
|
|
2258
|
+
function touchPoint(event) {
|
|
2259
|
+
const touch = event.touches?.[0] ?? event.changedTouches?.[0];
|
|
2260
|
+
return touch ? {
|
|
2261
|
+
x: touch.clientX,
|
|
2262
|
+
y: touch.clientY
|
|
2263
|
+
} : null;
|
|
2264
|
+
}
|
|
2265
|
+
function farther(from, to, limit) {
|
|
2266
|
+
return Math.hypot(to.x - from.x, to.y - from.y) > limit;
|
|
2267
|
+
}
|
|
2268
|
+
var HoldThenMoveTouchSensorImpl = class extends TouchSensor {
|
|
2269
|
+
static activators = TouchSensor.activators;
|
|
2270
|
+
static setup() {
|
|
2271
|
+
return TouchSensor.setup();
|
|
2272
|
+
}
|
|
2273
|
+
attach() {
|
|
2274
|
+
super.attach();
|
|
2275
|
+
const self = this;
|
|
2276
|
+
const { holdMs = 500 } = self.props.options;
|
|
2277
|
+
const state = {
|
|
2278
|
+
heldAt: null,
|
|
2279
|
+
last: null,
|
|
2280
|
+
timer: setTimeout(() => {
|
|
2281
|
+
state.heldAt = state.last ?? self.initialCoordinates;
|
|
2282
|
+
}, holdMs)
|
|
2283
|
+
};
|
|
2284
|
+
holds.set(this, state);
|
|
2285
|
+
}
|
|
2286
|
+
handleMove(event) {
|
|
2287
|
+
const self = this;
|
|
2288
|
+
const state = holds.get(this);
|
|
2289
|
+
if (self.activated || !state) {
|
|
2290
|
+
super.handleMove(event);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
if ((event.touches?.length ?? 1) > 1) {
|
|
2294
|
+
self.handleCancel();
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
const point = touchPoint(event);
|
|
2298
|
+
const start = self.initialCoordinates;
|
|
2299
|
+
if (!point || !start) return;
|
|
2300
|
+
state.last = point;
|
|
2301
|
+
const { distance = 10, tolerance = 10 } = self.props.options;
|
|
2302
|
+
if (!state.heldAt) {
|
|
2303
|
+
if (farther(start, point, tolerance)) self.handleCancel();
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
if (event.cancelable) event.preventDefault();
|
|
2307
|
+
if (farther(state.heldAt, point, distance)) self.handleStart();
|
|
2308
|
+
}
|
|
2309
|
+
detach() {
|
|
2310
|
+
const state = holds.get(this);
|
|
2311
|
+
if (state) clearTimeout(state.timer);
|
|
2312
|
+
holds.delete(this);
|
|
2313
|
+
super.detach();
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
/** A TouchSensor that starts a drag on the first move after a still hold (see above). */
|
|
2317
|
+
const HoldThenMoveTouchSensor = HoldThenMoveTouchSensorImpl;
|
|
2318
|
+
/**
|
|
2319
|
+
* dnd-kit's PointerSensor for a mouse and a pen only: a finger's pointerdown is
|
|
2320
|
+
* left to `HoldThenMoveTouchSensor`.
|
|
2321
|
+
*/
|
|
2322
|
+
var MouseOnlyPointerSensor = class extends PointerSensor {
|
|
2323
|
+
static activators = [{
|
|
2324
|
+
eventName: "onPointerDown",
|
|
2325
|
+
handler: (event, options) => event.nativeEvent.pointerType === "touch" ? false : PointerSensor.activators[0].handler(event, options)
|
|
2326
|
+
}];
|
|
2327
|
+
};
|
|
2328
|
+
/**
|
|
2329
|
+
* `useSensor(HoldThenMoveTouchSensor, …)` with the activation the sensor relies
|
|
2330
|
+
* on: no constraint dnd-kit would act on by itself, so only a move after the
|
|
2331
|
+
* hold starts the drag.
|
|
2332
|
+
*/
|
|
2333
|
+
function useHoldThenMoveTouchSensor({ holdMs = 500, distance = 10, tolerance = 10 } = {}) {
|
|
2334
|
+
const options = useMemo(() => ({
|
|
2335
|
+
holdMs,
|
|
2336
|
+
distance,
|
|
2337
|
+
tolerance,
|
|
2338
|
+
activationConstraint: { distance: Number.POSITIVE_INFINITY }
|
|
2339
|
+
}), [
|
|
2340
|
+
holdMs,
|
|
2341
|
+
distance,
|
|
2342
|
+
tolerance
|
|
2343
|
+
]);
|
|
2344
|
+
return useSensor(HoldThenMoveTouchSensor, options);
|
|
2345
|
+
}
|
|
2346
|
+
/** The mouse / pen sensor that goes with it: picks up after `distance` px. */
|
|
2347
|
+
function useMouseOnlyPointerSensor(distance) {
|
|
2348
|
+
const options = useMemo(() => ({ activationConstraint: { distance } }), [distance]);
|
|
2349
|
+
return useSensor(MouseOnlyPointerSensor, options);
|
|
2350
|
+
}
|
|
2351
|
+
//#endregion
|
|
2352
|
+
//#region src/components/popover/popover.tsx
|
|
2353
|
+
const popover = tv$1({ slots: {
|
|
2354
|
+
trigger: ["select-none"],
|
|
2355
|
+
positioner: ["isolate z-50 outline-none"],
|
|
2356
|
+
content: [
|
|
2357
|
+
"max-h-(--available-height) min-w-[220px] overflow-hidden",
|
|
2358
|
+
"outline-none",
|
|
2359
|
+
"rounded-container bg-layer-popover text-foreground",
|
|
2360
|
+
"shadow-float ring-1 ring-line-seam"
|
|
2361
|
+
],
|
|
2362
|
+
header: ["flex items-center justify-between px-3", "min-h-11 gap-2 py-1.5 pr-1.5 pl-4"],
|
|
2363
|
+
body: ["flex min-w-0 flex-col gap-1.5", "px-4 pt-4 pb-3 last:pb-4"],
|
|
2364
|
+
actions: ["flex flex-wrap items-center justify-end gap-2", "px-4 pb-3 last:pb-4"],
|
|
2365
|
+
separator: ["border-t", "border-line-hairline"],
|
|
2366
|
+
title: ["m-0", "font-sans text-headline font-bold text-foreground"],
|
|
2367
|
+
description: ["m-0", "font-sans text-footnote text-foreground-muted"],
|
|
2368
|
+
close: [
|
|
2369
|
+
"inline-flex items-center justify-center",
|
|
2370
|
+
"outline-none transition-colors",
|
|
2371
|
+
"h-8 min-w-8 rounded-chip px-1.5",
|
|
2372
|
+
"font-sans text-footnote font-bold text-foreground",
|
|
2373
|
+
"hover:bg-fill-medium active:bg-fill-medium",
|
|
2374
|
+
"focus-visible:shadow-focus",
|
|
2375
|
+
"[&_svg]:size-4.5"
|
|
2376
|
+
],
|
|
2377
|
+
arrow: ["size-2 rotate-45", "border-l border-t border-line-seam bg-layer-popover"],
|
|
2378
|
+
backdrop: ["fixed inset-0", "bg-transparent"],
|
|
2379
|
+
viewport: ["block"]
|
|
2380
|
+
} });
|
|
2381
|
+
function Popover$1(props) {
|
|
2382
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
2383
|
+
"data-slot": "popover",
|
|
2384
|
+
...props
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
function PopoverTrigger({ className, ...props }) {
|
|
2388
|
+
const { trigger } = popover();
|
|
2389
|
+
return /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2390
|
+
"data-slot": "popover-trigger",
|
|
2391
|
+
className: trigger({ className }),
|
|
2392
|
+
...props
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
function PopoverPortal(props) {
|
|
2396
|
+
return /* @__PURE__ */ jsx(Popover.Portal, {
|
|
2397
|
+
"data-slot": "popover-portal",
|
|
2398
|
+
...useThemeScopeProps(props)
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
function PopoverPopup({ className, ...props }) {
|
|
2402
|
+
const { content } = popover();
|
|
2403
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2404
|
+
"data-slot": "popover-content",
|
|
2405
|
+
className: content({ className }),
|
|
2406
|
+
...props
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
function PopoverContent({ align = "start", alignOffset = 0, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod = "fixed", side = "bottom", sideOffset = 4, sticky, container, portalProps, className, ...props }) {
|
|
2410
|
+
const { positioner, content } = popover();
|
|
2411
|
+
const portalContainer = container !== void 0 ? container : portalProps?.container;
|
|
2412
|
+
return /* @__PURE__ */ jsx(PopoverPortal, {
|
|
2413
|
+
...portalProps,
|
|
2414
|
+
container: portalContainer,
|
|
2415
|
+
children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
2416
|
+
align,
|
|
2417
|
+
alignOffset,
|
|
2418
|
+
anchor,
|
|
2419
|
+
arrowPadding,
|
|
2420
|
+
collisionAvoidance,
|
|
2421
|
+
collisionBoundary,
|
|
2422
|
+
collisionPadding,
|
|
2423
|
+
disableAnchorTracking,
|
|
2424
|
+
positionMethod,
|
|
2425
|
+
side,
|
|
2426
|
+
sideOffset,
|
|
2427
|
+
sticky,
|
|
2428
|
+
className: positioner(),
|
|
2429
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
2430
|
+
"data-slot": "popover-content",
|
|
2431
|
+
className: content({ className }),
|
|
2432
|
+
...props
|
|
2433
|
+
})
|
|
2434
|
+
})
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
function PopoverViewport({ className, ...props }) {
|
|
2438
|
+
const { viewport } = popover();
|
|
2439
|
+
return /* @__PURE__ */ jsx(Popover.Viewport, {
|
|
2440
|
+
"data-slot": "popover-viewport",
|
|
2441
|
+
className: viewport({ className }),
|
|
2442
|
+
...props
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
function PopoverHeader({ className, ...props }) {
|
|
2446
|
+
const { header } = popover();
|
|
2447
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2448
|
+
"data-slot": "popover-header",
|
|
2449
|
+
className: header({ className }),
|
|
2450
|
+
...props
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
function PopoverSeparator({ className, ...props }) {
|
|
2454
|
+
const { separator } = popover();
|
|
2455
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2456
|
+
role: "separator",
|
|
2457
|
+
"aria-orientation": "horizontal",
|
|
2458
|
+
"data-slot": "popover-separator",
|
|
2459
|
+
className: separator({ className }),
|
|
2460
|
+
...props
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
function PopoverTitle({ className, ...props }) {
|
|
2464
|
+
const { title } = popover();
|
|
2465
|
+
return /* @__PURE__ */ jsx(Popover.Title, {
|
|
2466
|
+
"data-slot": "popover-title",
|
|
2467
|
+
className: title({ className }),
|
|
2468
|
+
...props
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
function PopoverDescription({ className, ...props }) {
|
|
2472
|
+
const { description } = popover();
|
|
2473
|
+
return /* @__PURE__ */ jsx(Popover.Description, {
|
|
2474
|
+
"data-slot": "popover-description",
|
|
2475
|
+
className: description({ className }),
|
|
2476
|
+
...props
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
function PopoverClose({ className, ...props }) {
|
|
2480
|
+
const { close } = popover();
|
|
2481
|
+
return /* @__PURE__ */ jsx(Popover.Close, {
|
|
2482
|
+
"data-slot": "popover-close",
|
|
2483
|
+
className: close({ className }),
|
|
2484
|
+
...props
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
function PopoverArrow({ className, ...props }) {
|
|
2488
|
+
const { arrow } = popover();
|
|
2489
|
+
return /* @__PURE__ */ jsx(Popover.Arrow, {
|
|
2490
|
+
"data-slot": "popover-arrow",
|
|
2491
|
+
className: arrow({ className }),
|
|
2492
|
+
...props
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
function PopoverBackdrop({ className, ...props }) {
|
|
2496
|
+
const { backdrop } = popover();
|
|
2497
|
+
return /* @__PURE__ */ jsx(Popover.Backdrop, {
|
|
2498
|
+
"data-slot": "popover-backdrop",
|
|
2499
|
+
className: backdrop({ className }),
|
|
2500
|
+
...props
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
/** The panel's text: a `PopoverTitle` and a `PopoverDescription`, 16px in from its edges. */
|
|
2504
|
+
function PopoverBody({ className, ...props }) {
|
|
2505
|
+
const { body } = popover();
|
|
2506
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2507
|
+
"data-slot": "popover-body",
|
|
2508
|
+
className: body({ className }),
|
|
2509
|
+
...props
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
/** The panel's buttons, at its end, under `PopoverBody`: the way out first, then the confirm button. */
|
|
2513
|
+
function PopoverActions({ className, ...props }) {
|
|
2514
|
+
const { actions } = popover();
|
|
2515
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2516
|
+
"data-slot": "popover-actions",
|
|
2517
|
+
className: actions({ className }),
|
|
2518
|
+
...props
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
//#endregion
|
|
2522
|
+
//#region src/components/mobile-nav/mobile-nav-server.tsx
|
|
2523
|
+
const mobileNavServer = tv$1({ slots: {
|
|
2524
|
+
item: [
|
|
2525
|
+
"select-none [-webkit-touch-callout:none]",
|
|
2526
|
+
"*:data-[slot=avatar]:size-[22px]",
|
|
2527
|
+
"data-popup-open:before:bg-fill-medium"
|
|
2528
|
+
],
|
|
2529
|
+
content: [
|
|
2530
|
+
"flex flex-col",
|
|
2531
|
+
"w-[calc(var(--anchor-width)-16px)] min-w-0",
|
|
2532
|
+
"max-h-[min(60dvh,calc(var(--available-height)-env(safe-area-inset-top,0px)))]"
|
|
2533
|
+
],
|
|
2534
|
+
list: ["flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain p-1"],
|
|
2535
|
+
group: ["flex shrink-0 flex-col p-1"],
|
|
2536
|
+
row: [
|
|
2537
|
+
"flex w-full shrink-0 cursor-default select-none items-center",
|
|
2538
|
+
"text-left outline-none transition-colors",
|
|
2539
|
+
"min-h-option gap-2.5 rounded-chip px-2.5 py-option-inset",
|
|
2540
|
+
"font-sans text-callout font-medium text-foreground",
|
|
2541
|
+
"hover:bg-brand-soft active:bg-brand-soft focus-visible:bg-brand-soft",
|
|
2542
|
+
"aria-[current=true]:font-bold",
|
|
2543
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&>svg:not([class*='size-'])]:size-4",
|
|
2544
|
+
"[&>svg]:text-foreground-muted",
|
|
2545
|
+
"*:data-[slot=avatar]:size-5 *:data-[slot=avatar]:shrink-0"
|
|
2546
|
+
],
|
|
2547
|
+
rowLabel: ["min-w-0 flex-1 truncate"],
|
|
2548
|
+
rowBody: ["flex min-w-0 flex-1 flex-col"],
|
|
2549
|
+
rowDescription: ["min-w-0 truncate", "font-mono text-mono-meta font-normal text-foreground-hint"],
|
|
2550
|
+
destinations: ["grid shrink-0 grid-flow-col auto-cols-fr gap-1 p-1"],
|
|
2551
|
+
destination: [
|
|
2552
|
+
"relative flex min-w-0 cursor-default select-none flex-col items-center justify-center",
|
|
2553
|
+
"outline-none transition-colors",
|
|
2554
|
+
"gap-1 rounded-chip px-1 py-2",
|
|
2555
|
+
"font-sans text-footnote font-medium text-foreground",
|
|
2556
|
+
"hover:bg-brand-soft active:bg-brand-soft focus-visible:bg-brand-soft",
|
|
2557
|
+
"[&_svg]:pointer-events-none [&_svg]:size-5 [&_svg]:shrink-0 [&_svg]:text-foreground-muted",
|
|
2558
|
+
"[&>span]:max-w-full [&>span]:truncate",
|
|
2559
|
+
"*:data-[slot=status]:absolute *:data-[slot=status]:top-1 *:data-[slot=status]:left-[calc(50%+6px)]"
|
|
2560
|
+
]
|
|
2561
|
+
} });
|
|
2562
|
+
const MobileNavServerContext = createContext(null);
|
|
2563
|
+
function useMobileNavServerContext(part) {
|
|
2564
|
+
const context = use(MobileNavServerContext);
|
|
2565
|
+
if (!context) throw new Error(`${part} must be inside MobileNavServerPopover`);
|
|
2566
|
+
return context;
|
|
2567
|
+
}
|
|
2568
|
+
/** The root: owns the popover's open state, like `Popover` (`open`, `onOpenChange`). */
|
|
2569
|
+
function MobileNavServerPopover(props) {
|
|
2570
|
+
const [handle] = useState(() => Popover.createHandle());
|
|
2571
|
+
const triggerRef = useRef(null);
|
|
2572
|
+
const context = useMemo(() => ({
|
|
2573
|
+
handle,
|
|
2574
|
+
triggerRef
|
|
2575
|
+
}), [handle]);
|
|
2576
|
+
return /* @__PURE__ */ jsx(MobileNavServerContext, {
|
|
2577
|
+
value: context,
|
|
2578
|
+
children: /* @__PURE__ */ jsx(Popover.Root, {
|
|
2579
|
+
"data-slot": "mobile-nav-server-popover",
|
|
2580
|
+
handle,
|
|
2581
|
+
...props
|
|
2582
|
+
})
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* The tab: the server's avatar and its name in `MobileNavLabel`, as children.
|
|
2587
|
+
* A press opens the popover, and so does holding it for 500ms (the long press
|
|
2588
|
+
* of raft-ui's menus). Lifting the finger after a hold never closes it: the
|
|
2589
|
+
* click that ends the hold is not a press, whether the hold opened the
|
|
2590
|
+
* popover or found it open.
|
|
2591
|
+
* It is never `selected`: it holds no page.
|
|
2592
|
+
*/
|
|
2593
|
+
function MobileNavServerItem({ className, id, ref, onClick, onContextMenu, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onPointerLeave, ...props }) {
|
|
2594
|
+
const { handle, triggerRef } = useMobileNavServerContext("MobileNavServerItem");
|
|
2595
|
+
const { item } = mobileNavServer();
|
|
2596
|
+
const generatedId = useId();
|
|
2597
|
+
const triggerId = id ?? generatedId;
|
|
2598
|
+
const holdTimer = useRef(void 0);
|
|
2599
|
+
const holdStart = useRef(null);
|
|
2600
|
+
const held = useRef(false);
|
|
2601
|
+
const cancelHold = useCallback(() => {
|
|
2602
|
+
clearTimeout(holdTimer.current);
|
|
2603
|
+
holdTimer.current = void 0;
|
|
2604
|
+
holdStart.current = null;
|
|
2605
|
+
}, []);
|
|
2606
|
+
useEffect(() => cancelHold, [cancelHold]);
|
|
2607
|
+
const setRef = useCallback((node) => {
|
|
2608
|
+
triggerRef.current = node;
|
|
2609
|
+
assignRef(ref, node);
|
|
2610
|
+
}, [ref, triggerRef]);
|
|
2611
|
+
const handlePointerDown = useCallback((event) => {
|
|
2612
|
+
onPointerDown?.(event);
|
|
2613
|
+
cancelHold();
|
|
2614
|
+
held.current = false;
|
|
2615
|
+
if (event.button !== 0) return;
|
|
2616
|
+
holdStart.current = {
|
|
2617
|
+
x: event.clientX,
|
|
2618
|
+
y: event.clientY
|
|
2619
|
+
};
|
|
2620
|
+
holdTimer.current = setTimeout(() => {
|
|
2621
|
+
holdTimer.current = void 0;
|
|
2622
|
+
held.current = true;
|
|
2623
|
+
if (!handle.isOpen) handle.open(triggerId);
|
|
2624
|
+
}, 500);
|
|
2625
|
+
}, [
|
|
2626
|
+
cancelHold,
|
|
2627
|
+
handle,
|
|
2628
|
+
onPointerDown,
|
|
2629
|
+
triggerId
|
|
2630
|
+
]);
|
|
2631
|
+
const handlePointerMove = useCallback((event) => {
|
|
2632
|
+
onPointerMove?.(event);
|
|
2633
|
+
const start = holdStart.current;
|
|
2634
|
+
if (!start) return;
|
|
2635
|
+
if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > 10) cancelHold();
|
|
2636
|
+
}, [cancelHold, onPointerMove]);
|
|
2637
|
+
const handlePointerUp = useCallback((event) => {
|
|
2638
|
+
onPointerUp?.(event);
|
|
2639
|
+
cancelHold();
|
|
2640
|
+
}, [cancelHold, onPointerUp]);
|
|
2641
|
+
const handlePointerCancel = useCallback((event) => {
|
|
2642
|
+
onPointerCancel?.(event);
|
|
2643
|
+
cancelHold();
|
|
2644
|
+
}, [cancelHold, onPointerCancel]);
|
|
2645
|
+
const handlePointerLeave = useCallback((event) => {
|
|
2646
|
+
onPointerLeave?.(event);
|
|
2647
|
+
cancelHold();
|
|
2648
|
+
}, [cancelHold, onPointerLeave]);
|
|
2649
|
+
const handleClick = useCallback((event) => {
|
|
2650
|
+
onClick?.(event);
|
|
2651
|
+
if (!held.current) return;
|
|
2652
|
+
held.current = false;
|
|
2653
|
+
event.preventBaseUIHandler?.();
|
|
2654
|
+
}, [onClick]);
|
|
2655
|
+
const handleContextMenu = useCallback((event) => {
|
|
2656
|
+
onContextMenu?.(event);
|
|
2657
|
+
event.preventDefault();
|
|
2658
|
+
}, [onContextMenu]);
|
|
2659
|
+
return /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2660
|
+
handle,
|
|
2661
|
+
id: triggerId,
|
|
2662
|
+
ref: setRef,
|
|
2663
|
+
render: /* @__PURE__ */ jsx(MobileNavItem, {}),
|
|
2664
|
+
"data-slot": "mobile-nav-server-item",
|
|
2665
|
+
className: item({ className }),
|
|
2666
|
+
onPointerDown: handlePointerDown,
|
|
2667
|
+
onPointerMove: handlePointerMove,
|
|
2668
|
+
onPointerUp: handlePointerUp,
|
|
2669
|
+
onPointerCancel: handlePointerCancel,
|
|
2670
|
+
onPointerLeave: handlePointerLeave,
|
|
2671
|
+
onClick: handleClick,
|
|
2672
|
+
onContextMenu: handleContextMenu,
|
|
2673
|
+
...props
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* The popover above the tab bar, 8px from each side of it and 8px above it,
|
|
2678
|
+
* as tall as its content and at most 60% of the screen. It stops below the
|
|
2679
|
+
* top safe area (a notch in a standalone web app): Base UI's collision padding
|
|
2680
|
+
* takes only pixels, so the height cap subtracts `env(safe-area-inset-top)`.
|
|
2681
|
+
* The bottom needs none: the tab bar under the popover already pads the
|
|
2682
|
+
* bottom safe area. A column:
|
|
2683
|
+
* `MobileNavServerPopoverList` takes the room left and scrolls, and the
|
|
2684
|
+
* destinations and the footer after it stay in view.
|
|
2685
|
+
*/
|
|
2686
|
+
function MobileNavServerPopoverContent({ anchor, side = "top", align = "center", sideOffset = 8, collisionPadding = 8, className, ...props }) {
|
|
2687
|
+
const { triggerRef } = useMobileNavServerContext("MobileNavServerPopoverContent");
|
|
2688
|
+
const { content } = mobileNavServer();
|
|
2689
|
+
const tabBar = useCallback(() => {
|
|
2690
|
+
const trigger = triggerRef.current;
|
|
2691
|
+
return trigger?.closest("[data-slot=mobile-nav-root]") ?? trigger;
|
|
2692
|
+
}, [triggerRef]);
|
|
2693
|
+
return /* @__PURE__ */ jsx(PopoverContent, {
|
|
2694
|
+
"data-slot": "mobile-nav-server-popover-content",
|
|
2695
|
+
anchor: anchor ?? tabBar,
|
|
2696
|
+
side,
|
|
2697
|
+
align,
|
|
2698
|
+
sideOffset,
|
|
2699
|
+
collisionPadding,
|
|
2700
|
+
className: content({ className }),
|
|
2701
|
+
...props
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
/** The server list: takes the room left in the popover and scrolls inside it. */
|
|
2705
|
+
function MobileNavServerPopoverList({ className, ...props }) {
|
|
2706
|
+
const { list } = mobileNavServer();
|
|
2707
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2708
|
+
"data-slot": "mobile-nav-server-popover-list",
|
|
2709
|
+
className: list({ className }),
|
|
2710
|
+
...props
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
/** Rows that always stay in view, such as the footer actions. */
|
|
2714
|
+
function MobileNavServerPopoverGroup({ className, ...props }) {
|
|
2715
|
+
const { group } = mobileNavServer();
|
|
2716
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2717
|
+
"data-slot": "mobile-nav-server-popover-group",
|
|
2718
|
+
className: group({ className }),
|
|
2719
|
+
...props
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* One row at the option height of every popup list (`py-option-inset`, 36px
|
|
2724
|
+
* for one line; a second line adds its own height and nothing else): a 20px
|
|
2725
|
+
* avatar or a 16px icon, `MobileNavServerPopoverRowLabel` (or a
|
|
2726
|
+
* `MobileNavServerPopoverRowBody` with a description under the label), then
|
|
2727
|
+
* anything trailing (a check, a count), centered on the row.
|
|
2728
|
+
* `aria-current="true"` marks the current server in bold.
|
|
2729
|
+
*/
|
|
2730
|
+
function MobileNavServerPopoverRow({ className, type = "button", ...props }) {
|
|
2731
|
+
const { row } = mobileNavServer();
|
|
2732
|
+
return /* @__PURE__ */ jsx("button", {
|
|
2733
|
+
type,
|
|
2734
|
+
"data-slot": "mobile-nav-server-popover-row",
|
|
2735
|
+
className: row({ className }),
|
|
2736
|
+
...props
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
/** A row's label: takes the room and truncates. */
|
|
2740
|
+
function MobileNavServerPopoverRowLabel({ className, ...props }) {
|
|
2741
|
+
const { rowLabel } = mobileNavServer();
|
|
2742
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2743
|
+
"data-slot": "mobile-nav-server-popover-row-label",
|
|
2744
|
+
className: rowLabel({ className }),
|
|
2745
|
+
...props
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
/** The server's own pages, side by side in equal cells; a `group`, named with `aria-label`. */
|
|
2749
|
+
function MobileNavServerPopoverDestinations({ className, ...props }) {
|
|
2750
|
+
const { destinations } = mobileNavServer();
|
|
2751
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2752
|
+
role: "group",
|
|
2753
|
+
"data-slot": "mobile-nav-server-popover-destinations",
|
|
2754
|
+
className: destinations({ className }),
|
|
2755
|
+
...props
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* One of the server's pages: a 20px icon over a short label in a `span`, which
|
|
2760
|
+
* truncates. Something new on that page (a feedback reply, a computer that
|
|
2761
|
+
* needs attention) is an `accent` `Status` child, the attention dot at the
|
|
2762
|
+
* icon's top-right corner: the server tab's avatar never carries it.
|
|
2763
|
+
*/
|
|
2764
|
+
function MobileNavServerPopoverDestination({ className, type = "button", ...props }) {
|
|
2765
|
+
const { destination } = mobileNavServer();
|
|
2766
|
+
return /* @__PURE__ */ jsx("button", {
|
|
2767
|
+
type,
|
|
2768
|
+
"data-slot": "mobile-nav-server-popover-destination",
|
|
2769
|
+
className: destination({ className }),
|
|
2770
|
+
...props
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* The text of a two-line row: `MobileNavServerPopoverRowLabel`, then
|
|
2775
|
+
* `MobileNavServerPopoverRowDescription` under it. The second line adds its
|
|
2776
|
+
* own height and nothing else; the avatar and the trailing check or count
|
|
2777
|
+
* center on both lines.
|
|
2778
|
+
*/
|
|
2779
|
+
function MobileNavServerPopoverRowBody({ className, ...props }) {
|
|
2780
|
+
const { rowBody } = mobileNavServer();
|
|
2781
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2782
|
+
"data-slot": "mobile-nav-server-popover-row-body",
|
|
2783
|
+
className: rowBody({ className }),
|
|
2784
|
+
...props
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
/** A row's second line, such as a server's address: muted Space Mono; truncates. */
|
|
2788
|
+
function MobileNavServerPopoverRowDescription({ className, ...props }) {
|
|
2789
|
+
const { rowDescription } = mobileNavServer();
|
|
2790
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2791
|
+
"data-slot": "mobile-nav-server-popover-row-description",
|
|
2792
|
+
className: rowDescription({ className }),
|
|
2793
|
+
...props
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
function assignRef(ref, value) {
|
|
2797
|
+
if (typeof ref === "function") ref(value);
|
|
2798
|
+
else if (ref) ref.current = value;
|
|
2799
|
+
}
|
|
2800
|
+
//#endregion
|
|
2801
|
+
//#region src/components/preview-card/preview-card.tsx
|
|
2802
|
+
const previewCard = tv$1({ slots: {
|
|
2803
|
+
trigger: [],
|
|
2804
|
+
positioner: ["isolate z-50 outline-none"],
|
|
2805
|
+
popup: [
|
|
2806
|
+
"max-h-(--available-height) min-w-[220px] overflow-hidden",
|
|
2807
|
+
"outline-none",
|
|
2808
|
+
"origin-(--transform-origin) transform-gpu opacity-100 will-change-[transform,opacity]",
|
|
2809
|
+
"transition-[opacity,transform] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
2810
|
+
"data-starting-style:scale-[0.97] data-starting-style:opacity-0",
|
|
2811
|
+
"data-ending-style:scale-[0.99] data-ending-style:opacity-0 data-ending-style:duration-150",
|
|
2812
|
+
"data-instant:transition-none motion-reduce:transition-none",
|
|
2813
|
+
"w-[300px] rounded-container bg-layer-popover text-foreground",
|
|
2814
|
+
"shadow-float ring-1 ring-line-seam"
|
|
2815
|
+
],
|
|
2816
|
+
header: ["flex items-start gap-3", "px-4 pt-4 pb-3 last:pb-4"],
|
|
2817
|
+
separator: ["border-t", "border-line-hairline"],
|
|
2818
|
+
title: ["m-0 truncate", "font-sans text-headline font-bold text-foreground"],
|
|
2819
|
+
description: ["m-0 truncate", "px-4 py-3 last:pb-4 font-sans text-callout text-foreground-muted"],
|
|
2820
|
+
arrow: ["size-2 rotate-45", "border-l border-t border-line-seam bg-layer-popover"],
|
|
2821
|
+
body: [
|
|
2822
|
+
"flex min-w-0 flex-col gap-1.5",
|
|
2823
|
+
"px-4 pt-4 pb-3 last:pb-4",
|
|
2824
|
+
"*:data-[slot=preview-card-title]:line-clamp-3 *:data-[slot=preview-card-title]:whitespace-normal"
|
|
2825
|
+
],
|
|
2826
|
+
eyebrow: ["m-0 truncate", "font-mono text-mono-meta text-foreground-muted"],
|
|
2827
|
+
meta: ["flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", "m-0 font-sans text-callout text-foreground-muted"],
|
|
2828
|
+
actions: ["flex flex-wrap items-center gap-2", "px-4 pb-3 last:pb-4"],
|
|
2829
|
+
footer: [
|
|
2830
|
+
"m-0 truncate",
|
|
2831
|
+
"border-t border-line-hairline px-4 py-2",
|
|
2832
|
+
"font-sans text-caption text-foreground-muted"
|
|
2833
|
+
]
|
|
2834
|
+
} });
|
|
2835
|
+
function PreviewCard$1(props) {
|
|
2836
|
+
return /* @__PURE__ */ jsx(PreviewCard.Root, {
|
|
2837
|
+
"data-slot": "preview-card",
|
|
2838
|
+
...props
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
function PreviewCardTrigger({ delay = 80, closeDelay = 40, className, ...props }) {
|
|
2842
|
+
const { trigger } = previewCard();
|
|
2843
|
+
return /* @__PURE__ */ jsx(PreviewCard.Trigger, {
|
|
2844
|
+
"data-slot": "preview-card-trigger",
|
|
2845
|
+
delay,
|
|
2846
|
+
closeDelay,
|
|
2847
|
+
className: trigger({ className }),
|
|
2848
|
+
...props
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
function PreviewCardPortal(props) {
|
|
2852
|
+
return /* @__PURE__ */ jsx(PreviewCard.Portal, {
|
|
2853
|
+
"data-slot": "preview-card-portal",
|
|
2854
|
+
...useThemeScopeProps(props)
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
function PreviewCardPopup({ className, ...props }) {
|
|
2858
|
+
const themeState = useTheme();
|
|
2859
|
+
const { popup } = previewCard();
|
|
2860
|
+
const scopeProps = resolveThemeScopeProps({
|
|
2861
|
+
...props,
|
|
2862
|
+
className: popup({ className })
|
|
2863
|
+
}, themeState);
|
|
2864
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2865
|
+
"data-slot": "preview-card-content",
|
|
2866
|
+
...scopeProps
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
function PreviewCardContent({ align = "start", alignOffset = 0, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod = "fixed", side = "bottom", sideOffset = 6, sticky, portalProps, className, ...props }) {
|
|
2870
|
+
const { positioner, popup } = previewCard();
|
|
2871
|
+
return /* @__PURE__ */ jsx(PreviewCardPortal, {
|
|
2872
|
+
...portalProps,
|
|
2873
|
+
children: /* @__PURE__ */ jsx(PreviewCard.Positioner, {
|
|
2874
|
+
align,
|
|
2875
|
+
alignOffset,
|
|
2876
|
+
anchor,
|
|
2877
|
+
arrowPadding,
|
|
2878
|
+
collisionAvoidance,
|
|
2879
|
+
collisionBoundary,
|
|
2880
|
+
collisionPadding,
|
|
2881
|
+
disableAnchorTracking,
|
|
2882
|
+
positionMethod,
|
|
2883
|
+
side,
|
|
2884
|
+
sideOffset,
|
|
2885
|
+
sticky,
|
|
2886
|
+
className: positioner(),
|
|
2887
|
+
children: /* @__PURE__ */ jsx(PreviewCard.Popup, {
|
|
2888
|
+
"data-slot": "preview-card-content",
|
|
2889
|
+
className: popup({ className }),
|
|
2890
|
+
...props
|
|
2891
|
+
})
|
|
2892
|
+
})
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
function PreviewCardHeader({ className, ...props }) {
|
|
2896
|
+
const { header } = previewCard();
|
|
2897
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2898
|
+
"data-slot": "preview-card-header",
|
|
2899
|
+
className: header({ className }),
|
|
2900
|
+
...props
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
function PreviewCardSeparator({ className, ...props }) {
|
|
2904
|
+
const { separator } = previewCard();
|
|
2905
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2906
|
+
role: "separator",
|
|
2907
|
+
"aria-orientation": "horizontal",
|
|
2908
|
+
"data-slot": "preview-card-separator",
|
|
2909
|
+
className: separator({ className }),
|
|
2910
|
+
...props
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
function PreviewCardTitle({ className, ...props }) {
|
|
2914
|
+
const { title } = previewCard();
|
|
2915
|
+
return /* @__PURE__ */ jsx("p", {
|
|
2916
|
+
"data-slot": "preview-card-title",
|
|
2917
|
+
className: title({ className }),
|
|
2918
|
+
...props
|
|
1552
2919
|
});
|
|
1553
2920
|
}
|
|
1554
|
-
function
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
className: "size-3 shrink-0",
|
|
1561
|
-
children: /* @__PURE__ */ jsx("g", {
|
|
1562
|
-
fill: "none",
|
|
1563
|
-
strokeLinecap: "round",
|
|
1564
|
-
strokeLinejoin: "round",
|
|
1565
|
-
strokeWidth: "1",
|
|
1566
|
-
stroke: "currentColor",
|
|
1567
|
-
children: /* @__PURE__ */ jsx("path", { d: "M1.75 6.41L4.4 9.25L10.25 2.99" })
|
|
1568
|
-
})
|
|
2921
|
+
function PreviewCardDescription({ className, ...props }) {
|
|
2922
|
+
const { description } = previewCard();
|
|
2923
|
+
return /* @__PURE__ */ jsx("p", {
|
|
2924
|
+
"data-slot": "preview-card-description",
|
|
2925
|
+
className: description({ className }),
|
|
2926
|
+
...props
|
|
1569
2927
|
});
|
|
1570
2928
|
}
|
|
1571
|
-
function
|
|
1572
|
-
|
|
1573
|
-
|
|
2929
|
+
function PreviewCardArrow({ className, ...props }) {
|
|
2930
|
+
const { arrow } = previewCard();
|
|
2931
|
+
return /* @__PURE__ */ jsx(PreviewCard.Arrow, {
|
|
2932
|
+
"data-slot": "preview-card-arrow",
|
|
2933
|
+
className: arrow({ className }),
|
|
1574
2934
|
...props
|
|
1575
2935
|
});
|
|
1576
2936
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
className: indicator(),
|
|
1585
|
-
children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(DropdownMenuCheckIcon, {}) })
|
|
1586
|
-
})]
|
|
2937
|
+
/** The item card's block: the id line, the title and the details row, in that order. */
|
|
2938
|
+
function PreviewCardBody({ className, ...props }) {
|
|
2939
|
+
const { body } = previewCard();
|
|
2940
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2941
|
+
"data-slot": "preview-card-body",
|
|
2942
|
+
className: body({ className }),
|
|
2943
|
+
...props
|
|
1587
2944
|
});
|
|
1588
2945
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
"data-slot": "dropdown-menu-radio-item-label",
|
|
1597
|
-
className,
|
|
1598
|
-
children
|
|
2946
|
+
/** The muted line above the title that identifies the item, such as its id. One line, truncated. */
|
|
2947
|
+
function PreviewCardEyebrow({ className, ...props }) {
|
|
2948
|
+
const { eyebrow } = previewCard();
|
|
2949
|
+
return /* @__PURE__ */ jsx("p", {
|
|
2950
|
+
"data-slot": "preview-card-eyebrow",
|
|
2951
|
+
className: eyebrow({ className }),
|
|
2952
|
+
...props
|
|
1599
2953
|
});
|
|
1600
|
-
return children;
|
|
1601
2954
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2955
|
+
/** The row under the title: one state chip, then muted details. Wraps when it runs out of room. */
|
|
2956
|
+
function PreviewCardMeta({ className, ...props }) {
|
|
2957
|
+
const { meta } = previewCard();
|
|
2958
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2959
|
+
"data-slot": "preview-card-meta",
|
|
2960
|
+
className: meta({ className }),
|
|
1606
2961
|
...props
|
|
1607
2962
|
});
|
|
1608
2963
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
className: indicator(),
|
|
1617
|
-
children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(DropdownMenuCheckIcon, {}) })
|
|
1618
|
-
})]
|
|
2964
|
+
/** The row of at most two buttons. */
|
|
2965
|
+
function PreviewCardActions({ className, ...props }) {
|
|
2966
|
+
const { actions } = previewCard();
|
|
2967
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2968
|
+
"data-slot": "preview-card-actions",
|
|
2969
|
+
className: actions({ className }),
|
|
2970
|
+
...props
|
|
1619
2971
|
});
|
|
1620
2972
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2973
|
+
/** The caption under a hairline at the bottom of the card: where the item comes from. */
|
|
2974
|
+
function PreviewCardFooter({ className, ...props }) {
|
|
2975
|
+
const { footer } = previewCard();
|
|
2976
|
+
return /* @__PURE__ */ jsx("p", {
|
|
2977
|
+
"data-slot": "preview-card-footer",
|
|
2978
|
+
className: footer({ className }),
|
|
1625
2979
|
...props
|
|
1626
2980
|
});
|
|
1627
2981
|
}
|
|
@@ -1819,138 +3173,6 @@ function useOverflowTitle(ref, title) {
|
|
|
1819
3173
|
}, [own, ref]);
|
|
1820
3174
|
}
|
|
1821
3175
|
//#endregion
|
|
1822
|
-
//#region src/components/segmented-control/segmented-control.tsx
|
|
1823
|
-
const segmentedControl = tv$1({
|
|
1824
|
-
slots: {
|
|
1825
|
-
root: ["inline-flex shrink-0 items-center", "w-fit flex-nowrap gap-[3px] rounded-[11px] bg-fill-muted p-[3px]"],
|
|
1826
|
-
item: [
|
|
1827
|
-
"inline-flex min-w-0 shrink-0 items-center gap-1.5",
|
|
1828
|
-
"outline-none disabled:pointer-events-none",
|
|
1829
|
-
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1830
|
-
"h-8 justify-center rounded-control border-[1.5px] border-transparent bg-transparent px-3",
|
|
1831
|
-
"font-sans text-callout font-semibold text-foreground-muted",
|
|
1832
|
-
"[&_svg:not([class*='size-'])]:size-4",
|
|
1833
|
-
"hover:bg-fill-medium active:bg-fill-medium",
|
|
1834
|
-
"data-checked:border-sticker-ink data-checked:bg-brand data-checked:font-bold data-checked:text-sticker-ink data-checked:ring-[1.5px] data-checked:ring-sticker-edge",
|
|
1835
|
-
"data-checked:hover:bg-brand data-checked:active:bg-brand data-checked:active:overlay-sticker-medium",
|
|
1836
|
-
"focus-visible:shadow-focus",
|
|
1837
|
-
"data-disabled:cursor-not-allowed data-disabled:text-foreground-disabled data-disabled:hover:bg-transparent"
|
|
1838
|
-
],
|
|
1839
|
-
label: ["truncate"],
|
|
1840
|
-
count: ["font-mono", "text-mono-meta font-normal"]
|
|
1841
|
-
},
|
|
1842
|
-
variants: {
|
|
1843
|
-
variant: {
|
|
1844
|
-
default: {},
|
|
1845
|
-
"on-frame": { root: [
|
|
1846
|
-
"bg-fill-frame-medium",
|
|
1847
|
-
"**:data-[slot=segmented-control-item]:text-foreground-on-frame-muted **:data-[slot=segmented-control-item]:hover:text-foreground-on-frame",
|
|
1848
|
-
"**:data-[slot=segmented-control-item]:hover:bg-transparent **:data-[slot=segmented-control-item]:hover:overlay-frame-medium **:data-[slot=segmented-control-item]:active:bg-transparent **:data-[slot=segmented-control-item]:active:overlay-frame-medium",
|
|
1849
|
-
"**:data-[slot=segmented-control-item]:data-checked:border-line-frame-active **:data-[slot=segmented-control-item]:data-checked:bg-layer-frame-active **:data-[slot=segmented-control-item]:data-checked:text-foreground-on-frame-active",
|
|
1850
|
-
"**:data-[slot=segmented-control-item]:data-checked:hover:bg-layer-frame-active **:data-[slot=segmented-control-item]:data-checked:hover:bg-none **:data-[slot=segmented-control-item]:data-checked:active:bg-layer-frame-active"
|
|
1851
|
-
] }
|
|
1852
|
-
},
|
|
1853
|
-
iconsOnlyBelow: {
|
|
1854
|
-
xs: { root: ["@max-xs:**:data-[slot=segmented-control-item]:relative @max-xs:**:data-[slot=segmented-control-item]:w-8 @max-xs:**:data-[slot=segmented-control-item]:px-0", "@max-xs:**:data-[slot=segmented-control-label]:sr-only @max-xs:**:data-[slot=segmented-control-count]:sr-only"] },
|
|
1855
|
-
sm: { root: ["@max-sm:**:data-[slot=segmented-control-item]:relative @max-sm:**:data-[slot=segmented-control-item]:w-8 @max-sm:**:data-[slot=segmented-control-item]:px-0", "@max-sm:**:data-[slot=segmented-control-label]:sr-only @max-sm:**:data-[slot=segmented-control-count]:sr-only"] },
|
|
1856
|
-
md: { root: ["@max-md:**:data-[slot=segmented-control-item]:relative @max-md:**:data-[slot=segmented-control-item]:w-8 @max-md:**:data-[slot=segmented-control-item]:px-0", "@max-md:**:data-[slot=segmented-control-label]:sr-only @max-md:**:data-[slot=segmented-control-count]:sr-only"] },
|
|
1857
|
-
lg: { root: ["@max-lg:**:data-[slot=segmented-control-item]:relative @max-lg:**:data-[slot=segmented-control-item]:w-8 @max-lg:**:data-[slot=segmented-control-item]:px-0", "@max-lg:**:data-[slot=segmented-control-label]:sr-only @max-lg:**:data-[slot=segmented-control-count]:sr-only"] },
|
|
1858
|
-
xl: { root: ["@max-xl:**:data-[slot=segmented-control-item]:relative @max-xl:**:data-[slot=segmented-control-item]:w-8 @max-xl:**:data-[slot=segmented-control-item]:px-0", "@max-xl:**:data-[slot=segmented-control-label]:sr-only @max-xl:**:data-[slot=segmented-control-count]:sr-only"] },
|
|
1859
|
-
"2xl": { root: ["@max-2xl:**:data-[slot=segmented-control-item]:relative @max-2xl:**:data-[slot=segmented-control-item]:w-8 @max-2xl:**:data-[slot=segmented-control-item]:px-0", "@max-2xl:**:data-[slot=segmented-control-label]:sr-only @max-2xl:**:data-[slot=segmented-control-count]:sr-only"] }
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
});
|
|
1863
|
-
const SegmentedControlIconsOnlyContext = createContext(false);
|
|
1864
|
-
function SegmentedControl({ disabled = false, iconsOnlyBelow, variant = "default", className, ...props }) {
|
|
1865
|
-
const { root } = segmentedControl({
|
|
1866
|
-
iconsOnlyBelow,
|
|
1867
|
-
variant
|
|
1868
|
-
});
|
|
1869
|
-
return /* @__PURE__ */ jsx(SegmentedControlIconsOnlyContext, {
|
|
1870
|
-
value: iconsOnlyBelow !== void 0,
|
|
1871
|
-
children: /* @__PURE__ */ jsx(RadioGroup, {
|
|
1872
|
-
disabled,
|
|
1873
|
-
"data-slot": "segmented-control",
|
|
1874
|
-
className: root({ className }),
|
|
1875
|
-
...props
|
|
1876
|
-
})
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
function SegmentedControlItem({ className, ...props }) {
|
|
1880
|
-
const { item } = segmentedControl();
|
|
1881
|
-
const iconsOnly = use(SegmentedControlIconsOnlyContext);
|
|
1882
|
-
const radio = /* @__PURE__ */ jsx(Radio.Root, {
|
|
1883
|
-
render: /* @__PURE__ */ jsx("button", { type: "button" }),
|
|
1884
|
-
nativeButton: true,
|
|
1885
|
-
"data-slot": "segmented-control-item",
|
|
1886
|
-
className: item({ className: `group/segmented-control-item ${className ?? ""}`.trim() }),
|
|
1887
|
-
...props
|
|
1888
|
-
});
|
|
1889
|
-
return iconsOnly ? /* @__PURE__ */ jsx(SegmentedControlItemTooltip, { children: radio }) : radio;
|
|
1890
|
-
}
|
|
1891
|
-
function SegmentedControlItemTooltip({ children }) {
|
|
1892
|
-
const [text, setText] = useState("");
|
|
1893
|
-
const triggerRef = useRef(null);
|
|
1894
|
-
const handleOpenChange = useCallback((open, details) => {
|
|
1895
|
-
if (!open) return;
|
|
1896
|
-
const label = triggerRef.current?.querySelector("[data-slot=segmented-control-label]");
|
|
1897
|
-
if (!label || label.getBoundingClientRect().width > 1) {
|
|
1898
|
-
details.cancel();
|
|
1899
|
-
return;
|
|
1900
|
-
}
|
|
1901
|
-
setText(label.textContent ?? "");
|
|
1902
|
-
}, []);
|
|
1903
|
-
return /* @__PURE__ */ jsxs(Tooltip$1, {
|
|
1904
|
-
onOpenChange: handleOpenChange,
|
|
1905
|
-
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1906
|
-
ref: triggerRef,
|
|
1907
|
-
render: children
|
|
1908
|
-
}), /* @__PURE__ */ jsx(TooltipContent, { children: text })]
|
|
1909
|
-
});
|
|
1910
|
-
}
|
|
1911
|
-
function SegmentedControlLabel({ className, ...props }) {
|
|
1912
|
-
const { label } = segmentedControl();
|
|
1913
|
-
return /* @__PURE__ */ jsx("span", {
|
|
1914
|
-
"data-slot": "segmented-control-label",
|
|
1915
|
-
className: label({ className }),
|
|
1916
|
-
...props
|
|
1917
|
-
});
|
|
1918
|
-
}
|
|
1919
|
-
function SegmentedControlCount({ className, ...props }) {
|
|
1920
|
-
const { count } = segmentedControl();
|
|
1921
|
-
return /* @__PURE__ */ jsx("span", {
|
|
1922
|
-
"data-slot": "segmented-control-count",
|
|
1923
|
-
className: count({ className }),
|
|
1924
|
-
...props
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
/**
|
|
1928
|
-
* The same track for choices that are places, not values: each segment is a
|
|
1929
|
-
* link to another URL (a language, a version of the page), so it can be
|
|
1930
|
-
* opened in a new tab and copied. Name it with `aria-label`.
|
|
1931
|
-
*/
|
|
1932
|
-
function SegmentedControlNav({ variant = "default", className, ...props }) {
|
|
1933
|
-
const { root } = segmentedControl({ variant });
|
|
1934
|
-
return /* @__PURE__ */ jsx("nav", {
|
|
1935
|
-
"data-slot": "segmented-control",
|
|
1936
|
-
className: root({ className }),
|
|
1937
|
-
...props
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1940
|
-
/** One segment of a `SegmentedControlNav`: a link, `<a>` unless `render` gives a router link. */
|
|
1941
|
-
function SegmentedControlLink({ current = false, className, render, ...props }) {
|
|
1942
|
-
const { item } = segmentedControl();
|
|
1943
|
-
return useRender({
|
|
1944
|
-
render: render ?? /* @__PURE__ */ jsx("a", {}),
|
|
1945
|
-
props: mergeProps({
|
|
1946
|
-
"data-slot": "segmented-control-item",
|
|
1947
|
-
"aria-current": current ? "page" : void 0,
|
|
1948
|
-
...current ? { "data-checked": "" } : {},
|
|
1949
|
-
className: item({ className: `group/segmented-control-item ${className ?? ""}`.trim() })
|
|
1950
|
-
}, props)
|
|
1951
|
-
});
|
|
1952
|
-
}
|
|
1953
|
-
//#endregion
|
|
1954
3176
|
//#region src/components/avatar/avatar.tsx
|
|
1955
3177
|
const avatar = tv$1({
|
|
1956
3178
|
slots: {
|
|
@@ -2997,10 +4219,38 @@ function MessageReactionTooltipMeta({ className, render, ...props }) {
|
|
|
2997
4219
|
});
|
|
2998
4220
|
}
|
|
2999
4221
|
//#endregion
|
|
4222
|
+
//#region src/lib/inline-chip.ts
|
|
4223
|
+
/**
|
|
4224
|
+
* The one box every chip in message text shares: a reference chip (any
|
|
4225
|
+
* variant, loading or unavailable too), the yellow sticker of a mention of the
|
|
4226
|
+
* person reading, and an inline task chip. The rule and its numbers are in
|
|
4227
|
+
* message-item.md, Chips in running text.
|
|
4228
|
+
*
|
|
4229
|
+
* - Type: 0.875em of the sentence, semibold.
|
|
4230
|
+
* - Height: the line box of its text, the body line less 2px, with no padding
|
|
4231
|
+
* above or below: 20px on a desktop (body 14/22), 22px on a phone (16/24). A
|
|
4232
|
+
* chip is always 2px shorter than its line, so it never makes the line taller.
|
|
4233
|
+
* `--chip-line-height` overrides it for text that isn't on the body line.
|
|
4234
|
+
* - Alignment: the chip's text on the sentence's baseline, a leading mark
|
|
4235
|
+
* centred on the chip (alignment.md, Marks in running text).
|
|
4236
|
+
* - Sides: 6px at each end. A chip with a leading mark (a 14px icon, an app's
|
|
4237
|
+
* image or avatar in `MessageReferenceIcon`'s 14px box, the loading spinner,
|
|
4238
|
+
* a task's 9px status pixel, each in its own box) takes
|
|
4239
|
+
* 4.5px at each end, so the ink at its two ends sits equally far in
|
|
4240
|
+
* (alignment.md, Chips), and the mark is 4px from the text. A note after the
|
|
4241
|
+
* label keeps the same 4px.
|
|
4242
|
+
* - Corners: `rounded-sticker`.
|
|
4243
|
+
*/
|
|
4244
|
+
const inlineChip = [
|
|
4245
|
+
"items-baseline gap-1 px-1.5 py-0 align-baseline",
|
|
4246
|
+
"has-[>svg,>[data-slot=spinner],>[data-slot=message-reference-icon]]:px-[4.5px]",
|
|
4247
|
+
"[font-size:0.875em] leading-[var(--chip-line-height,calc(var(--text-body--line-height)-2px))] font-semibold",
|
|
4248
|
+
"rounded-sticker"
|
|
4249
|
+
];
|
|
4250
|
+
//#endregion
|
|
3000
4251
|
//#region src/components/message-item/message-reference.recipe.ts
|
|
3001
4252
|
const chip = (fill) => [
|
|
3002
|
-
|
|
3003
|
-
"[font-size:0.875em] leading-5 font-semibold",
|
|
4253
|
+
...inlineChip,
|
|
3004
4254
|
"[--message-reference-em:1em]",
|
|
3005
4255
|
"[&_svg]:size-3.5 [&_svg]:text-foreground-muted",
|
|
3006
4256
|
fill
|
|
@@ -3030,23 +4280,29 @@ const messageReference = tv$1({
|
|
|
3030
4280
|
SUFFIX_LIFT,
|
|
3031
4281
|
"font-mono text-label leading-none font-normal text-foreground-hint"
|
|
3032
4282
|
],
|
|
4283
|
+
icon: ["inline-flex size-3.5 shrink-0 items-center justify-center self-center", "*:size-full *:shrink-0 [&>img]:rounded-[2px] [&>img]:object-cover"],
|
|
3033
4284
|
indicator: ["pointer-events-none absolute -right-1.5 top-0 inline-flex size-2 shrink-0 items-center justify-center", "*:data-[slot=status]:size-full"]
|
|
3034
4285
|
},
|
|
3035
|
-
variants: {
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
4286
|
+
variants: {
|
|
4287
|
+
variant: {
|
|
4288
|
+
primary: { root: [
|
|
4289
|
+
"inline-flex cursor-pointer",
|
|
4290
|
+
...inlineChip,
|
|
4291
|
+
"[--message-reference-em:1em]",
|
|
4292
|
+
"text-sticker-ink no-underline",
|
|
4293
|
+
"bg-brand inset-ring-[1.5px] inset-ring-sticker-ink",
|
|
4294
|
+
"hover:overlay-sticker-medium active:overlay-sticker-medium"
|
|
4295
|
+
] },
|
|
4296
|
+
secondary: { root: ["inline cursor-pointer font-bold text-foreground", "decoration-foreground decoration-[1.5px] underline-offset-[3px] hover:underline"] },
|
|
4297
|
+
accent: { root: linkChip("bg-attention-soft") },
|
|
4298
|
+
muted: { root: linkChip("bg-inactive-soft") },
|
|
4299
|
+
info: { root: linkChip("bg-info-soft") },
|
|
4300
|
+
link: { root: linkChip("bg-info-soft") },
|
|
4301
|
+
pending: { root: chip("cursor-wait bg-inactive-soft text-foreground-muted") },
|
|
4302
|
+
unavailable: { root: chip("cursor-default bg-inactive-soft text-foreground-hint") }
|
|
4303
|
+
},
|
|
4304
|
+
capped: { true: { root: "max-w-[min(100%,24em)]" } }
|
|
4305
|
+
},
|
|
3050
4306
|
defaultVariants: { variant: "muted" }
|
|
3051
4307
|
});
|
|
3052
4308
|
//#endregion
|
|
@@ -3073,8 +4329,11 @@ function MessageReferenceIndicator({ className, render, ...props }) {
|
|
|
3073
4329
|
state: { slot: "message-reference-indicator" }
|
|
3074
4330
|
});
|
|
3075
4331
|
}
|
|
3076
|
-
function MessageReferenceChip({ className, render, variant = "muted", ...props }) {
|
|
3077
|
-
const { root } = messageReference({
|
|
4332
|
+
function MessageReferenceChip({ className, render, variant = "muted", capped, ...props }) {
|
|
4333
|
+
const { root } = messageReference({
|
|
4334
|
+
variant,
|
|
4335
|
+
capped
|
|
4336
|
+
});
|
|
3078
4337
|
return useReferenceRender(root({ className }), render, props);
|
|
3079
4338
|
}
|
|
3080
4339
|
function MessageReferenceLabel({ className, render, ...props }) {
|
|
@@ -3099,8 +4358,26 @@ function MessageReferenceSuffix({ className, render, ...props }) {
|
|
|
3099
4358
|
state: { slot: "message-reference-suffix" }
|
|
3100
4359
|
});
|
|
3101
4360
|
}
|
|
3102
|
-
|
|
3103
|
-
|
|
4361
|
+
/**
|
|
4362
|
+
* The chip's leading icon when it is not a plain svg: an image or a `3xs`
|
|
4363
|
+
* Avatar, such as an app's registered icon. It holds one child at 14px,
|
|
4364
|
+
* centred on the chip. Put it first in the chip.
|
|
4365
|
+
*/
|
|
4366
|
+
function MessageReferenceIcon({ className, render, ...props }) {
|
|
4367
|
+
const { icon } = messageReference();
|
|
4368
|
+
const defaultProps = { className: icon({ className }) };
|
|
4369
|
+
return useRender({
|
|
4370
|
+
defaultTagName: "span",
|
|
4371
|
+
render,
|
|
4372
|
+
props: mergeProps(defaultProps, props),
|
|
4373
|
+
state: { slot: "message-reference-icon" }
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
function MessageReferencePending({ className, render, capped, ...props }) {
|
|
4377
|
+
const { root } = messageReference({
|
|
4378
|
+
variant: "pending",
|
|
4379
|
+
capped
|
|
4380
|
+
});
|
|
3104
4381
|
return useReferenceRender(root({ className }), render, props);
|
|
3105
4382
|
}
|
|
3106
4383
|
function MessageReferenceUnavailable({ className, render, ...props }) {
|
|
@@ -3666,100 +4943,6 @@ function MessageListJumpPillDismiss({ className, children, render, nativeButton,
|
|
|
3666
4943
|
})
|
|
3667
4944
|
});
|
|
3668
4945
|
}
|
|
3669
|
-
const holds = /* @__PURE__ */ new WeakMap();
|
|
3670
|
-
function touchPoint(event) {
|
|
3671
|
-
const touch = event.touches?.[0] ?? event.changedTouches?.[0];
|
|
3672
|
-
return touch ? {
|
|
3673
|
-
x: touch.clientX,
|
|
3674
|
-
y: touch.clientY
|
|
3675
|
-
} : null;
|
|
3676
|
-
}
|
|
3677
|
-
function farther(from, to, limit) {
|
|
3678
|
-
return Math.hypot(to.x - from.x, to.y - from.y) > limit;
|
|
3679
|
-
}
|
|
3680
|
-
var HoldThenMoveTouchSensorImpl = class extends TouchSensor {
|
|
3681
|
-
static activators = TouchSensor.activators;
|
|
3682
|
-
static setup() {
|
|
3683
|
-
return TouchSensor.setup();
|
|
3684
|
-
}
|
|
3685
|
-
attach() {
|
|
3686
|
-
super.attach();
|
|
3687
|
-
const self = this;
|
|
3688
|
-
const { holdMs = 500 } = self.props.options;
|
|
3689
|
-
const state = {
|
|
3690
|
-
heldAt: null,
|
|
3691
|
-
last: null,
|
|
3692
|
-
timer: setTimeout(() => {
|
|
3693
|
-
state.heldAt = state.last ?? self.initialCoordinates;
|
|
3694
|
-
}, holdMs)
|
|
3695
|
-
};
|
|
3696
|
-
holds.set(this, state);
|
|
3697
|
-
}
|
|
3698
|
-
handleMove(event) {
|
|
3699
|
-
const self = this;
|
|
3700
|
-
const state = holds.get(this);
|
|
3701
|
-
if (self.activated || !state) {
|
|
3702
|
-
super.handleMove(event);
|
|
3703
|
-
return;
|
|
3704
|
-
}
|
|
3705
|
-
if ((event.touches?.length ?? 1) > 1) {
|
|
3706
|
-
self.handleCancel();
|
|
3707
|
-
return;
|
|
3708
|
-
}
|
|
3709
|
-
const point = touchPoint(event);
|
|
3710
|
-
const start = self.initialCoordinates;
|
|
3711
|
-
if (!point || !start) return;
|
|
3712
|
-
state.last = point;
|
|
3713
|
-
const { distance = 10, tolerance = 10 } = self.props.options;
|
|
3714
|
-
if (!state.heldAt) {
|
|
3715
|
-
if (farther(start, point, tolerance)) self.handleCancel();
|
|
3716
|
-
return;
|
|
3717
|
-
}
|
|
3718
|
-
if (event.cancelable) event.preventDefault();
|
|
3719
|
-
if (farther(state.heldAt, point, distance)) self.handleStart();
|
|
3720
|
-
}
|
|
3721
|
-
detach() {
|
|
3722
|
-
const state = holds.get(this);
|
|
3723
|
-
if (state) clearTimeout(state.timer);
|
|
3724
|
-
holds.delete(this);
|
|
3725
|
-
super.detach();
|
|
3726
|
-
}
|
|
3727
|
-
};
|
|
3728
|
-
/** A TouchSensor that starts a drag on the first move after a still hold (see above). */
|
|
3729
|
-
const HoldThenMoveTouchSensor = HoldThenMoveTouchSensorImpl;
|
|
3730
|
-
/**
|
|
3731
|
-
* dnd-kit's PointerSensor for a mouse and a pen only: a finger's pointerdown is
|
|
3732
|
-
* left to `HoldThenMoveTouchSensor`.
|
|
3733
|
-
*/
|
|
3734
|
-
var MouseOnlyPointerSensor = class extends PointerSensor {
|
|
3735
|
-
static activators = [{
|
|
3736
|
-
eventName: "onPointerDown",
|
|
3737
|
-
handler: (event, options) => event.nativeEvent.pointerType === "touch" ? false : PointerSensor.activators[0].handler(event, options)
|
|
3738
|
-
}];
|
|
3739
|
-
};
|
|
3740
|
-
/**
|
|
3741
|
-
* `useSensor(HoldThenMoveTouchSensor, …)` with the activation the sensor relies
|
|
3742
|
-
* on: no constraint dnd-kit would act on by itself, so only a move after the
|
|
3743
|
-
* hold starts the drag.
|
|
3744
|
-
*/
|
|
3745
|
-
function useHoldThenMoveTouchSensor({ holdMs = 500, distance = 10, tolerance = 10 } = {}) {
|
|
3746
|
-
const options = useMemo(() => ({
|
|
3747
|
-
holdMs,
|
|
3748
|
-
distance,
|
|
3749
|
-
tolerance,
|
|
3750
|
-
activationConstraint: { distance: Number.POSITIVE_INFINITY }
|
|
3751
|
-
}), [
|
|
3752
|
-
holdMs,
|
|
3753
|
-
distance,
|
|
3754
|
-
tolerance
|
|
3755
|
-
]);
|
|
3756
|
-
return useSensor(HoldThenMoveTouchSensor, options);
|
|
3757
|
-
}
|
|
3758
|
-
/** The mouse / pen sensor that goes with it: picks up after `distance` px. */
|
|
3759
|
-
function useMouseOnlyPointerSensor(distance) {
|
|
3760
|
-
const options = useMemo(() => ({ activationConstraint: { distance } }), [distance]);
|
|
3761
|
-
return useSensor(MouseOnlyPointerSensor, options);
|
|
3762
|
-
}
|
|
3763
4946
|
//#endregion
|
|
3764
4947
|
//#region src/components/inbox-item/inbox-item.recipe.ts
|
|
3765
4948
|
const inboxItem = tv$1({
|
|
@@ -3900,4 +5083,4 @@ const inboxItem = tv$1({
|
|
|
3900
5083
|
}]
|
|
3901
5084
|
});
|
|
3902
5085
|
//#endregion
|
|
3903
|
-
export { MessageReactionPickerStatic as $,
|
|
5086
|
+
export { MessageReactionPickerStatic as $, DropdownMenuRadioGroup as $n, DialogSheetReset as $r, PreviewCardContent as $t, messageEmbed as A, PopoverPopup as An, Badge as Ar, MessageItemEmbedded as At, MessageReferenceIndicator as B, MobileNavRoot as Bn, DialogFooterNote as Br, AvatarFallback as Bt, MessageQuotedPreviewThread as C, PopoverArrow as Cn, CheckboxIndicator as Cr, MessageItemSenderButton as Ct, MessageEmbedContent as D, PopoverContent as Dn, TooltipPortal as Dr, MessageItemBody as Dt, MessageEmbed as E, PopoverClose as En, TooltipContent as Er, MessageItemAttachments as Et, MessageMultiSelectCheckbox as F, PopoverViewport as Fn, DialogBody as Fr, MessageItemGutter as Ft, MessageReferenceUnavailable as G, DropdownMenuFooter as Gn, DialogSheetBack as Gr, measureContent as Gt, MessageReferencePending as H, DropdownMenuCheckboxItem as Hn, DialogNav as Hr, AvatarGroupCount as Ht, MessageMultiSelectToolbar as I, useHoldThenMoveTouchSensor as In, DialogClose as Ir, MessageItem as It, MessageReactionChip as J, DropdownMenuItemLabel as Jn, DialogTrigger as Jr, readNow as Jt, inlineChip as K, DropdownMenuItem as Kn, DialogSurface as Kr, measureElement as Kt, MessageMultiSelectToolbarActions as L, useMouseOnlyPointerSensor as Ln, DialogContent as Lr, searchHit as Lt, MessageTextSelectionToolbarButton as M, PopoverSeparator as Mn, DialogSheetContent as Mr, MessageItemSearchMatch as Mt, MessageTextSelectionToolbarSeparator as N, PopoverTitle as Nn, useMobileViewport as Nr, MessageItemAvatarSlot as Nt, MessageEmbedFooter as O, PopoverDescription as On, TooltipProvider as Or, MessageItemContent as Ot, MessageMultiSelect as P, PopoverTrigger as Pn, Dialog$1 as Pr, MessageItemContinuationTime as Pt, MessageReactionPickerContent as Q, DropdownMenuPortal as Qn, textLinkVariants as Qr, PreviewCardBody as Qt, MessageReferenceChip as R, MobileNavItem as Rn, DialogDescription as Rr, Avatar$1 as Rt, MessageQuotedPreviewText as S, PopoverActions as Sn, Checkbox$1 as Sr, MessageItemSenderBadges as St, MessageQuotedPreviewUnavailable as T, PopoverBody as Tn, Tooltip$1 as Tr, MessageItemTime as Tt, MessageReferenceSuffix as U, DropdownMenuCheckboxItemIndicator as Un, DialogOverlay as Ur, AvatarImage as Ut, MessageReferenceLabel as V, DropdownMenu as Vn, DialogHeader as Vr, AvatarGroup as Vt, MessageReferenceText as W, DropdownMenuContent as Wn, DialogPortal as Wr, useOverflowTitle as Wt, MessageReactionErrorIndicator as X, DropdownMenuLinkItem as Xn, panelToggleAction as Xr, PreviewCardActions as Xt, MessageReactionCount as Y, DropdownMenuLabel as Yn, PanelAction as Yr, PreviewCard$1 as Yt, MessageReactionGlyph as Z, DropdownMenuPopup as Zn, TextLink as Zr, PreviewCardArrow as Zt, MessageQuotedPreviewAuthorName as _, MobileNavServerPopoverRow as _n, ScrollAreaContent as _r, MessageItemDelegatedByName as _t, MessageListJumpPillGroup as a, useParentOverlayKind as ai, PreviewCardPopup as an, DropdownMenuSubmenuTrigger as ar, MessageReactionTooltipNameText as at, MessageQuotedPreviewHeader as b, MobileNavServerPopoverRowLabel as bn, ScrollAreaThumb as br, MessageItemMeta as bt, MessageListHistoryLimited as c, useTheme as ci, PreviewCardTitle as cn, KbdGroup as cr, MessageItemBadge as ct, MessageQuotedPreview as d, stackedActions as di, MobileNavServerPopover as dn, PanelFooter as dr, MessageItemSavedIndicator as dt, Button$1 as ei, PreviewCardDescription as en, DropdownMenuRadioItem as er, MessageReactionQuickButton as et, MessageQuotedPreviewAttachment as f, useStackedActions as fi, MobileNavServerPopoverContent as fn, PanelHeaderRoot as fr, MessageItemToolbar as ft, MessageQuotedPreviewAuthorMeta as g, decisionWidth as gi, MobileNavServerPopoverList as gn, ScrollArea$1 as gr, MessageItemDelegatedByButton as gt, MessageQuotedPreviewAuthorDelegatedBy as h, headEdges as hi, MobileNavServerPopoverGroup as hn, PanelSection as hr, MessageItemDelegatedBy as ht, MessageListJumpPillDismiss as i, OverlayLayerProvider as ii, PreviewCardMeta as in, DropdownMenuSubmenu as ir, MessageReactionTooltipName as it, MessageTextSelectionToolbar as j, PopoverPortal as jn, DialogCommit as jr, MessageItemFooter as jt, MessageEmbedHeader as k, PopoverHeader as kn, TooltipTrigger as kr, MessageItemEdited as kt, MessageListNotice as l, ThemeStateContext as li, PreviewCardTrigger as ln, Panel as lr, MessageItemBadgeSeparator as lt, MessageQuotedPreviewAuthor as m, headEdgePull as mi, MobileNavServerPopoverDestinations as mn, PanelScrollViewport as mr, MessageItemDelegatedAvatar as mt, MessageListJumpControls as n, Spinner as ni, PreviewCardFooter as nn, DropdownMenuSeparator as nr, MessageReactionTooltipContent as nt, MessageListDateDivider as o, withOverlayLayer as oi, PreviewCardPortal as on, DropdownMenuTrigger as or, MessageReactionTooltipNames as ot, MessageQuotedPreviewAttachments as p, controlIconInset as pi, MobileNavServerPopoverDestination as pn, PanelScrollContent as pr, MessageItemToolbarButton as pt, MessageReactionAddButton as q, DropdownMenuItemCount as qn, DialogTitle as qr, observeTruncation as qt, MessageListJumpPill as r, defaultNativeButton as ri, PreviewCardHeader as rn, DropdownMenuShortcut as rr, MessageReactionTooltipMeta as rt, MessageListEndFade as s, useThemeScopeProps as si, PreviewCardSeparator as sn, Kbd as sr, MessageReactionTooltipSeparator as st, inboxItem as t, buttonVariants as ti, PreviewCardEyebrow as tn, DropdownMenuRadioItemIndicator as tr, MessageReactionQuickRow as tt, MessageList as u, tv$1 as ui, MobileNavServerItem as un, PanelBody as ur, MessageItemReadReceipt as ut, MessageQuotedPreviewChannel as v, MobileNavServerPopoverRowBody as vn, ScrollAreaCorner as vr, MessageItemHeader as vt, MessageQuotedPreviewTimestamp as w, PopoverBackdrop as wn, PRESENCE_COLOR as wr, MessageItemSenderGroup as wt, MessageQuotedPreviewLoading as x, Popover$1 as xn, ScrollAreaViewport as xr, MessageItemSender as xt, MessageQuotedPreviewContent as y, MobileNavServerPopoverRowDescription as yn, ScrollAreaScrollbar as yr, MessageItemInlineStatus as yt, MessageReferenceIcon as z, MobileNavLabel as zn, DialogFooter as zr, AvatarBadge as zt };
|