@burtson-labs/ui 0.8.1 → 0.9.1

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/components/composer.d.ts +12 -2
  3. package/dist/components/composer.js +2 -2
  4. package/dist/components/composer.js.map +1 -1
  5. package/dist/components/context-menu.d.ts +30 -0
  6. package/dist/components/context-menu.js +131 -0
  7. package/dist/components/context-menu.js.map +1 -0
  8. package/dist/components/dropdown-menu.js +5 -6
  9. package/dist/components/dropdown-menu.js.map +1 -1
  10. package/dist/components/editor-tabs.d.ts +47 -0
  11. package/dist/components/editor-tabs.js +221 -0
  12. package/dist/components/editor-tabs.js.map +1 -0
  13. package/dist/components/menubar.d.ts +30 -0
  14. package/dist/components/menubar.js +140 -0
  15. package/dist/components/menubar.js.map +1 -0
  16. package/dist/components/onboarding-checklist.d.ts +26 -0
  17. package/dist/components/onboarding-checklist.js +106 -0
  18. package/dist/components/onboarding-checklist.js.map +1 -0
  19. package/dist/components/popover.d.ts +2 -0
  20. package/dist/components/popover.js +3 -1
  21. package/dist/components/popover.js.map +1 -1
  22. package/dist/components/resizable.d.ts +33 -0
  23. package/dist/components/resizable.js +54 -0
  24. package/dist/components/resizable.js.map +1 -0
  25. package/dist/components/tour.d.ts +96 -0
  26. package/dist/components/tour.js +367 -0
  27. package/dist/components/tour.js.map +1 -0
  28. package/dist/components/tree-view.d.ts +76 -0
  29. package/dist/components/tree-view.js +286 -0
  30. package/dist/components/tree-view.js.map +1 -0
  31. package/dist/index.d.ts +7 -0
  32. package/dist/index.js +10 -3
  33. package/dist/styles.css +1 -1
  34. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":["Popover","PopoverPrimitive"],"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { Popover as PopoverPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\n/**\n * The floating surface shared by popovers, menus and select lists: raised,\n * with a strong border, and the one place a heavy shadow is allowed.\n */\nexport const surfaceClasses =\n 'z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]';\n\nfunction Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = 'center',\n sideOffset = 6,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'w-72 max-w-[calc(100vw-2rem)] max-h-(--radix-popover-content-available-height) overflow-y-auto origin-(--radix-popover-content-transform-origin) p-4 outline-hidden',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\n"],"mappings":";;;;;;;;;AASA,IAAa,iBACX;AAEF,SAASA,UAAQ,OAA2D;CAC1E,OAAO,oBAACC,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,OAA8D;CACpF,OAAO,oBAACA,QAAiB,SAAlB;EAA0B,aAAU;EAAkB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,cAAc,OAA6D;CAClF,OAAO,oBAACA,QAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,eAAe,EACtB,WACA,QAAQ,UACR,aAAa,GACb,GAAG,SACqD;CACxD,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,oBAACA,QAAiB,SAAlB;EACE,aAAU;EACH;EACK;EACZ,WAAW,GACT,gBACA,uKACA,SACF;EACA,GAAI;CACL,CAAA,EACsB,CAAA;AAE7B"}
1
+ {"version":3,"file":"popover.js","names":["Popover","PopoverPrimitive"],"sources":["../../src/components/popover.tsx"],"sourcesContent":["import { Popover as PopoverPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\n/**\n * The floating surface shared by popovers, menus and select lists: raised,\n * with a strong border, and the one place a heavy shadow is allowed.\n */\nexport const surfaceClasses =\n 'z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]';\n\n/** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */\nexport const menuItemClasses =\n \"relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\";\n\nfunction Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = 'center',\n sideOffset = 6,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'w-72 max-w-[calc(100vw-2rem)] max-h-(--radix-popover-content-available-height) overflow-y-auto origin-(--radix-popover-content-transform-origin) p-4 outline-hidden',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };\n"],"mappings":";;;;;;;;;AASA,IAAa,iBACX;;AAGF,IAAa,kBACX;AAEF,SAASA,UAAQ,OAA2D;CAC1E,OAAO,oBAACC,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,OAA8D;CACpF,OAAO,oBAACA,QAAiB,SAAlB;EAA0B,aAAU;EAAkB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,cAAc,OAA6D;CAClF,OAAO,oBAACA,QAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,eAAe,EACtB,WACA,QAAQ,UACR,aAAa,GACb,GAAG,SACqD;CACxD,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,oBAACA,QAAiB,SAAlB;EACE,aAAU;EACH;EACK;EACZ,WAAW,GACT,gBACA,uKACA,SACF;EACA,GAAI;CACL,CAAA,EACsB,CAAA;AAE7B"}
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { Group, Panel, Separator, type Layout, type LayoutChangedMeta } from 'react-resizable-panels';
3
+ export type ResizableLayout = Layout;
4
+ export interface ResizablePanelGroupProps extends Omit<React.ComponentProps<typeof Group>, 'defaultLayout' | 'onLayoutChange' | 'onLayoutChanged'> {
5
+ /**
6
+ * Controlled sizes: panel id → percentage (0..100). Pass it with
7
+ * onLayoutChange, or onLayoutChanged if you only need the settled value.
8
+ * The group re-applies it whenever it differs from what is on screen.
9
+ */
10
+ layout?: ResizableLayout;
11
+ /** Initial sizes when uncontrolled, e.g. restored from storage. */
12
+ defaultLayout?: ResizableLayout;
13
+ /** Every change, including each pointer move while dragging. */
14
+ onLayoutChange?: (layout: ResizableLayout) => void;
15
+ /** After a drag ends or a key resizes; the one to persist. */
16
+ onLayoutChanged?: (layout: ResizableLayout, meta: LayoutChangedMeta) => void;
17
+ }
18
+ declare function ResizablePanelGroup({ className, layout, defaultLayout, groupRef: groupRefProp, orientation, ...props }: ResizablePanelGroupProps): React.JSX.Element;
19
+ declare function ResizablePanel({ className, ...props }: React.ComponentProps<typeof Panel>): React.JSX.Element;
20
+ export interface ResizeHandleProps extends React.ComponentProps<typeof Separator> {
21
+ /** Show a small grip in the middle of the line. */
22
+ withHandle?: boolean;
23
+ /** What the handle resizes, e.g. "Resize explorer". Required for screen readers. */
24
+ 'aria-label': string;
25
+ }
26
+ /**
27
+ * The separator between two panels. It is a 1px line with an 8px hit area;
28
+ * hover and drag tint it, keyboard focus draws it in the brand colour.
29
+ */
30
+ declare function ResizeHandle({ className, withHandle, ...props }: ResizeHandleProps): React.JSX.Element;
31
+ export { usePanelRef, useGroupRef } from 'react-resizable-panels';
32
+ export type { GroupImperativeHandle as ResizablePanelGroupHandle, PanelImperativeHandle as ResizablePanelHandle, } from 'react-resizable-panels';
33
+ export { ResizablePanelGroup, ResizablePanel, ResizeHandle };
@@ -0,0 +1,54 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import * as React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { Group, Panel, Separator, useGroupRef, usePanelRef } from "react-resizable-panels";
5
+ //#region src/components/resizable.tsx
6
+ var sameLayout = (a, b) => {
7
+ const keys = Object.keys(a);
8
+ return keys.length === Object.keys(b).length && keys.every((k) => b[k] !== void 0 && Math.abs((a[k] ?? 0) - (b[k] ?? 0)) < .01);
9
+ };
10
+ function ResizablePanelGroup({ className, layout, defaultLayout, groupRef: groupRefProp, orientation = "horizontal", ...props }) {
11
+ const groupRef = React.useRef(null);
12
+ React.useImperativeHandle(groupRefProp, () => groupRef.current, []);
13
+ React.useEffect(() => {
14
+ const group = groupRef.current;
15
+ if (!layout || !group) return;
16
+ if (!sameLayout(group.getLayout(), layout)) group.setLayout(layout);
17
+ }, [layout]);
18
+ return /* @__PURE__ */ jsx(Group, {
19
+ "data-slot": "resizable-panel-group",
20
+ "data-orientation": orientation,
21
+ orientation,
22
+ groupRef,
23
+ defaultLayout: layout ?? defaultLayout,
24
+ className: cn("flex h-full w-full data-[orientation=vertical]:flex-col", className),
25
+ ...props
26
+ });
27
+ }
28
+ function ResizablePanel({ className, ...props }) {
29
+ return /* @__PURE__ */ jsx(Panel, {
30
+ "data-slot": "resizable-panel",
31
+ className: cn("min-h-0 min-w-0", className),
32
+ ...props
33
+ });
34
+ }
35
+ /**
36
+ * The separator between two panels. It is a 1px line with an 8px hit area;
37
+ * hover and drag tint it, keyboard focus draws it in the brand colour.
38
+ */
39
+ function ResizeHandle({ className, withHandle, ...props }) {
40
+ return /* @__PURE__ */ jsx(Separator, {
41
+ "data-slot": "resize-handle",
42
+ className: cn("group/handle relative flex shrink-0 items-center justify-center bg-border outline-none", "after:absolute after:content-[\"\"]", "aria-[orientation=vertical]:w-px aria-[orientation=vertical]:after:inset-y-0 aria-[orientation=vertical]:after:-inset-x-1", "aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:after:inset-x-0 aria-[orientation=horizontal]:after:-inset-y-1", "transition-colors data-[separator=hover]:bg-border-strong data-[separator=active]:bg-brand", "focus-visible:outline-none data-[separator=focus]:bg-ring data-[separator=focus]:shadow-[0_0_0_1px_var(--ring)]", "data-[separator=disabled]:opacity-50", className),
43
+ ...props,
44
+ children: withHandle && /* @__PURE__ */ jsx("span", {
45
+ "aria-hidden": true,
46
+ className: "z-10 flex items-center justify-center rounded-xs border bg-surface group-aria-[orientation=vertical]/handle:h-6 group-aria-[orientation=vertical]/handle:w-2 group-aria-[orientation=horizontal]/handle:h-2 group-aria-[orientation=horizontal]/handle:w-6",
47
+ children: /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted-foreground/60 group-aria-[orientation=vertical]/handle:h-3 group-aria-[orientation=vertical]/handle:w-px group-aria-[orientation=horizontal]/handle:h-px group-aria-[orientation=horizontal]/handle:w-3" })
48
+ })
49
+ });
50
+ }
51
+ //#endregion
52
+ export { ResizablePanel, ResizablePanelGroup, ResizeHandle, useGroupRef, usePanelRef };
53
+
54
+ //# sourceMappingURL=resizable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable.js","names":[],"sources":["../../src/components/resizable.tsx"],"sourcesContent":["import * as React from 'react';\nimport {\n Group,\n Panel,\n Separator,\n type GroupImperativeHandle,\n type Layout,\n type LayoutChangedMeta,\n} from 'react-resizable-panels';\n\nimport { cn } from '../lib/utils';\n\n// Resizing is react-resizable-panels: it implements the WAI-ARIA window\n// splitter (arrow keys, Home/End, Enter to collapse), captures the pointer so\n// a drag over an iframe or terminal keeps tracking, and enforces min/max and\n// collapse. This file adds the Burtson look and a controlled `layout` prop.\n\nexport type ResizableLayout = Layout;\n\nexport interface ResizablePanelGroupProps extends Omit<\n React.ComponentProps<typeof Group>,\n 'defaultLayout' | 'onLayoutChange' | 'onLayoutChanged'\n> {\n /**\n * Controlled sizes: panel id → percentage (0..100). Pass it with\n * onLayoutChange, or onLayoutChanged if you only need the settled value.\n * The group re-applies it whenever it differs from what is on screen.\n */\n layout?: ResizableLayout;\n /** Initial sizes when uncontrolled, e.g. restored from storage. */\n defaultLayout?: ResizableLayout;\n /** Every change, including each pointer move while dragging. */\n onLayoutChange?: (layout: ResizableLayout) => void;\n /** After a drag ends or a key resizes; the one to persist. */\n onLayoutChanged?: (layout: ResizableLayout, meta: LayoutChangedMeta) => void;\n}\n\nconst sameLayout = (a: ResizableLayout, b: ResizableLayout) => {\n const keys = Object.keys(a);\n return (\n keys.length === Object.keys(b).length &&\n keys.every((k) => b[k] !== undefined && Math.abs((a[k] ?? 0) - (b[k] ?? 0)) < 0.01)\n );\n};\n\nfunction ResizablePanelGroup({\n className,\n layout,\n defaultLayout,\n groupRef: groupRefProp,\n orientation = 'horizontal',\n ...props\n}: ResizablePanelGroupProps) {\n const groupRef = React.useRef<GroupImperativeHandle | null>(null);\n React.useImperativeHandle(groupRefProp, () => groupRef.current as GroupImperativeHandle, []);\n\n React.useEffect(() => {\n const group = groupRef.current;\n if (!layout || !group) return;\n if (!sameLayout(group.getLayout(), layout)) group.setLayout(layout);\n }, [layout]);\n\n return (\n <Group\n data-slot=\"resizable-panel-group\"\n data-orientation={orientation}\n orientation={orientation}\n groupRef={groupRef}\n defaultLayout={layout ?? defaultLayout}\n className={cn('flex h-full w-full data-[orientation=vertical]:flex-col', className)}\n {...props}\n />\n );\n}\n\nfunction ResizablePanel({ className, ...props }: React.ComponentProps<typeof Panel>) {\n return (\n <Panel data-slot=\"resizable-panel\" className={cn('min-h-0 min-w-0', className)} {...props} />\n );\n}\n\nexport interface ResizeHandleProps extends React.ComponentProps<typeof Separator> {\n /** Show a small grip in the middle of the line. */\n withHandle?: boolean;\n /** What the handle resizes, e.g. \"Resize explorer\". Required for screen readers. */\n 'aria-label': string;\n}\n\n/**\n * The separator between two panels. It is a 1px line with an 8px hit area;\n * hover and drag tint it, keyboard focus draws it in the brand colour.\n */\nfunction ResizeHandle({ className, withHandle, ...props }: ResizeHandleProps) {\n return (\n <Separator\n data-slot=\"resize-handle\"\n className={cn(\n 'group/handle relative flex shrink-0 items-center justify-center bg-border outline-none',\n // Hit area wider than the line, on the axis being dragged.\n 'after:absolute after:content-[\"\"]',\n // The library sets aria-orientation on the line itself: a horizontal\n // group has vertical separators. Nested groups stay independent.\n 'aria-[orientation=vertical]:w-px aria-[orientation=vertical]:after:inset-y-0 aria-[orientation=vertical]:after:-inset-x-1',\n 'aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:after:inset-x-0 aria-[orientation=horizontal]:after:-inset-y-1',\n 'transition-colors data-[separator=hover]:bg-border-strong data-[separator=active]:bg-brand',\n 'focus-visible:outline-none data-[separator=focus]:bg-ring data-[separator=focus]:shadow-[0_0_0_1px_var(--ring)]',\n 'data-[separator=disabled]:opacity-50',\n className,\n )}\n {...props}\n >\n {withHandle && (\n <span\n aria-hidden\n className=\"z-10 flex items-center justify-center rounded-xs border bg-surface group-aria-[orientation=vertical]/handle:h-6 group-aria-[orientation=vertical]/handle:w-2 group-aria-[orientation=horizontal]/handle:h-2 group-aria-[orientation=horizontal]/handle:w-6\"\n >\n <span className=\"rounded-full bg-muted-foreground/60 group-aria-[orientation=vertical]/handle:h-3 group-aria-[orientation=vertical]/handle:w-px group-aria-[orientation=horizontal]/handle:h-px group-aria-[orientation=horizontal]/handle:w-3\" />\n </span>\n )}\n </Separator>\n );\n}\n\nexport { usePanelRef, useGroupRef } from 'react-resizable-panels';\nexport type {\n GroupImperativeHandle as ResizablePanelGroupHandle,\n PanelImperativeHandle as ResizablePanelHandle,\n} from 'react-resizable-panels';\nexport { ResizablePanelGroup, ResizablePanel, ResizeHandle };\n"],"mappings":";;;;;AAqCA,IAAM,cAAc,GAAoB,MAAuB;CAC7D,MAAM,OAAO,OAAO,KAAK,CAAC;CAC1B,OACE,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,UAC/B,KAAK,OAAO,MAAM,EAAE,OAAO,KAAA,KAAa,KAAK,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,EAAE,IAAI,GAAI;AAEtF;AAEA,SAAS,oBAAoB,EAC3B,WACA,QACA,eACA,UAAU,cACV,cAAc,cACd,GAAG,SACwB;CAC3B,MAAM,WAAW,MAAM,OAAqC,IAAI;CAChE,MAAM,oBAAoB,oBAAoB,SAAS,SAAkC,CAAC,CAAC;CAE3F,MAAM,gBAAgB;EACpB,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,UAAU,CAAC,OAAO;EACvB,IAAI,CAAC,WAAW,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,UAAU,MAAM;CACpE,GAAG,CAAC,MAAM,CAAC;CAEX,OACE,oBAAC,OAAD;EACE,aAAU;EACV,oBAAkB;EACL;EACH;EACV,eAAe,UAAU;EACzB,WAAW,GAAG,2DAA2D,SAAS;EAClF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,SAA6C;CACnF,OACE,oBAAC,OAAD;EAAO,aAAU;EAAkB,WAAW,GAAG,mBAAmB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAEhG;;;;;AAaA,SAAS,aAAa,EAAE,WAAW,YAAY,GAAG,SAA4B;CAC5E,OACE,oBAAC,WAAD;EACE,aAAU;EACV,WAAW,GACT,0FAEA,uCAGA,6HACA,mIACA,8FACA,mHACA,wCACA,SACF;EACA,GAAI;EAEH,UAAA,cACC,oBAAC,QAAD;GACE,eAAA;GACA,WAAU;GAEV,UAAA,oBAAC,QAAD,EAAM,WAAU,gOAAiO,CAAA;EAC7O,CAAA;CAEC,CAAA;AAEf"}
@@ -0,0 +1,96 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * A registered target id (see TourAnchor), or a function returning the
4
+ * element. Ids are stable; never target generated class names or nth-child.
5
+ */
6
+ export type TourTarget = string | (() => Element | null);
7
+ /** Marks an element as a tour target. Merges onto its one child. */
8
+ declare function TourAnchor({ id, children }: {
9
+ id: string;
10
+ children: React.ReactElement;
11
+ }): React.JSX.Element;
12
+ /** The same marker as TourAnchor, for elements you render yourself. */
13
+ export declare const tourTarget: (id: string) => {
14
+ 'data-tour-target': string;
15
+ };
16
+ export declare function findTourTarget(target: TourTarget | undefined): Element | null;
17
+ /**
18
+ * Resolves when the target is mounted and visible, or with null after
19
+ * `timeoutMs` or when `signal` aborts. Handles targets that render late
20
+ * (lazy panels, async data) without polling forever.
21
+ */
22
+ export declare function waitForTourTarget(target: TourTarget | undefined, timeoutMs: number, signal: AbortSignal): Promise<Element | null>;
23
+ export interface TourStep {
24
+ id: string;
25
+ /** Omit for a centred step with no anchor. */
26
+ target?: TourTarget;
27
+ title: React.ReactNode;
28
+ content: React.ReactNode;
29
+ side?: 'top' | 'right' | 'bottom' | 'left';
30
+ align?: 'start' | 'center' | 'end';
31
+ /**
32
+ * Reveal what the step points at (open a panel, switch a route) before it
33
+ * shows. Aborted if the person leaves the step first; a rejection shows the
34
+ * step centred rather than stranding the tour.
35
+ */
36
+ prepare?: (signal: AbortSignal) => void | Promise<void>;
37
+ }
38
+ export type TourEndReason = 'complete' | 'skip' | 'close' | 'escape';
39
+ export interface TourLabels {
40
+ next: string;
41
+ back: string;
42
+ skip: string;
43
+ done: string;
44
+ close: string;
45
+ progress: (step: number, total: number) => string;
46
+ }
47
+ export interface TourProps {
48
+ steps: TourStep[];
49
+ open: boolean;
50
+ /** Controlled, zero-based. */
51
+ step: number;
52
+ onStepChange: (step: number) => void;
53
+ /** The tour is over. Store dismissal or completion yourself, keyed by tour id and revision. */
54
+ onEnd: (reason: TourEndReason, step: number) => void;
55
+ /** `center` (default) shows a step whose target never appears in the middle; `skip` moves past it. */
56
+ missingTarget?: 'center' | 'skip';
57
+ /** How long to wait for a late target. Default 2000ms. */
58
+ waitMs?: number;
59
+ /** Dim the page outside the target. Default true. */
60
+ dim?: boolean;
61
+ labels?: Partial<TourLabels>;
62
+ /** Where focus goes if the element that started the tour is gone. */
63
+ fallbackFocus?: () => HTMLElement | null;
64
+ }
65
+ /**
66
+ * A guided sequence of anchored steps. It is non-modal: the page stays
67
+ * usable and outside clicks do not end it. Focus moves into each step,
68
+ * Escape ends the tour (unless the person is typing in a field or editor
69
+ * outside it), and focus returns to where it was when the tour started.
70
+ * The app owns the copy, the eligibility rules and what it persists.
71
+ */
72
+ declare function Tour({ steps, open, step, onStepChange, onEnd, missingTarget, waitMs, dim, labels: labelOverrides, fallbackFocus, }: TourProps): React.JSX.Element | null;
73
+ export interface SpotlightProps {
74
+ target: TourTarget;
75
+ open: boolean;
76
+ onDismiss: (reason: 'dismiss' | 'escape') => void;
77
+ title: React.ReactNode;
78
+ children: React.ReactNode;
79
+ side?: 'top' | 'right' | 'bottom' | 'left';
80
+ align?: 'start' | 'center' | 'end';
81
+ /** Default "Got it". */
82
+ dismissLabel?: string;
83
+ /** An extra button, e.g. "Add a provider". */
84
+ action?: React.ReactNode;
85
+ /** Ring the target. Default true. */
86
+ highlight?: boolean;
87
+ className?: string;
88
+ }
89
+ /**
90
+ * One contextual hint beside a target: a new feature, a setup nudge. It is
91
+ * informational, so it does not take focus or block the page. Escape (unless
92
+ * the person is typing elsewhere) or the dismiss button closes it. If the target is
93
+ * not on screen, nothing renders.
94
+ */
95
+ declare function Spotlight({ target, open, onDismiss, title, children, side, align, dismissLabel, action, highlight, className, }: SpotlightProps): React.JSX.Element | null;
96
+ export { Spotlight, Tour, TourAnchor };
@@ -0,0 +1,367 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Button } from "./button.js";
3
+ import { surfaceClasses } from "./popover.js";
4
+ import { Popover, Slot } from "radix-ui";
5
+ import * as React from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import X from "@burtson-labs/icons/react/x";
8
+ //#region src/components/tour.tsx
9
+ /** Marks an element as a tour target. Merges onto its one child. */
10
+ function TourAnchor({ id, children }) {
11
+ return /* @__PURE__ */ jsx(Slot.Root, {
12
+ "data-tour-target": id,
13
+ children
14
+ });
15
+ }
16
+ /** The same marker as TourAnchor, for elements you render yourself. */
17
+ var tourTarget = (id) => ({ "data-tour-target": id });
18
+ var isVisible = (el) => Boolean(el?.isConnected && el.getClientRects().length > 0);
19
+ function findTourTarget(target) {
20
+ if (!target) return null;
21
+ const el = typeof target === "function" ? target() : Array.from(document.querySelectorAll("[data-tour-target]")).find((node) => node.getAttribute("data-tour-target") === target) ?? null;
22
+ return isVisible(el) ? el : null;
23
+ }
24
+ /**
25
+ * Resolves when the target is mounted and visible, or with null after
26
+ * `timeoutMs` or when `signal` aborts. Handles targets that render late
27
+ * (lazy panels, async data) without polling forever.
28
+ */
29
+ function waitForTourTarget(target, timeoutMs, signal) {
30
+ return new Promise((resolve) => {
31
+ const now = findTourTarget(target);
32
+ if (now || !target || timeoutMs <= 0 || signal.aborted) return resolve(now);
33
+ let done = false;
34
+ const finish = (el) => {
35
+ if (done) return;
36
+ done = true;
37
+ observer.disconnect();
38
+ clearInterval(poll);
39
+ clearTimeout(timer);
40
+ signal.removeEventListener("abort", abort);
41
+ resolve(el);
42
+ };
43
+ const check = () => {
44
+ const el = findTourTarget(target);
45
+ if (el) finish(el);
46
+ };
47
+ const abort = () => finish(null);
48
+ const observer = new MutationObserver(check);
49
+ observer.observe(document.body, {
50
+ childList: true,
51
+ subtree: true,
52
+ attributes: true
53
+ });
54
+ const poll = setInterval(check, 150);
55
+ const timer = setTimeout(() => finish(null), timeoutMs);
56
+ signal.addEventListener("abort", abort);
57
+ });
58
+ }
59
+ /**
60
+ * Escape belongs to whatever the person is typing in (a field, CodeMirror's
61
+ * contenteditable, xterm's hidden textarea), not to the tour.
62
+ */
63
+ var typingElsewhere = (container) => {
64
+ const active = document.activeElement;
65
+ if (!(active instanceof HTMLElement) || container?.contains(active)) return false;
66
+ return active.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);
67
+ };
68
+ var prefersReducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
69
+ /** Follows an element's box across scroll and resize; null until measured. */
70
+ function useRect(el) {
71
+ const [measured, setMeasured] = React.useState(null);
72
+ React.useEffect(() => {
73
+ if (!el) return;
74
+ let frame = 0;
75
+ const update = () => {
76
+ cancelAnimationFrame(frame);
77
+ frame = requestAnimationFrame(() => setMeasured({
78
+ el,
79
+ rect: el.getBoundingClientRect()
80
+ }));
81
+ };
82
+ update();
83
+ const ro = new ResizeObserver(update);
84
+ ro.observe(el);
85
+ window.addEventListener("scroll", update, true);
86
+ window.addEventListener("resize", update);
87
+ return () => {
88
+ cancelAnimationFrame(frame);
89
+ ro.disconnect();
90
+ window.removeEventListener("scroll", update, true);
91
+ window.removeEventListener("resize", update);
92
+ };
93
+ }, [el]);
94
+ return measured && measured.el === el ? measured.rect : null;
95
+ }
96
+ /** A ring around the target, optionally dimming the rest of the page. Never blocks clicks. */
97
+ function TourHighlight({ rect, dim }) {
98
+ const pad = 4;
99
+ return /* @__PURE__ */ jsx("div", {
100
+ "aria-hidden": true,
101
+ "data-slot": "tour-highlight",
102
+ className: cn("pointer-events-none fixed z-40 rounded-md ring-2 ring-brand motion-safe:transition-[top,left,width,height] motion-safe:duration-200", dim && "shadow-[0_0_0_9999px_rgb(10_8_14/0.45)]"),
103
+ style: {
104
+ top: rect.top - pad,
105
+ left: rect.left - pad,
106
+ width: rect.width + 8,
107
+ height: rect.height + 8
108
+ }
109
+ });
110
+ }
111
+ /** A zero-size box a third of the way down the viewport, for steps without a target. */
112
+ var centerRect = () => new DOMRect(window.innerWidth / 2, Math.max(80, window.innerHeight / 3), 0, 0);
113
+ var contentClasses = cn(surfaceClasses, "w-80 max-w-[calc(100vw-2rem)] p-4 text-[13px] outline-none origin-(--radix-popover-content-transform-origin)");
114
+ var defaultLabels = {
115
+ next: "Next",
116
+ back: "Back",
117
+ skip: "Skip tour",
118
+ done: "Done",
119
+ close: "Close tour",
120
+ progress: (step, total) => `${step} of ${total}`
121
+ };
122
+ /**
123
+ * A guided sequence of anchored steps. It is non-modal: the page stays
124
+ * usable and outside clicks do not end it. Focus moves into each step,
125
+ * Escape ends the tour (unless the person is typing in a field or editor
126
+ * outside it), and focus returns to where it was when the tour started.
127
+ * The app owns the copy, the eligibility rules and what it persists.
128
+ */
129
+ function Tour({ steps, open, step, onStepChange, onEnd, missingTarget = "center", waitMs = 2e3, dim = true, labels: labelOverrides, fallbackFocus }) {
130
+ const labels = {
131
+ ...defaultLabels,
132
+ ...labelOverrides
133
+ };
134
+ const current = steps[step];
135
+ const stepKey = current ? `${step}:${current.id}` : "";
136
+ const [resolved, setResolved] = React.useState(null);
137
+ const ready = open && resolved?.key === stepKey;
138
+ const target = ready ? resolved?.el ?? null : null;
139
+ const rect = useRect(target);
140
+ const contentRef = React.useRef(null);
141
+ const [contentEl, setContentEl] = React.useState(null);
142
+ const returnFocus = React.useRef(null);
143
+ const direction = React.useRef(1);
144
+ const titleId = React.useId();
145
+ const bodyId = React.useId();
146
+ React.useLayoutEffect(() => {
147
+ if (!open) return;
148
+ const active = document.activeElement;
149
+ returnFocus.current = active instanceof HTMLElement && active !== document.body ? active : null;
150
+ return () => {
151
+ const back = returnFocus.current;
152
+ (back && isVisible(back) ? back : fallbackFocus?.() ?? null)?.focus();
153
+ };
154
+ }, [open]);
155
+ React.useEffect(() => {
156
+ if (!open || !current) return;
157
+ const controller = new AbortController();
158
+ const { signal } = controller;
159
+ (async () => {
160
+ let prepared = true;
161
+ try {
162
+ await current.prepare?.(signal);
163
+ } catch {
164
+ prepared = false;
165
+ }
166
+ if (signal.aborted) return;
167
+ const el = prepared ? await waitForTourTarget(current.target, waitMs, signal) : null;
168
+ if (signal.aborted) return;
169
+ if (!el && current.target && missingTarget === "skip") {
170
+ const next = step + direction.current;
171
+ if (next >= 0 && next < steps.length) return onStepChange(next);
172
+ }
173
+ if (el) el.scrollIntoView?.({
174
+ block: "nearest",
175
+ behavior: prefersReducedMotion() ? "auto" : "smooth"
176
+ });
177
+ setResolved({
178
+ key: stepKey,
179
+ el
180
+ });
181
+ })();
182
+ return () => controller.abort();
183
+ }, [
184
+ open,
185
+ stepKey,
186
+ waitMs,
187
+ missingTarget
188
+ ]);
189
+ const shown = ready && (!target || rect !== null);
190
+ React.useEffect(() => {
191
+ if (shown) contentEl?.focus();
192
+ }, [
193
+ shown,
194
+ step,
195
+ contentEl
196
+ ]);
197
+ if (!open || !current) return null;
198
+ const last = step === steps.length - 1;
199
+ const go = (to) => {
200
+ direction.current = to < step ? -1 : 1;
201
+ onStepChange(to);
202
+ };
203
+ if (!shown && ready) return null;
204
+ const anchor = rect ?? centerRect();
205
+ return /* @__PURE__ */ jsxs(Fragment, { children: [ready && rect && /* @__PURE__ */ jsx(TourHighlight, {
206
+ rect,
207
+ dim
208
+ }), /* @__PURE__ */ jsxs(Popover.Root, {
209
+ open: shown,
210
+ modal: false,
211
+ children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: { current: { getBoundingClientRect: () => anchor } } }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
212
+ ref: (el) => {
213
+ contentRef.current = el;
214
+ setContentEl(el);
215
+ },
216
+ "data-slot": "tour",
217
+ "data-step": current.id,
218
+ tabIndex: -1,
219
+ "aria-labelledby": titleId,
220
+ "aria-describedby": bodyId,
221
+ side: rect ? current.side ?? "bottom" : "bottom",
222
+ align: rect ? current.align ?? "center" : "center",
223
+ sideOffset: 10,
224
+ collisionPadding: 12,
225
+ className: contentClasses,
226
+ onOpenAutoFocus: (event) => event.preventDefault(),
227
+ onCloseAutoFocus: (event) => event.preventDefault(),
228
+ onInteractOutside: (event) => event.preventDefault(),
229
+ onEscapeKeyDown: (event) => {
230
+ if (typingElsewhere(contentRef.current)) event.preventDefault();
231
+ else onEnd("escape", step);
232
+ },
233
+ children: [
234
+ /* @__PURE__ */ jsxs("div", {
235
+ className: "flex items-start gap-3",
236
+ children: [/* @__PURE__ */ jsxs("div", {
237
+ className: "min-w-0 flex-1",
238
+ children: [/* @__PURE__ */ jsx("p", {
239
+ className: "text-xs text-muted-foreground",
240
+ children: labels.progress(step + 1, steps.length)
241
+ }), /* @__PURE__ */ jsx("h2", {
242
+ id: titleId,
243
+ className: "mt-0.5 text-sm font-semibold text-foreground",
244
+ children: current.title
245
+ })]
246
+ }), /* @__PURE__ */ jsx("button", {
247
+ type: "button",
248
+ "aria-label": labels.close,
249
+ onClick: () => onEnd("close", step),
250
+ className: "-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]",
251
+ children: /* @__PURE__ */ jsx(X, {
252
+ "aria-hidden": true,
253
+ className: "size-4"
254
+ })
255
+ })]
256
+ }),
257
+ /* @__PURE__ */ jsx("div", {
258
+ id: bodyId,
259
+ className: "mt-2 text-muted-foreground",
260
+ children: current.content
261
+ }),
262
+ /* @__PURE__ */ jsxs("div", {
263
+ className: "mt-4 flex items-center gap-2",
264
+ children: [!last && /* @__PURE__ */ jsx(Button, {
265
+ variant: "ghost",
266
+ size: "sm",
267
+ className: "-ms-2",
268
+ onClick: () => onEnd("skip", step),
269
+ children: labels.skip
270
+ }), /* @__PURE__ */ jsxs("div", {
271
+ className: "ms-auto flex gap-2",
272
+ children: [step > 0 && /* @__PURE__ */ jsx(Button, {
273
+ variant: "outline",
274
+ size: "sm",
275
+ onClick: () => go(step - 1),
276
+ children: labels.back
277
+ }), /* @__PURE__ */ jsx(Button, {
278
+ size: "sm",
279
+ onClick: () => last ? onEnd("complete", step) : go(step + 1),
280
+ children: last ? labels.done : labels.next
281
+ })]
282
+ })]
283
+ })
284
+ ]
285
+ }) })]
286
+ })] });
287
+ }
288
+ /**
289
+ * One contextual hint beside a target: a new feature, a setup nudge. It is
290
+ * informational, so it does not take focus or block the page. Escape (unless
291
+ * the person is typing elsewhere) or the dismiss button closes it. If the target is
292
+ * not on screen, nothing renders.
293
+ */
294
+ function Spotlight({ target, open, onDismiss, title, children, side = "bottom", align = "center", dismissLabel = "Got it", action, highlight = true, className }) {
295
+ const key = typeof target === "string" ? target : "fn";
296
+ const [found, setFound] = React.useState(null);
297
+ const el = open && found?.key === key ? found.el : null;
298
+ const rect = useRect(el);
299
+ const titleId = React.useId();
300
+ const contentRef = React.useRef(null);
301
+ React.useEffect(() => {
302
+ if (!open) return;
303
+ const controller = new AbortController();
304
+ waitForTourTarget(target, 2e3, controller.signal).then((found) => {
305
+ if (!controller.signal.aborted) setFound({
306
+ key,
307
+ el: found
308
+ });
309
+ });
310
+ return () => controller.abort();
311
+ }, [open, key]);
312
+ if (!open || !el || !rect) return null;
313
+ return /* @__PURE__ */ jsxs(Fragment, { children: [highlight && /* @__PURE__ */ jsx(TourHighlight, {
314
+ rect,
315
+ dim: false
316
+ }), /* @__PURE__ */ jsxs(Popover.Root, {
317
+ open: true,
318
+ modal: false,
319
+ children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: { current: { getBoundingClientRect: () => rect } } }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
320
+ ref: contentRef,
321
+ "data-slot": "spotlight",
322
+ role: "note",
323
+ "aria-labelledby": titleId,
324
+ side,
325
+ align,
326
+ sideOffset: 10,
327
+ collisionPadding: 12,
328
+ className: cn(contentClasses, "w-72", className),
329
+ onOpenAutoFocus: (event) => event.preventDefault(),
330
+ onCloseAutoFocus: (event) => event.preventDefault(),
331
+ onInteractOutside: (event) => event.preventDefault(),
332
+ onEscapeKeyDown: (event) => {
333
+ if (typingElsewhere(contentRef.current)) event.preventDefault();
334
+ else onDismiss("escape");
335
+ },
336
+ children: [
337
+ /* @__PURE__ */ jsxs("span", {
338
+ className: "sr-only",
339
+ "aria-live": "polite",
340
+ children: ["Tip: ", title]
341
+ }),
342
+ /* @__PURE__ */ jsx("p", {
343
+ id: titleId,
344
+ className: "text-sm font-semibold text-foreground",
345
+ children: title
346
+ }),
347
+ /* @__PURE__ */ jsx("div", {
348
+ className: "mt-1.5 text-muted-foreground",
349
+ children
350
+ }),
351
+ /* @__PURE__ */ jsxs("div", {
352
+ className: "mt-3 flex justify-end gap-2",
353
+ children: [action, /* @__PURE__ */ jsx(Button, {
354
+ size: "sm",
355
+ variant: action ? "ghost" : "default",
356
+ onClick: () => onDismiss("dismiss"),
357
+ children: dismissLabel
358
+ })]
359
+ })
360
+ ]
361
+ }) })]
362
+ })] });
363
+ }
364
+ //#endregion
365
+ export { Spotlight, Tour, TourAnchor, findTourTarget, tourTarget, waitForTourTarget };
366
+
367
+ //# sourceMappingURL=tour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tour.js","names":["_Fragment","PopoverPrimitive"],"sources":["../../src/components/tour.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport { Popover as PopoverPrimitive, Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { surfaceClasses } from './popover';\n\n/**\n * A registered target id (see TourAnchor), or a function returning the\n * element. Ids are stable; never target generated class names or nth-child.\n */\nexport type TourTarget = string | (() => Element | null);\n\n/** Marks an element as a tour target. Merges onto its one child. */\nfunction TourAnchor({ id, children }: { id: string; children: React.ReactElement }) {\n return <Slot.Root data-tour-target={id}>{children}</Slot.Root>;\n}\n\n/** The same marker as TourAnchor, for elements you render yourself. */\nexport const tourTarget = (id: string) => ({ 'data-tour-target': id });\n\nconst isVisible = (el: Element | null): el is Element =>\n Boolean(el?.isConnected && el.getClientRects().length > 0);\n\nexport function findTourTarget(target: TourTarget | undefined): Element | null {\n if (!target) return null;\n const el =\n typeof target === 'function'\n ? target()\n : (Array.from(document.querySelectorAll('[data-tour-target]')).find(\n (node) => node.getAttribute('data-tour-target') === target,\n ) ?? null);\n return isVisible(el) ? el : null;\n}\n\n/**\n * Resolves when the target is mounted and visible, or with null after\n * `timeoutMs` or when `signal` aborts. Handles targets that render late\n * (lazy panels, async data) without polling forever.\n */\nexport function waitForTourTarget(\n target: TourTarget | undefined,\n timeoutMs: number,\n signal: AbortSignal,\n): Promise<Element | null> {\n return new Promise((resolve) => {\n const now = findTourTarget(target);\n if (now || !target || timeoutMs <= 0 || signal.aborted) return resolve(now);\n let done = false;\n const finish = (el: Element | null) => {\n if (done) return;\n done = true;\n observer.disconnect();\n clearInterval(poll);\n clearTimeout(timer);\n signal.removeEventListener('abort', abort);\n resolve(el);\n };\n const check = () => {\n const el = findTourTarget(target);\n if (el) finish(el);\n };\n const abort = () => finish(null);\n const observer = new MutationObserver(check);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true });\n // Visibility can change through CSS alone, which no mutation reports.\n const poll = setInterval(check, 150);\n const timer = setTimeout(() => finish(null), timeoutMs);\n signal.addEventListener('abort', abort);\n });\n}\n\n/**\n * Escape belongs to whatever the person is typing in (a field, CodeMirror's\n * contenteditable, xterm's hidden textarea), not to the tour.\n */\nconst typingElsewhere = (container: HTMLElement | null) => {\n const active = document.activeElement;\n if (!(active instanceof HTMLElement) || container?.contains(active)) return false;\n return active.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);\n};\n\nconst prefersReducedMotion = () =>\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n/** Follows an element's box across scroll and resize; null until measured. */\nfunction useRect(el: Element | null) {\n const [measured, setMeasured] = React.useState<{ el: Element; rect: DOMRect } | null>(null);\n React.useEffect(() => {\n if (!el) return;\n let frame = 0;\n const update = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => setMeasured({ el, rect: el.getBoundingClientRect() }));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n window.addEventListener('scroll', update, true);\n window.addEventListener('resize', update);\n return () => {\n cancelAnimationFrame(frame);\n ro.disconnect();\n window.removeEventListener('scroll', update, true);\n window.removeEventListener('resize', update);\n };\n }, [el]);\n return measured && measured.el === el ? measured.rect : null;\n}\n\n/** A ring around the target, optionally dimming the rest of the page. Never blocks clicks. */\nfunction TourHighlight({ rect, dim }: { rect: DOMRect; dim: boolean }) {\n const pad = 4;\n return (\n <div\n aria-hidden\n data-slot=\"tour-highlight\"\n className={cn(\n 'pointer-events-none fixed z-40 rounded-md ring-2 ring-brand motion-safe:transition-[top,left,width,height] motion-safe:duration-200',\n dim && 'shadow-[0_0_0_9999px_rgb(10_8_14/0.45)]',\n )}\n style={{\n top: rect.top - pad,\n left: rect.left - pad,\n width: rect.width + pad * 2,\n height: rect.height + pad * 2,\n }}\n />\n );\n}\n\n/** A zero-size box a third of the way down the viewport, for steps without a target. */\nconst centerRect = () =>\n new DOMRect(window.innerWidth / 2, Math.max(80, window.innerHeight / 3), 0, 0);\n\nconst contentClasses = cn(\n surfaceClasses,\n 'w-80 max-w-[calc(100vw-2rem)] p-4 text-[13px] outline-none origin-(--radix-popover-content-transform-origin)',\n);\n\nexport interface TourStep {\n id: string;\n /** Omit for a centred step with no anchor. */\n target?: TourTarget;\n title: React.ReactNode;\n content: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /**\n * Reveal what the step points at (open a panel, switch a route) before it\n * shows. Aborted if the person leaves the step first; a rejection shows the\n * step centred rather than stranding the tour.\n */\n prepare?: (signal: AbortSignal) => void | Promise<void>;\n}\n\nexport type TourEndReason = 'complete' | 'skip' | 'close' | 'escape';\n\nexport interface TourLabels {\n next: string;\n back: string;\n skip: string;\n done: string;\n close: string;\n progress: (step: number, total: number) => string;\n}\n\nconst defaultLabels: TourLabels = {\n next: 'Next',\n back: 'Back',\n skip: 'Skip tour',\n done: 'Done',\n close: 'Close tour',\n progress: (step, total) => `${step} of ${total}`,\n};\n\nexport interface TourProps {\n steps: TourStep[];\n open: boolean;\n /** Controlled, zero-based. */\n step: number;\n onStepChange: (step: number) => void;\n /** The tour is over. Store dismissal or completion yourself, keyed by tour id and revision. */\n onEnd: (reason: TourEndReason, step: number) => void;\n /** `center` (default) shows a step whose target never appears in the middle; `skip` moves past it. */\n missingTarget?: 'center' | 'skip';\n /** How long to wait for a late target. Default 2000ms. */\n waitMs?: number;\n /** Dim the page outside the target. Default true. */\n dim?: boolean;\n labels?: Partial<TourLabels>;\n /** Where focus goes if the element that started the tour is gone. */\n fallbackFocus?: () => HTMLElement | null;\n}\n\n/**\n * A guided sequence of anchored steps. It is non-modal: the page stays\n * usable and outside clicks do not end it. Focus moves into each step,\n * Escape ends the tour (unless the person is typing in a field or editor\n * outside it), and focus returns to where it was when the tour started.\n * The app owns the copy, the eligibility rules and what it persists.\n */\nfunction Tour({\n steps,\n open,\n step,\n onStepChange,\n onEnd,\n missingTarget = 'center',\n waitMs = 2000,\n dim = true,\n labels: labelOverrides,\n fallbackFocus,\n}: TourProps) {\n const labels = { ...defaultLabels, ...labelOverrides };\n const current = steps[step];\n // The resolved step: which step it is for, and its target (null: centred).\n const stepKey = current ? `${step}:${current.id}` : '';\n const [resolved, setResolved] = React.useState<{ key: string; el: Element | null } | null>(null);\n const ready = open && resolved?.key === stepKey;\n const target = ready ? (resolved?.el ?? null) : null;\n const rect = useRect(target);\n const contentRef = React.useRef<HTMLDivElement>(null);\n // Radix mounts the content after the popper measures, so focus follows the node.\n const [contentEl, setContentEl] = React.useState<HTMLDivElement | null>(null);\n const returnFocus = React.useRef<HTMLElement | null>(null);\n const direction = React.useRef(1);\n const titleId = React.useId();\n const bodyId = React.useId();\n\n // Remember who started the tour; hand focus back when it ends.\n React.useLayoutEffect(() => {\n if (!open) return;\n const active = document.activeElement;\n returnFocus.current = active instanceof HTMLElement && active !== document.body ? active : null;\n return () => {\n const back = returnFocus.current;\n const el = back && isVisible(back) ? back : (fallbackFocus?.() ?? null);\n el?.focus();\n };\n // fallbackFocus is read at close time; re-running on its identity would steal focus.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n React.useEffect(() => {\n if (!open || !current) return;\n const controller = new AbortController();\n const { signal } = controller;\n void (async () => {\n let prepared = true;\n try {\n await current.prepare?.(signal);\n } catch {\n prepared = false;\n }\n if (signal.aborted) return;\n const el = prepared ? await waitForTourTarget(current.target, waitMs, signal) : null;\n if (signal.aborted) return;\n if (!el && current.target && missingTarget === 'skip') {\n const next = step + direction.current;\n if (next >= 0 && next < steps.length) return onStepChange(next);\n }\n if (el)\n el.scrollIntoView?.({\n block: 'nearest',\n behavior: prefersReducedMotion() ? 'auto' : 'smooth',\n });\n setResolved({ key: stepKey, el });\n })();\n return () => controller.abort();\n // steps/onStepChange identity changes should not restart a step in progress.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, stepKey, waitMs, missingTarget]);\n\n // Shown once resolved and, for an anchored step, measured.\n const shown = ready && (!target || rect !== null);\n React.useEffect(() => {\n if (shown) contentEl?.focus();\n }, [shown, step, contentEl]);\n\n if (!open || !current) return null;\n const last = step === steps.length - 1;\n const go = (to: number) => {\n direction.current = to < step ? -1 : 1;\n onStepChange(to);\n };\n // Wait one frame for the target's box rather than flashing the step centred.\n if (!shown && ready) return null;\n const anchor = rect ?? centerRect();\n\n return (\n <>\n {ready && rect && <TourHighlight rect={rect} dim={dim} />}\n <PopoverPrimitive.Root open={shown} modal={false}>\n <PopoverPrimitive.Anchor\n virtualRef={{ current: { getBoundingClientRect: () => anchor } }}\n />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={(el) => {\n contentRef.current = el;\n setContentEl(el);\n }}\n data-slot=\"tour\"\n data-step={current.id}\n tabIndex={-1}\n aria-labelledby={titleId}\n aria-describedby={bodyId}\n side={rect ? (current.side ?? 'bottom') : 'bottom'}\n align={rect ? (current.align ?? 'center') : 'center'}\n sideOffset={10}\n collisionPadding={12}\n className={contentClasses}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onEnd('escape', step);\n }}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-xs text-muted-foreground\">\n {labels.progress(step + 1, steps.length)}\n </p>\n <h2 id={titleId} className=\"mt-0.5 text-sm font-semibold text-foreground\">\n {current.title}\n </h2>\n </div>\n <button\n type=\"button\"\n aria-label={labels.close}\n onClick={() => onEnd('close', step)}\n className=\"-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]\"\n >\n <X aria-hidden className=\"size-4\" />\n </button>\n </div>\n <div id={bodyId} className=\"mt-2 text-muted-foreground\">\n {current.content}\n </div>\n <div className=\"mt-4 flex items-center gap-2\">\n {!last && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ms-2\"\n onClick={() => onEnd('skip', step)}\n >\n {labels.skip}\n </Button>\n )}\n <div className=\"ms-auto flex gap-2\">\n {step > 0 && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => go(step - 1)}>\n {labels.back}\n </Button>\n )}\n <Button size=\"sm\" onClick={() => (last ? onEnd('complete', step) : go(step + 1))}>\n {last ? labels.done : labels.next}\n </Button>\n </div>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport interface SpotlightProps {\n target: TourTarget;\n open: boolean;\n onDismiss: (reason: 'dismiss' | 'escape') => void;\n title: React.ReactNode;\n children: React.ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /** Default \"Got it\". */\n dismissLabel?: string;\n /** An extra button, e.g. \"Add a provider\". */\n action?: React.ReactNode;\n /** Ring the target. Default true. */\n highlight?: boolean;\n className?: string;\n}\n\n/**\n * One contextual hint beside a target: a new feature, a setup nudge. It is\n * informational, so it does not take focus or block the page. Escape (unless\n * the person is typing elsewhere) or the dismiss button closes it. If the target is\n * not on screen, nothing renders.\n */\nfunction Spotlight({\n target,\n open,\n onDismiss,\n title,\n children,\n side = 'bottom',\n align = 'center',\n dismissLabel = 'Got it',\n action,\n highlight = true,\n className,\n}: SpotlightProps) {\n const key = typeof target === 'string' ? target : 'fn';\n const [found, setFound] = React.useState<{ key: string; el: Element | null } | null>(null);\n const el = open && found?.key === key ? found.el : null;\n const rect = useRect(el);\n const titleId = React.useId();\n const contentRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const controller = new AbortController();\n void waitForTourTarget(target, 2000, controller.signal).then((found) => {\n if (!controller.signal.aborted) setFound({ key, el: found });\n });\n return () => controller.abort();\n // A new function identity for `target` should not re-run the lookup.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, key]);\n\n if (!open || !el || !rect) return null;\n return (\n <>\n {highlight && <TourHighlight rect={rect} dim={false} />}\n <PopoverPrimitive.Root open modal={false}>\n <PopoverPrimitive.Anchor virtualRef={{ current: { getBoundingClientRect: () => rect } }} />\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={contentRef}\n data-slot=\"spotlight\"\n role=\"note\"\n aria-labelledby={titleId}\n side={side}\n align={align}\n sideOffset={10}\n collisionPadding={12}\n className={cn(contentClasses, 'w-72', className)}\n onOpenAutoFocus={(event) => event.preventDefault()}\n onCloseAutoFocus={(event) => event.preventDefault()}\n onInteractOutside={(event) => event.preventDefault()}\n onEscapeKeyDown={(event) => {\n if (typingElsewhere(contentRef.current)) event.preventDefault();\n else onDismiss('escape');\n }}\n >\n <span className=\"sr-only\" aria-live=\"polite\">\n Tip: {title}\n </span>\n <p id={titleId} className=\"text-sm font-semibold text-foreground\">\n {title}\n </p>\n <div className=\"mt-1.5 text-muted-foreground\">{children}</div>\n <div className=\"mt-3 flex justify-end gap-2\">\n {action}\n <Button\n size=\"sm\"\n variant={action ? 'ghost' : 'default'}\n onClick={() => onDismiss('dismiss')}\n >\n {dismissLabel}\n </Button>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </>\n );\n}\n\nexport { Spotlight, Tour, TourAnchor };\n"],"mappings":"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"}