@almadar/ui 5.13.2 → 5.14.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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Coachmark Molecule
3
+ *
4
+ * A controlled, externally-anchored popover for onboarding hints. Unlike
5
+ * Popover (uncontrolled, wraps its own trigger), a Coachmark is driven by
6
+ * `open` and points at an element it does not own — a tab button, the persona
7
+ * menu, a canvas node — resolved from a ref, a CSS selector, or a DOMRect.
8
+ * Portals to document.body so it escapes the canvas/preview transform contexts.
9
+ */
10
+ import React from "react";
11
+ export type CoachmarkPlacement = "top" | "bottom" | "left" | "right";
12
+ export type CoachmarkAnchor = React.RefObject<HTMLElement | null> | string | DOMRect;
13
+ export interface CoachmarkProps {
14
+ /** Controlled visibility. */
15
+ open: boolean;
16
+ /** The element to point at: a ref, a CSS selector, or a DOMRect. */
17
+ anchor: CoachmarkAnchor;
18
+ /** Side of the anchor to render on. @default 'bottom' */
19
+ placement?: CoachmarkPlacement;
20
+ title?: string;
21
+ children: React.ReactNode;
22
+ /** Close (X) handler — always rendered. */
23
+ onDismiss: () => void;
24
+ /** Optional filled primary action (e.g. Next / Got it). */
25
+ onPrimary?: () => void;
26
+ primaryLabel?: string;
27
+ /** Optional ghost secondary action (e.g. Skip). */
28
+ onSecondary?: () => void;
29
+ secondaryLabel?: string;
30
+ /** Render a pulsing beacon dot over the anchor (keystone hints). */
31
+ showBeacon?: boolean;
32
+ className?: string;
33
+ }
34
+ /**
35
+ * Tracks the live bounding rect of an anchor while `active`. Re-reads on
36
+ * scroll/resize, and polls a few frames so a selector that mounts just after
37
+ * activation (a freshly-revealed tab) still resolves.
38
+ */
39
+ export declare function useAnchorRect(anchor: CoachmarkAnchor, active: boolean): DOMRect | null;
40
+ export declare const Coachmark: React.FC<CoachmarkProps>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OnboardingSpotlight Molecule
3
+ *
4
+ * Optional first-run welcome: a dimmed backdrop with a cut-out over the current
5
+ * step's anchor, plus a stepped Coachmark bubble (Next / Skip). The backdrop is
6
+ * four dim rectangles around the anchor rect so the highlighted element stays
7
+ * lit and clickable-looking; the bubble and step dots reuse Coachmark.
8
+ */
9
+ import React from "react";
10
+ import { type CoachmarkAnchor, type CoachmarkPlacement } from "./Coachmark";
11
+ export interface SpotlightStep {
12
+ anchor: CoachmarkAnchor;
13
+ placement?: CoachmarkPlacement;
14
+ title: string;
15
+ body: string;
16
+ }
17
+ export interface OnboardingSpotlightProps {
18
+ steps: SpotlightStep[];
19
+ /** Index of the active step. Out-of-range hides the spotlight. */
20
+ stepIndex: number;
21
+ onNext: () => void;
22
+ onSkip: () => void;
23
+ onFinish: () => void;
24
+ /** Padding around the cut-out, in px. @default 6 */
25
+ cutoutPadding?: number;
26
+ }
27
+ export declare const OnboardingSpotlight: React.FC<OnboardingSpotlightProps>;
@@ -14,7 +14,7 @@ export declare const CLINIC_SCHEMA: OrbitalSchema;
14
14
  /**
15
15
  * Task manager schema: 3 orbitals for a richer multi-module story.
16
16
  * - TaskOrbital (std-browse): task list
17
- * - TimerOrbital (std-timer): countdown timer
17
+ * - FocusTimer (std-list): focus session list
18
18
  * - ArchiveOrbital (std-confirmation): archive confirmation
19
19
  */
20
20
  export declare const TASK_SCHEMA: OrbitalSchema;
@@ -20,6 +20,8 @@ export { Menu, type MenuProps, type MenuItem } from './Menu';
20
20
  export { Modal, type ModalProps, type ModalSize } from './Modal';
21
21
  export { Pagination, type PaginationProps } from './Pagination';
22
22
  export { Popover, type PopoverProps } from './Popover';
23
+ export { Coachmark, useAnchorRect, type CoachmarkProps, type CoachmarkPlacement, type CoachmarkAnchor, } from './Coachmark';
24
+ export { OnboardingSpotlight, type OnboardingSpotlightProps, type SpotlightStep, } from './OnboardingSpotlight';
23
25
  export { RelationSelect, type RelationSelectProps, type RelationOption } from './RelationSelect';
24
26
  export { SearchInput, type SearchInputProps } from './SearchInput';
25
27
  export { SidePanel, type SidePanelProps } from './SidePanel';
@@ -19,7 +19,7 @@ export interface CodeBlockProps {
19
19
  showLanguageBadge?: boolean;
20
20
  /** Maximum height before scrolling */
21
21
  maxHeight?: string;
22
- /** Enable JSON-style code folding (default: true for json/orb) */
22
+ /** Enable brace-based code folding of multi-line `{}`/`[]` blocks (default: true). */
23
23
  foldable?: boolean;
24
24
  /** Additional CSS classes */
25
25
  className?: string;
@@ -10,9 +10,9 @@ var LucideIcons2 = require('lucide-react');
10
10
  var PhosphorIcons = require('@phosphor-icons/react');
11
11
  var TablerIcons = require('@tabler/icons-react');
12
12
  var FaIcons = require('react-icons/fa');
13
+ var reactDom = require('react-dom');
13
14
  var evaluator = require('@almadar/evaluator');
14
15
  var context = require('@almadar/ui/context');
15
- var reactDom = require('react-dom');
16
16
  var reactRouterDom = require('react-router-dom');
17
17
  var ELK = require('elkjs/lib/elk.bundled.js');
18
18
  var ReactMarkdown = require('react-markdown');
@@ -2041,7 +2041,7 @@ var init_Modal = __esm({
2041
2041
  document.body.style.overflow = "";
2042
2042
  };
2043
2043
  }, [isOpen]);
2044
- if (!isOpen) return null;
2044
+ if (!isOpen || typeof document === "undefined") return null;
2045
2045
  const handleClose = () => {
2046
2046
  if (closeEvent) eventBus.emit(`UI:${closeEvent}`, {});
2047
2047
  onClose();
@@ -2051,124 +2051,127 @@ var init_Modal = __esm({
2051
2051
  handleClose();
2052
2052
  }
2053
2053
  };
2054
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2055
- /* @__PURE__ */ jsxRuntime.jsx(
2056
- Overlay,
2057
- {
2058
- isVisible: isOpen,
2059
- onClick: handleOverlayClick,
2060
- className: "z-40"
2061
- }
2062
- ),
2063
- /* @__PURE__ */ jsxRuntime.jsx(
2064
- Box,
2065
- {
2066
- className: cn(
2067
- "fixed inset-0 z-50 pointer-events-none",
2068
- "flex items-start justify-center px-4 pb-4 pt-[10vh]",
2069
- "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
2070
- ),
2071
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2072
- Dialog,
2073
- {
2074
- ref: modalRef,
2075
- open: true,
2076
- className: cn(
2077
- // Reset browser-default dialog chrome — we own styling. `static`
2078
- // overrides the user-agent `position: absolute` so the parent
2079
- // flex container's `justify-center` actually centers the dialog
2080
- // (without this, the dialog drops out of flex flow and `m-0`
2081
- // kills the user-agent's `margin: auto` centering, pinning the
2082
- // dialog to top-left).
2083
- "static m-0 p-0 border-0 bg-transparent",
2084
- // Pre-existing dialog frame
2085
- "pointer-events-auto w-full flex flex-col bg-surface border shadow-elevation-dialog rounded-container",
2086
- // Desktop sizing + viewport-aware floor.
2087
- sizeClasses2[size],
2088
- minWidthClasses[size],
2089
- "max-h-[80vh]",
2090
- // Mobile: take the entire screen. Override desktop max-w cap,
2091
- // full height, no rounded corners, no min-width.
2092
- "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
2093
- lookStyles[look],
2094
- className
2095
- ),
2096
- style: dragY > 0 ? {
2097
- transform: `translateY(${dragY}px)`,
2098
- transition: isDragging.current ? "none" : "transform 200ms ease-out"
2099
- } : void 0,
2100
- ...title && { "aria-labelledby": "modal-title" },
2101
- children: [
2102
- /* @__PURE__ */ jsxRuntime.jsx(
2103
- Box,
2104
- {
2105
- className: "hidden max-sm:flex justify-center py-2 cursor-grab active:cursor-grabbing touch-none",
2106
- onPointerDown: (e) => {
2107
- if (!swipeDownToClose) return;
2108
- dragStartY.current = e.clientY;
2109
- isDragging.current = true;
2110
- e.target.setPointerCapture(e.pointerId);
2111
- },
2112
- onPointerMove: (e) => {
2113
- if (!isDragging.current) return;
2114
- const dy = Math.max(0, e.clientY - dragStartY.current);
2115
- setDragY(dy);
2116
- },
2117
- onPointerUp: () => {
2118
- if (!isDragging.current) return;
2119
- isDragging.current = false;
2120
- if (dragY > 100) {
2121
- handleClose();
2122
- }
2123
- setDragY(0);
2124
- },
2125
- onPointerCancel: () => {
2126
- isDragging.current = false;
2127
- setDragY(0);
2128
- },
2129
- children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-10 h-1 rounded-full bg-border" })
2130
- }
2054
+ return reactDom.createPortal(
2055
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2056
+ /* @__PURE__ */ jsxRuntime.jsx(
2057
+ Overlay,
2058
+ {
2059
+ isVisible: isOpen,
2060
+ onClick: handleOverlayClick,
2061
+ className: "z-[1000]"
2062
+ }
2063
+ ),
2064
+ /* @__PURE__ */ jsxRuntime.jsx(
2065
+ Box,
2066
+ {
2067
+ className: cn(
2068
+ "fixed inset-0 z-[1001] pointer-events-none",
2069
+ "flex items-start justify-center px-4 pb-4 pt-[10vh]",
2070
+ "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
2071
+ ),
2072
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2073
+ Dialog,
2074
+ {
2075
+ ref: modalRef,
2076
+ open: true,
2077
+ className: cn(
2078
+ // Reset browser-default dialog chrome we own styling. `static`
2079
+ // overrides the user-agent `position: absolute` so the parent
2080
+ // flex container's `justify-center` actually centers the dialog
2081
+ // (without this, the dialog drops out of flex flow and `m-0`
2082
+ // kills the user-agent's `margin: auto` centering, pinning the
2083
+ // dialog to top-left).
2084
+ "static m-0 p-0 border-0 bg-transparent",
2085
+ // Pre-existing dialog frame
2086
+ "pointer-events-auto w-full flex flex-col bg-surface border shadow-elevation-dialog rounded-container",
2087
+ // Desktop sizing + viewport-aware floor.
2088
+ sizeClasses2[size],
2089
+ minWidthClasses[size],
2090
+ "max-h-[80vh]",
2091
+ // Mobile: take the entire screen. Override desktop max-w cap,
2092
+ // full height, no rounded corners, no min-width.
2093
+ "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
2094
+ lookStyles[look],
2095
+ className
2131
2096
  ),
2132
- (title || showCloseButton) && /* @__PURE__ */ jsxRuntime.jsxs(
2133
- Box,
2134
- {
2135
- className: cn(
2136
- "px-6 py-4 flex items-center justify-between",
2137
- "border-b-[length:var(--border-width)] border-border"
2138
- ),
2139
- children: [
2140
- title && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", as: "h2", id: "modal-title", children: title }),
2141
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
2142
- Button,
2143
- {
2144
- variant: "ghost",
2145
- size: "sm",
2146
- icon: "x",
2147
- onClick: handleClose,
2148
- "data-event": "CLOSE",
2149
- "aria-label": "Close modal"
2097
+ style: dragY > 0 ? {
2098
+ transform: `translateY(${dragY}px)`,
2099
+ transition: isDragging.current ? "none" : "transform 200ms ease-out"
2100
+ } : void 0,
2101
+ ...title && { "aria-labelledby": "modal-title" },
2102
+ children: [
2103
+ /* @__PURE__ */ jsxRuntime.jsx(
2104
+ Box,
2105
+ {
2106
+ className: "hidden max-sm:flex justify-center py-2 cursor-grab active:cursor-grabbing touch-none",
2107
+ onPointerDown: (e) => {
2108
+ if (!swipeDownToClose) return;
2109
+ dragStartY.current = e.clientY;
2110
+ isDragging.current = true;
2111
+ e.target.setPointerCapture(e.pointerId);
2112
+ },
2113
+ onPointerMove: (e) => {
2114
+ if (!isDragging.current) return;
2115
+ const dy = Math.max(0, e.clientY - dragStartY.current);
2116
+ setDragY(dy);
2117
+ },
2118
+ onPointerUp: () => {
2119
+ if (!isDragging.current) return;
2120
+ isDragging.current = false;
2121
+ if (dragY > 100) {
2122
+ handleClose();
2150
2123
  }
2151
- )
2152
- ]
2153
- }
2154
- ),
2155
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto p-6", children }),
2156
- footer && /* @__PURE__ */ jsxRuntime.jsx(
2157
- Box,
2158
- {
2159
- className: cn(
2160
- "px-6 py-4 bg-muted",
2161
- "border-t-[length:var(--border-width)] border-border"
2162
- ),
2163
- children: footer
2164
- }
2165
- )
2166
- ]
2167
- }
2168
- )
2169
- }
2170
- )
2171
- ] });
2124
+ setDragY(0);
2125
+ },
2126
+ onPointerCancel: () => {
2127
+ isDragging.current = false;
2128
+ setDragY(0);
2129
+ },
2130
+ children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-10 h-1 rounded-full bg-border" })
2131
+ }
2132
+ ),
2133
+ (title || showCloseButton) && /* @__PURE__ */ jsxRuntime.jsxs(
2134
+ Box,
2135
+ {
2136
+ className: cn(
2137
+ "px-6 py-4 flex items-center justify-between",
2138
+ "border-b-[length:var(--border-width)] border-border"
2139
+ ),
2140
+ children: [
2141
+ title && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", as: "h2", id: "modal-title", children: title }),
2142
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
2143
+ Button,
2144
+ {
2145
+ variant: "ghost",
2146
+ size: "sm",
2147
+ icon: "x",
2148
+ onClick: handleClose,
2149
+ "data-event": "CLOSE",
2150
+ "aria-label": "Close modal"
2151
+ }
2152
+ )
2153
+ ]
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto p-6", children }),
2157
+ footer && /* @__PURE__ */ jsxRuntime.jsx(
2158
+ Box,
2159
+ {
2160
+ className: cn(
2161
+ "px-6 py-4 bg-muted",
2162
+ "border-t-[length:var(--border-width)] border-border"
2163
+ ),
2164
+ children: footer
2165
+ }
2166
+ )
2167
+ ]
2168
+ }
2169
+ )
2170
+ }
2171
+ )
2172
+ ] }),
2173
+ document.body
2174
+ );
2172
2175
  };
2173
2176
  Modal.displayName = "Modal";
2174
2177
  }
@@ -13964,7 +13967,7 @@ var init_CodeBlock = __esm({
13964
13967
  };
13965
13968
  };
13966
13969
  }, [errorLines]);
13967
- const isFoldable = foldableProp ?? (language === "orb" || language === "json");
13970
+ const isFoldable = foldableProp ?? true;
13968
13971
  const [collapsed, setCollapsed] = React86.useState(() => /* @__PURE__ */ new Set());
13969
13972
  const foldRegions = React86.useMemo(
13970
13973
  () => isFoldable ? computeFoldRegions(code) : [],
@@ -13987,6 +13990,8 @@ var init_CodeBlock = __esm({
13987
13990
  collapsedRef.current = collapsed;
13988
13991
  const foldStartMapRef = React86.useRef(foldStartMap);
13989
13992
  foldStartMapRef.current = foldStartMap;
13993
+ const hiddenLinesRef = React86.useRef(hiddenLines);
13994
+ hiddenLinesRef.current = hiddenLines;
13990
13995
  const toggleFold = React86.useCallback((lineNum) => {
13991
13996
  setCollapsed((prev) => {
13992
13997
  const next = new Set(prev);
@@ -14099,6 +14104,60 @@ var init_CodeBlock = __esm({
14099
14104
  eventBus.emit("UI:COPY_CODE", { language, success: false });
14100
14105
  }
14101
14106
  };
14107
+ const handleSelectionCopy = React86.useCallback((e) => {
14108
+ if (hiddenLinesRef.current.size === 0) return;
14109
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
14110
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
14111
+ const lineOf = (node) => {
14112
+ const start = node instanceof HTMLElement ? node : node?.parentElement ?? null;
14113
+ const lineEl = start?.closest("[data-line]");
14114
+ if (!lineEl) return null;
14115
+ const n = parseInt(lineEl.getAttribute("data-line") ?? "", 10);
14116
+ return Number.isNaN(n) ? null : n;
14117
+ };
14118
+ const range = sel.getRangeAt(0);
14119
+ let a = lineOf(range.startContainer);
14120
+ let b = lineOf(range.endContainer);
14121
+ if (a === null || b === null) {
14122
+ const container = codeRef.current;
14123
+ if (!container) return;
14124
+ let min = Infinity, max = -Infinity;
14125
+ container.querySelectorAll("[data-line]").forEach((el) => {
14126
+ if (!sel.containsNode(el, true)) return;
14127
+ const n = parseInt(el.getAttribute("data-line") ?? "", 10);
14128
+ if (!Number.isNaN(n)) {
14129
+ min = Math.min(min, n);
14130
+ max = Math.max(max, n);
14131
+ }
14132
+ });
14133
+ if (min === Infinity) return;
14134
+ a = a ?? min;
14135
+ b = b ?? max;
14136
+ }
14137
+ if (a > b) [a, b] = [b, a];
14138
+ let touchesFold = false;
14139
+ for (let i = a; i <= b; i++) {
14140
+ if (hiddenLinesRef.current.has(i) || foldStartMapRef.current.has(i) && collapsedRef.current.has(i)) {
14141
+ touchesFold = true;
14142
+ break;
14143
+ }
14144
+ }
14145
+ if (!touchesFold) return;
14146
+ let endLine = b;
14147
+ let changed = true;
14148
+ while (changed) {
14149
+ changed = false;
14150
+ foldStartMapRef.current.forEach((region, start) => {
14151
+ if (start >= a && start <= endLine && collapsedRef.current.has(start) && region.end > endLine) {
14152
+ endLine = region.end;
14153
+ changed = true;
14154
+ }
14155
+ });
14156
+ }
14157
+ const full = code.split("\n").slice(a, endLine + 1).join("\n");
14158
+ e.clipboardData.setData("text/plain", full);
14159
+ e.preventDefault();
14160
+ }, [code]);
14102
14161
  const hasHeader = showLanguageBadge || showCopyButton;
14103
14162
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: `relative group ${className || ""}`, style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
14104
14163
  hasHeader && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -14249,6 +14308,7 @@ var init_CodeBlock = __esm({
14249
14308
  "div",
14250
14309
  {
14251
14310
  ref: scrollRef,
14311
+ onCopy: handleSelectionCopy,
14252
14312
  style: {
14253
14313
  flex: 1,
14254
14314
  minHeight: 0,