@alfadocs/ui-kit-debug 0.67.0 → 0.68.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/dist/_chunks/{ai-tools-rail-DfmewPUN.js → ai-tools-rail-CInA_1-E.js} +2 -2
- package/dist/_chunks/{ai-tools-rail-DfmewPUN.js.map → ai-tools-rail-CInA_1-E.js.map} +1 -1
- package/dist/_chunks/{appointment-tray-CQJ3Nbi5.js → appointment-tray-BMl89NPW.js} +2 -2
- package/dist/_chunks/{appointment-tray-CQJ3Nbi5.js.map → appointment-tray-BMl89NPW.js.map} +1 -1
- package/dist/_chunks/{care-plan-card-CJei-nl9.js → care-plan-card-BwVUyUBb.js} +3 -3
- package/dist/_chunks/{care-plan-card-CJei-nl9.js.map → care-plan-card-BwVUyUBb.js.map} +1 -1
- package/dist/_chunks/{care-plan-entry-card-EmfrbHu8.js → care-plan-entry-card-VzdQHcOL.js} +2 -2
- package/dist/_chunks/{care-plan-entry-card-EmfrbHu8.js.map → care-plan-entry-card-VzdQHcOL.js.map} +1 -1
- package/dist/_chunks/{color-picker-B7xsn6tX.js → color-picker-FXpItIaF.js} +171 -167
- package/dist/_chunks/color-picker-FXpItIaF.js.map +1 -0
- package/dist/_chunks/{editable-currency-cell-renderer-BVJ04DkB.js → editable-currency-cell-renderer-DHOspPee.js} +906 -881
- package/dist/_chunks/editable-currency-cell-renderer-DHOspPee.js.map +1 -0
- package/dist/_chunks/editor-overlay-wYm22wZK.js.map +1 -1
- package/dist/_chunks/{marketplace-app-shell-Bu-HD3fy.js → marketplace-app-shell-D4-T9Qzz.js} +2 -2
- package/dist/_chunks/{marketplace-app-shell-Bu-HD3fy.js.map → marketplace-app-shell-D4-T9Qzz.js.map} +1 -1
- package/dist/_chunks/{patient-shell-DrV_iekt.js → patient-shell-BfgufL_q.js} +2 -2
- package/dist/_chunks/{patient-shell-DrV_iekt.js.map → patient-shell-BfgufL_q.js.map} +1 -1
- package/dist/_chunks/{patient-summary-card-ByfIU8N0.js → patient-summary-card-hEmr4eTW.js} +2 -2
- package/dist/_chunks/{patient-summary-card-ByfIU8N0.js.map → patient-summary-card-hEmr4eTW.js.map} +1 -1
- package/dist/_chunks/{payment-request-card-yTjzKQCU.js → payment-request-card-CYE5vhZ0.js} +2 -2
- package/dist/_chunks/{payment-request-card-yTjzKQCU.js.map → payment-request-card-CYE5vhZ0.js.map} +1 -1
- package/dist/_chunks/{rich-text-editor-BOQr5lHx.js → rich-text-editor-VawKN1FI.js} +884 -801
- package/dist/_chunks/rich-text-editor-VawKN1FI.js.map +1 -0
- package/dist/_chunks/{scroll-area-eccd_Uf-.js → scroll-area-5wNK0Cyz.js} +3 -3
- package/dist/_chunks/scroll-area-5wNK0Cyz.js.map +1 -0
- package/dist/_chunks/{sidebar-CGjIk_FQ.js → sidebar-DaB4ulxH.js} +344 -340
- package/dist/_chunks/sidebar-DaB4ulxH.js.map +1 -0
- package/dist/_chunks/{transaction-chip-BrW8P50O.js → transaction-chip-DK84XCBU.js} +31 -18
- package/dist/_chunks/transaction-chip-DK84XCBU.js.map +1 -0
- package/dist/agent-catalog.json +1 -1
- package/dist/components/ai-tools-rail/index.js +1 -1
- package/dist/components/appointment-card/index.js +1 -1
- package/dist/components/care-plan-card/index.js +1 -1
- package/dist/components/care-plan-entry-card/index.js +1 -1
- package/dist/components/color-picker/color-picker.d.ts.map +1 -1
- package/dist/components/color-picker/index.js +1 -1
- package/dist/components/data-table/cell-renderers/actions-cell-renderer.d.ts.map +1 -1
- package/dist/components/data-table/data-table.d.ts +1 -1
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/filters/date-range-filter.d.ts +1 -1
- package/dist/components/data-table/filters/date-range-filter.d.ts.map +1 -1
- package/dist/components/data-table/index.js +1 -1
- package/dist/components/data-table/toolbar.d.ts +4 -1
- package/dist/components/data-table/toolbar.d.ts.map +1 -1
- package/dist/components/editor-overlay/editor-overlay.d.ts.map +1 -1
- package/dist/components/patient-summary-card/index.js +1 -1
- package/dist/components/payment-card/index.js +1 -1
- package/dist/components/rich-text-editor/editor-toolbar.d.ts.map +1 -1
- package/dist/components/rich-text-editor/help-me-write-overlay.d.ts.map +1 -1
- package/dist/components/rich-text-editor/index.js +1 -1
- package/dist/components/rich-text-editor/rich-text-editor.d.ts +15 -0
- package/dist/components/rich-text-editor/rich-text-editor.d.ts.map +1 -1
- package/dist/components/scroll-area/index.js +1 -1
- package/dist/components/sidebar/index.js +1 -1
- package/dist/components/sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/transaction-chip/index.js +1 -1
- package/dist/components/transaction-chip/transaction-chip.d.ts.map +1 -1
- package/dist/i18n/locales/ar.d.ts +6 -1
- package/dist/i18n/locales/ar.d.ts.map +1 -1
- package/dist/i18n/locales/ar.js +8 -3
- package/dist/i18n/locales/ar.js.map +1 -1
- package/dist/i18n/locales/de.d.ts +6 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +8 -3
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/el.d.ts +6 -1
- package/dist/i18n/locales/el.d.ts.map +1 -1
- package/dist/i18n/locales/el.js +8 -3
- package/dist/i18n/locales/el.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +6 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +8 -3
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +6 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +8 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +6 -1
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +8 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hi.d.ts +6 -1
- package/dist/i18n/locales/hi.d.ts.map +1 -1
- package/dist/i18n/locales/hi.js +8 -3
- package/dist/i18n/locales/hi.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +6 -1
- package/dist/i18n/locales/it.d.ts.map +1 -1
- package/dist/i18n/locales/it.js +8 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts +6 -1
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +8 -3
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/nl.d.ts +6 -1
- package/dist/i18n/locales/nl.d.ts.map +1 -1
- package/dist/i18n/locales/nl.js +8 -3
- package/dist/i18n/locales/nl.js.map +1 -1
- package/dist/i18n/locales/pl.d.ts +6 -1
- package/dist/i18n/locales/pl.d.ts.map +1 -1
- package/dist/i18n/locales/pl.js +8 -3
- package/dist/i18n/locales/pl.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts +6 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +8 -3
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ro.d.ts +6 -1
- package/dist/i18n/locales/ro.d.ts.map +1 -1
- package/dist/i18n/locales/ro.js +8 -3
- package/dist/i18n/locales/ro.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts +6 -1
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +8 -3
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/sq.d.ts +6 -1
- package/dist/i18n/locales/sq.d.ts.map +1 -1
- package/dist/i18n/locales/sq.js +8 -3
- package/dist/i18n/locales/sq.js.map +1 -1
- package/dist/i18n/locales/sv.d.ts +6 -1
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/i18n/locales/sv.js +8 -3
- package/dist/i18n/locales/sv.js.map +1 -1
- package/dist/i18n/locales/tr.d.ts +6 -1
- package/dist/i18n/locales/tr.d.ts.map +1 -1
- package/dist/i18n/locales/tr.js +8 -3
- package/dist/i18n/locales/tr.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +6 -1
- package/dist/i18n/locales/zh.d.ts.map +1 -1
- package/dist/i18n/locales/zh.js +8 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.js +14 -14
- package/dist/locales/ar.json +8 -3
- package/dist/locales/de.json +8 -3
- package/dist/locales/el.json +8 -3
- package/dist/locales/en.json +8 -3
- package/dist/locales/es.json +8 -3
- package/dist/locales/fr.json +8 -3
- package/dist/locales/hi.json +8 -3
- package/dist/locales/it.json +8 -3
- package/dist/locales/ja.json +8 -3
- package/dist/locales/nl.json +8 -3
- package/dist/locales/pl.json +8 -3
- package/dist/locales/pt.json +8 -3
- package/dist/locales/ro.json +8 -3
- package/dist/locales/ru.json +8 -3
- package/dist/locales/sq.json +8 -3
- package/dist/locales/sv.json +8 -3
- package/dist/locales/tr.json +8 -3
- package/dist/locales/zh.json +8 -3
- package/dist/patterns/marketplace-app-shell/index.js +1 -1
- package/dist/patterns/patient-shell/index.js +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/color-picker-B7xsn6tX.js.map +0 -1
- package/dist/_chunks/editable-currency-cell-renderer-BVJ04DkB.js.map +0 -1
- package/dist/_chunks/rich-text-editor-BOQr5lHx.js.map +0 -1
- package/dist/_chunks/scroll-area-eccd_Uf-.js.map +0 -1
- package/dist/_chunks/sidebar-CGjIk_FQ.js.map +0 -1
- package/dist/_chunks/transaction-chip-BrW8P50O.js.map +0 -1
|
@@ -3,7 +3,7 @@ import { forwardRef as Q, useRef as v, useState as ee, useMemo as z, useCallback
|
|
|
3
3
|
import { c as m } from "./index-D2ZczOXr.js";
|
|
4
4
|
import { useTranslation as ie } from "react-i18next";
|
|
5
5
|
import { I as ne } from "./icon-button-LqoiKcUN.js";
|
|
6
|
-
import { S as te } from "./scroll-area-
|
|
6
|
+
import { S as te } from "./scroll-area-5wNK0Cyz.js";
|
|
7
7
|
import { a as oe } from "./tooltip-DvmfrNvB.js";
|
|
8
8
|
import { u as re } from "./use-controllable-state-BiY4xTzM.js";
|
|
9
9
|
import { u as de } from "./use-edge-resize-ZnGG7gyO.js";
|
|
@@ -353,4 +353,4 @@ export {
|
|
|
353
353
|
Re as A,
|
|
354
354
|
ce as a
|
|
355
355
|
};
|
|
356
|
-
//# sourceMappingURL=ai-tools-rail-
|
|
356
|
+
//# sourceMappingURL=ai-tools-rail-CInA_1-E.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-tools-rail-DfmewPUN.js","sources":["../../src/components/ai-tools-rail/ai-tools-rail.agent.ts","../../src/components/ai-tools-rail/ai-tools-rail.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — AiToolsRail. */\n/* */\n/* Rail-level surface: open / close a tool's panel and read which tool */\n/* is active. State is strictly the active tool id — never any tool's */\n/* panel CONTENT (which may carry PHI). Tool ids + badge counts are */\n/* supplied by the host via props, not derived from panel bodies. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { AiToolsRailHandle } from './ai-tools-rail';\n\nexport const aiToolsRailAgent: AgentAdapter<AiToolsRailHandle> = {\n id: 'ai-tools-rail',\n capabilities: ['open', 'close', 'select_single'],\n state: {\n activeToolId: {\n type: 'string | null',\n descriptionKey: 'ui.aiToolsRail.agent.state.activeToolId',\n description:\n 'Id of the tool whose panel is currently open, or null when the rail is collapsed to its strip. No panel content is ever exposed.',\n read: (handle) => handle.getActiveToolId(),\n },\n },\n actions: {\n open: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.open',\n description: \"Open the given tool's panel.\",\n invoke: (handle, args: { id: string }) => {\n handle.open(args.id);\n },\n },\n close: {\n safety: 'read',\n descriptionKey: 'ui.aiToolsRail.agent.actions.close',\n description: 'Collapse the rail to its strip (no active tool).',\n invoke: (handle) => {\n handle.close();\n },\n },\n select_tool: {\n safety: 'read',\n argsType: '{ id: string | null }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.selectTool',\n description:\n 'Set the active tool by id (opens its panel), or pass null to collapse.',\n invoke: (handle, args: { id: string | null }) => {\n handle.setActiveTool(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'ai-tools-rail',\n description: 'Marks the AiToolsRail wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific rail from the agent.',\n },\n item: {\n attr: 'data-tool-id',\n description:\n 'Stable tool id emitted on each strip IconButton. The active icon also carries data-active set to its id.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { IconButton } from '../button/icon-button';\nimport { ScrollArea } from '../scroll-area/scroll-area';\nimport { TooltipProvider } from '../tooltip/tooltip';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useEdgeResize } from '../../hooks/use-edge-resize';\nimport { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { aiToolsRailAgent } from './ai-tools-rail.agent';\n\n/* ------------------------------------------------------------------ */\n/* Tokenised widths */\n/* */\n/* Fixed + tokenised so the rail stays constraint-compliant — no */\n/* prop-driven inline styles. Bare `[inline-size:…]` is NOT a */\n/* recognised utility under the `ds:` prefix; it must carry the */\n/* prefix or Tailwind emits no rule. Consumers needing a different */\n/* size wrap the rail and override via CSS. */\n/* ------------------------------------------------------------------ */\nconst RAIL_WIDTH = 'ds:[inline-size:3rem]';\n// Panel width rides a consumer-overridable custom property (set\n// `--ai-tools-rail-panel-inline-size` on the rail or a wrapper) so\n// content-heavy tools (e.g. an inbox feed) can widen their panel\n// without inline styles. Defaults to the 22rem tray-family width.\nconst PANEL_WIDTH =\n 'ds:[inline-size:var(--ai-tools-rail-panel-inline-size,22rem)]';\n// The custom property PANEL_WIDTH reads. The resize hook writes the\n// committed px width here (on the panel element, so it beats consumer\n// class overrides on the root) and removes it on reset.\nconst PANEL_SIZE_PROPERTY = '--ai-tools-rail-panel-inline-size';\n// Resize clamp + nominal default, px (352px = the 22rem token default).\nconst PANEL_MIN_SIZE = 288;\nconst PANEL_MAX_SIZE = 640;\nconst PANEL_DEFAULT_SIZE = 352;\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailTool {\n /** Stable tool id — used in `data-active`, the imperative handle, and\n * to label the open panel region. */\n id: string;\n /** Accessible name for the strip icon + tooltip, and the open panel's\n * region label. Never rendered as visible body text. */\n label: string;\n /** lucide-react glyph (rendered `aria-hidden` inside the IconButton). */\n icon: ReactNode;\n /** Panel body for this tool. Rendered inline-start of the strip when\n * `side='end'` (mirrored for `'start'`). The consumer composes it. */\n panel: ReactNode;\n /** Optional unseen / notification count shown on the strip icon. */\n badgeCount?: number;\n /** When true the icon is non-interactive and skipped by roving focus. */\n disabled?: boolean;\n}\n\n/**\n * Curated imperative handle for AiToolsRail. Exposed as the forwardRef\n * target so a future agent / MCP UI bridge can drive the rail without\n * touching the DOM. See `ai-tools-rail.agent.ts`.\n *\n * State surfaced here is strictly identifiers + counts — never any\n * tool's panel content (which may carry PHI).\n */\nexport interface AiToolsRailHandle {\n /** Currently-active tool id, or null when collapsed to strip-only. */\n getActiveToolId: () => string | null;\n /** Activate a tool by id (opens its panel), or null to collapse. */\n setActiveTool: (id: string | null) => void;\n /** Activate a tool by id (opens its panel). */\n open: (id: string) => void;\n /** Collapse to strip-only (no active tool). */\n close: () => void;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA — `side` drives the dock edge + which shadow faces main content */\n/* ------------------------------------------------------------------ */\n\nconst railVariants = cva(\n ['ds:flex ds:shrink-0 ds:h-full', 'ds:bg-[var(--background)]'].join(' '),\n {\n variants: {\n side: {\n // Strip docks inline-end; the panel mounts inline-start of it\n // (flex-row, panel rendered first). Shadow faces the main area —\n // no border against main (the rail stays line-free; the only seam\n // is the strip↔panel divider on the open panel, see render).\n // Accessible (AAA) theme swaps the soft chrome shadow for a solid 1px\n // border on the content-facing edge (border-s for end-docked).\n end: 'ds:flex-row ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n // Strip docks inline-start; the panel mounts inline-end of it.\n start:\n 'ds:flex-row-reverse ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n },\n },\n defaultVariants: { side: 'end' },\n },\n);\n\nconst stripVariants = cva(\n [\n 'ds:flex ds:shrink-0 ds:flex-col ds:items-center',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-xs)]',\n RAIL_WIDTH,\n ].join(' '),\n);\n\nconst panelVariants = cva(\n [\n 'ds:relative ds:flex ds:min-h-0 ds:flex-col ds:h-full',\n 'ds:bg-[var(--background)]',\n PANEL_WIDTH,\n // Defensive: the panel doesn't animate its width today, but if a\n // consumer adds a transition on the var, suppress it mid-drag.\n 'ds:data-[resizing]:transition-none',\n ].join(' '),\n);\n\n// Resize handle on the panel's inner edge — mirrors Resizable's Handle\n// (::before hit area to --min-target-size, hover/focus/drag accent, token\n// focus ring) but absolutely positioned over the panel edge. Like the\n// Sidebar's handle it stays TRANSPARENT at rest: the rail root's\n// `shadow-chrome-*` (which faces the main content) carries the resting seam,\n// exactly as the start-docked Sidebar leans on its own shadow — so the rail\n// never shows the constant coloured bar it used to. The strip only tints\n// accent on hover / focus / drag.\nconst panelResizeHandleVariants = cva(\n [\n 'ds:absolute ds:[inset-block:0] ds:z-[1] ds:select-none ds:touch-none',\n 'ds:[inline-size:var(--resizable-handle-size)] ds:cursor-col-resize',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:bg-[var(--accent)] ds:focus-visible:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n \"ds:before:absolute ds:before:content-['']\",\n 'ds:before:[block-size:100%]',\n 'ds:before:[inline-size:var(--min-target-size)]',\n 'ds:before:[inset-block:0]',\n 'ds:before:[inset-inline:calc(0px_-_(var(--min-target-size)_-_var(--resizable-handle-size))_/_2)]',\n ],\n {\n variants: {\n edge: {\n 'inline-start': 'ds:start-0',\n 'inline-end': 'ds:end-0',\n },\n },\n defaultVariants: { edge: 'inline-start' },\n },\n);\n\n// Hand-rolled overlay count chip — mirrors the AliaSidebar / MessageTray\n// badge markup. A <Badge> would re-introduce its own landmark/role; the\n// strip icon already carries the count in its accessible name, so this\n// span is purely decorative (`aria-hidden`).\nconst badgeVariants = cva(\n [\n 'ds:absolute ds:-top-[var(--spacing-xs)] ds:-end-[var(--spacing-xs)]',\n 'ds:inline-flex ds:items-center ds:justify-center',\n 'ds:min-w-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:h-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:ps-[calc(var(--spacing-xs)/1.5)] ds:pe-[calc(var(--spacing-xs)/1.5)]',\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[color:var(--destructive)] ds:text-[color:var(--destructive-foreground)]',\n 'ds:text-[length:var(--font-size-xs)] ds:font-semibold ds:leading-none',\n 'ds:pointer-events-none ds:select-none',\n 'ds:forced-colors:outline ds:forced-colors:outline-1 ds:forced-colors:outline-[CanvasText]',\n ].join(' '),\n);\n\n/* ------------------------------------------------------------------ */\n/* Props */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailProps extends VariantProps<typeof railVariants> {\n /** Stable instance id, surfaced on the root as `data-component-id`. */\n id?: string;\n /** The tools shown as icons on the strip. */\n tools: AiToolsRailTool[];\n /** Controlled active tool id (null = collapsed / strip-only). */\n activeToolId?: string | null;\n /** Uncontrolled initial active tool id (default null). */\n defaultActiveToolId?: string | null;\n /** Fires whenever the active tool changes (incl. → null on collapse). */\n onActiveToolChange?: (id: string | null) => void;\n /** Which edge the rail docks to. Default `'end'` (inline-end). */\n side?: 'start' | 'end';\n /** Accessible label for the toolbar strip. */\n railLabel?: string;\n /**\n * Opt-in resize affordance for the open tool panel. Renders a\n * `role=\"separator\"` drag handle on the panel's inner edge (the edge\n * facing the main content). The committed width is written to\n * `--ai-tools-rail-panel-inline-size` on the panel element (288–640px);\n * Enter / double-click resets to the token-driven default. Default `false`.\n */\n resizablePanel?: boolean;\n className?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatBadgeCount(count: number, overflowLabel: string): string {\n return count > 99 ? overflowLabel : String(count);\n}\n\n/* ------------------------------------------------------------------ */\n/* Root */\n/* ------------------------------------------------------------------ */\n\nexport const AiToolsRail = forwardRef<AiToolsRailHandle, AiToolsRailProps>(\n (\n {\n id,\n tools,\n activeToolId,\n defaultActiveToolId = null,\n onActiveToolChange,\n side = 'end',\n railLabel,\n resizablePanel = false,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n // Controlled / uncontrolled bridge — the same hook MessageTray uses.\n const [activeRaw, setActiveRaw] = useControllableState<string | null>({\n value: activeToolId,\n defaultValue: defaultActiveToolId,\n onChange: onActiveToolChange,\n });\n const activeId = activeRaw ?? null;\n\n // Roving-tabindex bookkeeping for the vertical toolbar strip.\n const buttonsRef = useRef<Array<HTMLButtonElement | null>>([]);\n const panelRef = useRef<HTMLElement | null>(null);\n const [focusIndex, setFocusIndex] = useState(0);\n // Distinguishes keyboard activation (auto-focus into the panel) from\n // pointer activation (focus stays on the icon — less jarring).\n const activatedByKeyboardRef = useRef(false);\n // Set on Enter/Space keydown, read by the click handler that follows,\n // cleared on keyup — so a subsequent mouse click never inherits it.\n const keyboardActivateRef = useRef(false);\n\n const activeIndex = useMemo(\n () => tools.findIndex((tl) => tl.id === activeId),\n [tools, activeId],\n );\n const activeTool = activeIndex >= 0 ? tools[activeIndex] : null;\n\n // First non-disabled index — the resting roving stop when none active.\n const firstEnabledIndex = useMemo(\n () => tools.findIndex((tl) => !tl.disabled),\n [tools],\n );\n\n // The roving tab stop: the active icon when a tool is open, else the\n // index the user last focused, else the first enabled icon.\n const rovingIndex =\n activeIndex >= 0\n ? activeIndex\n : focusIndex >= 0 && !tools[focusIndex]?.disabled\n ? focusIndex\n : firstEnabledIndex;\n\n const selectTool = useCallback(\n (toolId: string, fromKeyboard: boolean) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = fromKeyboard;\n // Clicking / activating the active tool collapses to strip-only.\n setActiveRaw(activeId === toolId ? null : toolId);\n },\n [tools, activeId, setActiveRaw],\n );\n\n /* ---- Roving arrow-key navigation (vertical toolbar) ----------- */\n const handleStripKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const count = tools.length;\n if (count === 0) return;\n\n let next: number | null = null;\n if (event.key === 'ArrowDown') next = (rovingIndex + 1) % count;\n else if (event.key === 'ArrowUp')\n next = (rovingIndex - 1 + count) % count;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = count - 1;\n if (next === null) return;\n\n // Skip disabled icons in the travel direction.\n const goingBack = event.key === 'ArrowUp' || event.key === 'End';\n let candidate = next;\n let attempts = 0;\n while (attempts < count && tools[candidate]?.disabled) {\n candidate = goingBack\n ? (candidate - 1 + count) % count\n : (candidate + 1) % count;\n attempts += 1;\n }\n if (tools[candidate]?.disabled) return;\n\n event.preventDefault();\n setFocusIndex(candidate);\n buttonsRef.current[candidate]?.focus();\n },\n [tools, rovingIndex],\n );\n\n /* ---- Esc collapses + returns focus to the active tool's icon -- */\n // Attached imperatively via the root ref (not a JSX onKeyDown on the\n // static container) so the Escape shortcut works from anywhere inside the\n // open panel without an interaction handler on a non-interactive element.\n const rootRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n const node = rootRef.current;\n if (!node) return undefined;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (activeIndex < 0) return;\n event.preventDefault();\n const returnIndex = activeIndex;\n setActiveRaw(null);\n setFocusIndex(returnIndex);\n // Defer until after the panel unmounts so focus lands cleanly.\n requestAnimationFrame(() => {\n buttonsRef.current[returnIndex]?.focus();\n });\n };\n node.addEventListener('keydown', onKeyDown);\n return () => node.removeEventListener('keydown', onKeyDown);\n }, [activeIndex, setActiveRaw]);\n\n /* ---- Move focus into the panel when opened by keyboard -------- */\n useIsomorphicLayoutEffect(() => {\n if (activeTool && activatedByKeyboardRef.current) {\n panelRef.current?.focus();\n }\n activatedByKeyboardRef.current = false;\n }, [activeTool]);\n\n /* ---- Opt-in panel resize (inner edge) -------------------------- */\n // The handle sits on the edge facing the main content: the panel's\n // inline-start edge for an end-docked rail, inline-end for start.\n const panelEdge = side === 'end' ? 'inline-start' : 'inline-end';\n const { separatorProps, isResizing, isResized } = useEdgeResize({\n edge: panelEdge,\n targetRef: panelRef,\n cssProperty: PANEL_SIZE_PROPERTY,\n minSize: PANEL_MIN_SIZE,\n maxSize: PANEL_MAX_SIZE,\n defaultSize: PANEL_DEFAULT_SIZE,\n disabled: !resizablePanel,\n });\n\n // The aside unmounts on collapse, dropping the hook's inline override.\n // Re-assert the committed width when a panel (re)mounts so a reopened\n // panel keeps the user's chosen size.\n const committedPanelWidth = separatorProps['aria-valuenow'];\n useIsomorphicLayoutEffect(() => {\n if (!resizablePanel || !isResized || !activeTool) return;\n panelRef.current?.style.setProperty(\n PANEL_SIZE_PROPERTY,\n `${committedPanelWidth}px`,\n );\n }, [activeTool, resizablePanel, isResized, committedPanelWidth]);\n\n /* ---- Curated imperative handle for agent integration ---------- */\n const activeIdRef = useRef<string | null>(activeId);\n activeIdRef.current = activeId;\n\n const handle = useMemo<AiToolsRailHandle>(\n () => ({\n getActiveToolId: () => activeIdRef.current,\n setActiveTool: (toolId) => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n open: (toolId) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n close: () => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(null);\n },\n }),\n [tools, setActiveRaw],\n );\n\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(aiToolsRailAgent, handle, id);\n\n const overflowLabel = t('aiToolsRail.badgeCountOverflow', {\n defaultValue: '99+',\n });\n\n return (\n <div\n ref={rootRef}\n data-component=\"ai-tools-rail\"\n data-component-id={id}\n data-side={side}\n className={[railVariants({ side }), className]\n .filter(Boolean)\n .join(' ')}\n >\n {/* The OPEN panel is the single complementary landmark, named by\n the active tool. Rendered only while a tool is active so two\n unnamed regions never coexist. */}\n {activeTool ? (\n <aside\n ref={panelRef}\n tabIndex={-1}\n aria-label={t('aiToolsRail.panelLabel', {\n tool: activeTool.label,\n defaultValue: '{{tool}} panel',\n })}\n data-ai-tools-rail-state=\"expanded\"\n data-active-tool={activeTool.id}\n data-resizing={isResizing ? '' : undefined}\n className={[\n panelVariants(),\n // Subtle shadow — not a line — separating the panel from the\n // icon strip (the tab row). The panel casts a soft chrome\n // shadow toward the strip on its strip-facing edge. `z-[1]`\n // lifts it above the adjacent strip (the panel is already\n // `relative`) so the shadow paints at the seam instead of being\n // covered by the strip's opaque background. Only renders while\n // the panel is open (the aside is unmounted when collapsed), so\n // it never doubles as a rail↔main seam. In the accessible (AAA)\n // theme the soft shadow becomes a solid 1px border on that same\n // strip-facing edge.\n 'ds:z-[1]',\n side === 'end'\n ? 'ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none'\n : 'ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n 'ds:focus-visible:outline-none',\n ].join(' ')}\n >\n <ScrollArea orientation=\"vertical\" className=\"ds:flex-1 ds:min-h-0\">\n {activeTool.panel}\n </ScrollArea>\n {/* Inside the aside (so it belongs to the complementary\n landmark) and outside the toolbar strip — the strip's\n roving handlers never see its key events, and Escape\n still bubbles to the root to collapse, as everywhere\n else in the panel. No stopPropagation needed. */}\n {resizablePanel ? (\n <div\n {...separatorProps}\n aria-label={t('aiToolsRail.resizePanel', {\n defaultValue: 'Resize tool panel',\n })}\n data-resizing={isResizing ? '' : undefined}\n className={panelResizeHandleVariants({ edge: panelEdge })}\n />\n ) : null}\n </aside>\n ) : null}\n\n {/* The 3rem icon strip — a vertical toolbar with roving tabindex.\n Self-contained TooltipProvider so consumers needn't mount one;\n Radix providers compose, so a host-level provider still works. */}\n <TooltipProvider>\n <div\n role=\"toolbar\"\n aria-orientation=\"vertical\"\n aria-label={\n railLabel ??\n t('aiToolsRail.railLabel', { defaultValue: 'AI tools' })\n }\n className={stripVariants()}\n onKeyDown={handleStripKeyDown}\n >\n {tools.map((tool, index) => {\n const isActive = tool.id === activeId;\n const showBadge =\n typeof tool.badgeCount === 'number' && tool.badgeCount > 0;\n // Fold the count into the accessible name so SR users hear it\n // without relying on the decorative chip.\n const accessibleName = showBadge\n ? t('aiToolsRail.toolWithCount', {\n tool: tool.label,\n count: tool.badgeCount,\n defaultValue: '{{tool}}, {{count}} updates',\n })\n : tool.label;\n return (\n <div key={tool.id} className=\"ds:relative\">\n <IconButton\n ref={(el) => {\n buttonsRef.current[index] = el;\n }}\n icon={tool.icon}\n tooltip={tool.label}\n aria-label={accessibleName}\n intent=\"ghost\"\n size=\"sm\"\n disabled={tool.disabled}\n aria-pressed={isActive}\n aria-expanded={isActive}\n data-active={isActive ? tool.id : undefined}\n data-tool-id={tool.id}\n tabIndex={index === rovingIndex ? 0 : -1}\n onFocus={() => {\n if (!tool.disabled) setFocusIndex(index);\n }}\n onClick={() =>\n selectTool(tool.id, keyboardActivateRef.current)\n }\n onKeyDown={(event) => {\n // Enter/Space activate via the native button's click,\n // which fires AFTER this keydown. Flag the upcoming\n // click as keyboard-driven so focus moves into the\n // panel; reset on keyup so a later mouse click doesn't\n // inherit the flag.\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = true;\n }\n }}\n onKeyUp={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = false;\n }\n }}\n />\n {showBadge ? (\n <span aria-hidden=\"true\" className={badgeVariants()}>\n {formatBadgeCount(\n tool.badgeCount as number,\n overflowLabel,\n )}\n </span>\n ) : null}\n </div>\n );\n })}\n </div>\n </TooltipProvider>\n </div>\n );\n },\n);\n\nAiToolsRail.displayName = 'AiToolsRail';\n"],"names":["aiToolsRailAgent","handle","args","RAIL_WIDTH","PANEL_WIDTH","PANEL_SIZE_PROPERTY","PANEL_MIN_SIZE","PANEL_MAX_SIZE","PANEL_DEFAULT_SIZE","railVariants","cva","stripVariants","panelVariants","panelResizeHandleVariants","badgeVariants","formatBadgeCount","count","overflowLabel","AiToolsRail","forwardRef","id","tools","activeToolId","defaultActiveToolId","onActiveToolChange","side","railLabel","resizablePanel","className","ref","t","useTranslation","activeRaw","setActiveRaw","useControllableState","activeId","buttonsRef","useRef","panelRef","focusIndex","setFocusIndex","useState","activatedByKeyboardRef","keyboardActivateRef","activeIndex","useMemo","tl","activeTool","firstEnabledIndex","rovingIndex","_a","selectTool","useCallback","toolId","fromKeyboard","tool","handleStripKeyDown","event","next","goingBack","candidate","attempts","_b","_c","rootRef","useEffect","node","onKeyDown","returnIndex","useIsomorphicLayoutEffect","panelEdge","separatorProps","isResizing","isResized","useEdgeResize","committedPanelWidth","activeIdRef","useImperativeHandle","useAgentRegistration","jsxs","jsx","ScrollArea","TooltipProvider","index","isActive","showBadge","accessibleName","IconButton","el"],"mappings":";;;;;;;;;;;AAYO,MAAMA,KAAoD;AAAA,EAC/D,IAAI;AAAA,EACJ,cAAc,CAAC,QAAQ,SAAS,eAAe;AAAA,EAC/C,OAAO;AAAA,IACL,cAAc;AAAA,MACZ,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,MAAM,CAACC,MAAWA,EAAO,gBAAA;AAAA,IAAgB;AAAA,EAC3C;AAAA,EAEF,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,QAAQ,CAACA,GAAQC,MAAyB;AACxC,QAAAD,EAAO,KAAKC,EAAK,EAAE;AAAA,MACrB;AAAA,IAAA;AAAA,IAEF,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,QAAQ,CAACD,MAAW;AAClB,QAAAA,EAAO,MAAA;AAAA,MACT;AAAA,IAAA;AAAA,IAEF,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,aACE;AAAA,MACF,QAAQ,CAACA,GAAQC,MAAgC;AAC/C,QAAAD,EAAO,cAAcC,EAAK,EAAE;AAAA,MAC9B;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IAAA;AAAA,IAEf,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,aACE;AAAA,IAAA;AAAA,IAEJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aACE;AAAA,IAAA;AAAA,EACJ;AAEJ,GCxCMC,KAAa,yBAKbC,KACJ,iEAIIC,IAAsB,qCAEtBC,KAAiB,KACjBC,KAAiB,KACjBC,KAAqB,KA+CrBC,KAAeC;AAAA,EACnB,CAAC,iCAAiC,2BAA2B,EAAE,KAAK,GAAG;AAAA,EACvE;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOJ,KAAK;AAAA;AAAA,QAEL,OACE;AAAA,MAAA;AAAA,IACJ;AAAA,IAEF,iBAAiB,EAAE,MAAM,MAAA;AAAA,EAAM;AAEnC,GAEMC,KAAgBD;AAAA,EACpB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACAP;AAAA,EAAA,EACA,KAAK,GAAG;AACZ,GAEMS,KAAgBF;AAAA,EACpB;AAAA,IACE;AAAA,IACA;AAAA,IACAN;AAAA;AAAA;AAAA,IAGA;AAAA,EAAA,EACA,KAAK,GAAG;AACZ,GAUMS,KAA4BH;AAAA,EAChC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB;AAAA,IAEF,iBAAiB,EAAE,MAAM,eAAA;AAAA,EAAe;AAE5C,GAMMI,KAAgBJ;AAAA,EACpB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,EACA,KAAK,GAAG;AACZ;AAoCA,SAASK,GAAiBC,GAAeC,GAA+B;AACtE,SAAOD,IAAQ,KAAKC,IAAgB,OAAOD,CAAK;AAClD;AAMO,MAAME,KAAcC;AAAA,EACzB,CACE;AAAA,IACE,IAAAC;AAAA,IACA,OAAAC;AAAA,IACA,cAAAC;AAAA,IACA,qBAAAC,IAAsB;AAAA,IACtB,oBAAAC;AAAA,IACA,MAAAC,IAAO;AAAA,IACP,WAAAC;AAAA,IACA,gBAAAC,IAAiB;AAAA,IACjB,WAAAC;AAAA,EAAA,GAEFC,MACG;;AACH,UAAM,EAAE,GAAAC,EAAA,IAAMC,GAAA,GAGR,CAACC,GAAWC,CAAY,IAAIC,GAAoC;AAAA,MACpE,OAAOZ;AAAA,MACP,cAAcC;AAAA,MACd,UAAUC;AAAA,IAAA,CACX,GACKW,IAAWH,KAAa,MAGxBI,IAAaC,EAAwC,EAAE,GACvDC,IAAWD,EAA2B,IAAI,GAC1C,CAACE,GAAYC,CAAa,IAAIC,GAAS,CAAC,GAGxCC,IAAyBL,EAAO,EAAK,GAGrCM,IAAsBN,EAAO,EAAK,GAElCO,IAAcC;AAAA,MAClB,MAAMxB,EAAM,UAAU,CAACyB,MAAOA,EAAG,OAAOX,CAAQ;AAAA,MAChD,CAACd,GAAOc,CAAQ;AAAA,IAAA,GAEZY,IAAaH,KAAe,IAAIvB,EAAMuB,CAAW,IAAI,MAGrDI,IAAoBH;AAAA,MACxB,MAAMxB,EAAM,UAAU,CAACyB,MAAO,CAACA,EAAG,QAAQ;AAAA,MAC1C,CAACzB,CAAK;AAAA,IAAA,GAKF4B,IACJL,KAAe,IACXA,IACAL,KAAc,KAAK,GAACW,IAAA7B,EAAMkB,CAAU,MAAhB,QAAAW,EAAmB,YACrCX,IACAS,GAEFG,IAAaC;AAAA,MACjB,CAACC,GAAgBC,MAA0B;AACzC,cAAMC,IAAOlC,EAAM,KAAK,CAACyB,MAAOA,EAAG,OAAOO,CAAM;AAChD,QAAI,CAACE,KAAQA,EAAK,aAClBb,EAAuB,UAAUY,GAEjCrB,EAAaE,MAAakB,IAAS,OAAOA,CAAM;AAAA,MAClD;AAAA,MACA,CAAChC,GAAOc,GAAUF,CAAY;AAAA,IAAA,GAI1BuB,IAAqBJ;AAAA,MACzB,CAACK,MAA8C;;AAC7C,cAAMzC,IAAQK,EAAM;AACpB,YAAIL,MAAU,EAAG;AAEjB,YAAI0C,IAAsB;AAM1B,YALID,EAAM,QAAQ,cAAaC,KAAQT,IAAc,KAAKjC,IACjDyC,EAAM,QAAQ,YACrBC,KAAQT,IAAc,IAAIjC,KAASA,IAC5ByC,EAAM,QAAQ,SAAQC,IAAO,IAC7BD,EAAM,QAAQ,UAAOC,IAAO1C,IAAQ,IACzC0C,MAAS,KAAM;AAGnB,cAAMC,IAAYF,EAAM,QAAQ,aAAaA,EAAM,QAAQ;AAC3D,YAAIG,IAAYF,GACZG,IAAW;AACf,eAAOA,IAAW7C,OAASkC,IAAA7B,EAAMuC,CAAS,MAAf,QAAAV,EAAkB;AAC3C,UAAAU,IAAYD,KACPC,IAAY,IAAI5C,KAASA,KACzB4C,IAAY,KAAK5C,GACtB6C,KAAY;AAEd,SAAIC,IAAAzC,EAAMuC,CAAS,MAAf,QAAAE,EAAkB,aAEtBL,EAAM,eAAA,GACNjB,EAAcoB,CAAS,IACvBG,IAAA3B,EAAW,QAAQwB,CAAS,MAA5B,QAAAG,EAA+B;AAAA,MACjC;AAAA,MACA,CAAC1C,GAAO4B,CAAW;AAAA,IAAA,GAOfe,IAAU3B,EAAuB,IAAI;AAC3C,IAAA4B,GAAU,MAAM;AACd,YAAMC,IAAOF,EAAQ;AACrB,UAAI,CAACE,EAAM;AACX,YAAMC,IAAY,CAACV,MAAyB;AAE1C,YADIA,EAAM,QAAQ,YACdb,IAAc,EAAG;AACrB,QAAAa,EAAM,eAAA;AACN,cAAMW,IAAcxB;AACpB,QAAAX,EAAa,IAAI,GACjBO,EAAc4B,CAAW,GAEzB,sBAAsB,MAAM;;AAC1B,WAAAlB,IAAAd,EAAW,QAAQgC,CAAW,MAA9B,QAAAlB,EAAiC;AAAA,QACnC,CAAC;AAAA,MACH;AACA,aAAAgB,EAAK,iBAAiB,WAAWC,CAAS,GACnC,MAAMD,EAAK,oBAAoB,WAAWC,CAAS;AAAA,IAC5D,GAAG,CAACvB,GAAaX,CAAY,CAAC,GAG9BoC,EAA0B,MAAM;;AAC9B,MAAItB,KAAcL,EAAuB,aACvCQ,IAAAZ,EAAS,YAAT,QAAAY,EAAkB,UAEpBR,EAAuB,UAAU;AAAA,IACnC,GAAG,CAACK,CAAU,CAAC;AAKf,UAAMuB,IAAY7C,MAAS,QAAQ,iBAAiB,cAC9C,EAAE,gBAAA8C,GAAgB,YAAAC,GAAY,WAAAC,EAAA,IAAcC,GAAc;AAAA,MAC9D,MAAMJ;AAAA,MACN,WAAWhC;AAAA,MACX,aAAajC;AAAA,MACb,SAASC;AAAA,MACT,SAASC;AAAA,MACT,aAAaC;AAAA,MACb,UAAU,CAACmB;AAAA,IAAA,CACZ,GAKKgD,IAAsBJ,EAAe,eAAe;AAC1D,IAAAF,EAA0B,MAAM;;AAC9B,MAAI,CAAC1C,KAAkB,CAAC8C,KAAa,CAAC1B,MACtCG,IAAAZ,EAAS,YAAT,QAAAY,EAAkB,MAAM;AAAA,QACtB7C;AAAA,QACA,GAAGsE,CAAmB;AAAA;AAAA,IAE1B,GAAG,CAAC5B,GAAYpB,GAAgB8C,GAAWE,CAAmB,CAAC;AAG/D,UAAMC,IAAcvC,EAAsBF,CAAQ;AAClD,IAAAyC,EAAY,UAAUzC;AAEtB,UAAMlC,IAAS4C;AAAA,MACb,OAAO;AAAA,QACL,iBAAiB,MAAM+B,EAAY;AAAA,QACnC,eAAe,CAACvB,MAAW;AACzB,UAAAX,EAAuB,UAAU,IACjCT,EAAaoB,CAAM;AAAA,QACrB;AAAA,QACA,MAAM,CAACA,MAAW;AAChB,gBAAME,IAAOlC,EAAM,KAAK,CAACyB,MAAOA,EAAG,OAAOO,CAAM;AAChD,UAAI,CAACE,KAAQA,EAAK,aAClBb,EAAuB,UAAU,IACjCT,EAAaoB,CAAM;AAAA,QACrB;AAAA,QACA,OAAO,MAAM;AACX,UAAAX,EAAuB,UAAU,IACjCT,EAAa,IAAI;AAAA,QACnB;AAAA,MAAA;AAAA,MAEF,CAACZ,GAAOY,CAAY;AAAA,IAAA;AAGtB,IAAA4C,GAAoBhD,GAAK,MAAM5B,GAAQ,CAACA,CAAM,CAAC,GAC/C6E,GAAqB9E,IAAkBC,GAAQmB,CAAE;AAEjD,UAAMH,IAAgBa,EAAE,kCAAkC;AAAA,MACxD,cAAc;AAAA,IAAA,CACf;AAED,WACE,gBAAAiD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKf;AAAA,QACL,kBAAe;AAAA,QACf,qBAAmB5C;AAAA,QACnB,aAAWK;AAAA,QACX,WAAW,CAAChB,GAAa,EAAE,MAAAgB,GAAM,GAAGG,CAAS,EAC1C,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,QAKV,UAAA;AAAA,UAAAmB,IACC,gBAAAgC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAKzC;AAAA,cACL,UAAU;AAAA,cACV,cAAYR,EAAE,0BAA0B;AAAA,gBACtC,MAAMiB,EAAW;AAAA,gBACjB,cAAc;AAAA,cAAA,CACf;AAAA,cACD,4BAAyB;AAAA,cACzB,oBAAkBA,EAAW;AAAA,cAC7B,iBAAeyB,IAAa,KAAK;AAAA,cACjC,WAAW;AAAA,gBACT5D,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAWA;AAAA,gBACAa,MAAS,QACL,gKACA;AAAA,gBACJ;AAAA,cAAA,EACA,KAAK,GAAG;AAAA,cAEV,UAAA;AAAA,gBAAA,gBAAAuD,EAACC,MAAW,aAAY,YAAW,WAAU,wBAC1C,YAAW,OACd;AAAA,gBAMCtD,IACC,gBAAAqD;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACE,GAAGT;AAAA,oBACJ,cAAYzC,EAAE,2BAA2B;AAAA,sBACvC,cAAc;AAAA,oBAAA,CACf;AAAA,oBACD,iBAAe0C,IAAa,KAAK;AAAA,oBACjC,WAAW3D,GAA0B,EAAE,MAAMyD,GAAW;AAAA,kBAAA;AAAA,gBAAA,IAExD;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA,IAEJ;AAAA,4BAKHY,IAAA,EACC,UAAA,gBAAAF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,oBAAiB;AAAA,cACjB,cACEtD,KACAI,EAAE,yBAAyB,EAAE,cAAc,YAAY;AAAA,cAEzD,WAAWnB,GAAA;AAAA,cACX,WAAW6C;AAAA,cAEV,UAAAnC,EAAM,IAAI,CAACkC,GAAM4B,MAAU;AAC1B,sBAAMC,IAAW7B,EAAK,OAAOpB,GACvBkD,IACJ,OAAO9B,EAAK,cAAe,YAAYA,EAAK,aAAa,GAGrD+B,IAAiBD,IACnBvD,EAAE,6BAA6B;AAAA,kBAC7B,MAAMyB,EAAK;AAAA,kBACX,OAAOA,EAAK;AAAA,kBACZ,cAAc;AAAA,gBAAA,CACf,IACDA,EAAK;AACT,uBACE,gBAAAwB,EAAC,OAAA,EAAkB,WAAU,eAC3B,UAAA;AAAA,kBAAA,gBAAAC;AAAA,oBAACO;AAAA,oBAAA;AAAA,sBACC,KAAK,CAACC,MAAO;AACX,wBAAApD,EAAW,QAAQ+C,CAAK,IAAIK;AAAA,sBAC9B;AAAA,sBACA,MAAMjC,EAAK;AAAA,sBACX,SAASA,EAAK;AAAA,sBACd,cAAY+B;AAAA,sBACZ,QAAO;AAAA,sBACP,MAAK;AAAA,sBACL,UAAU/B,EAAK;AAAA,sBACf,gBAAc6B;AAAA,sBACd,iBAAeA;AAAA,sBACf,eAAaA,IAAW7B,EAAK,KAAK;AAAA,sBAClC,gBAAcA,EAAK;AAAA,sBACnB,UAAU4B,MAAUlC,IAAc,IAAI;AAAA,sBACtC,SAAS,MAAM;AACb,wBAAKM,EAAK,YAAUf,EAAc2C,CAAK;AAAA,sBACzC;AAAA,sBACA,SAAS,MACPhC,EAAWI,EAAK,IAAIZ,EAAoB,OAAO;AAAA,sBAEjD,WAAW,CAACc,MAAU;AAMpB,yBAAIA,EAAM,QAAQ,WAAWA,EAAM,QAAQ,SACzCd,EAAoB,UAAU;AAAA,sBAElC;AAAA,sBACA,SAAS,CAACc,MAAU;AAClB,yBAAIA,EAAM,QAAQ,WAAWA,EAAM,QAAQ,SACzCd,EAAoB,UAAU;AAAA,sBAElC;AAAA,oBAAA;AAAA,kBAAA;AAAA,kBAED0C,IACC,gBAAAL,EAAC,QAAA,EAAK,eAAY,QAAO,WAAWlE,MACjC,UAAAC;AAAA,oBACCwC,EAAK;AAAA,oBACLtC;AAAA,kBAAA,GAEJ,IACE;AAAA,gBAAA,EAAA,GA7CIsC,EAAK,EA8Cf;AAAA,cAEJ,CAAC;AAAA,YAAA;AAAA,UAAA,EACH,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEArC,GAAY,cAAc;"}
|
|
1
|
+
{"version":3,"file":"ai-tools-rail-CInA_1-E.js","sources":["../../src/components/ai-tools-rail/ai-tools-rail.agent.ts","../../src/components/ai-tools-rail/ai-tools-rail.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — AiToolsRail. */\n/* */\n/* Rail-level surface: open / close a tool's panel and read which tool */\n/* is active. State is strictly the active tool id — never any tool's */\n/* panel CONTENT (which may carry PHI). Tool ids + badge counts are */\n/* supplied by the host via props, not derived from panel bodies. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { AiToolsRailHandle } from './ai-tools-rail';\n\nexport const aiToolsRailAgent: AgentAdapter<AiToolsRailHandle> = {\n id: 'ai-tools-rail',\n capabilities: ['open', 'close', 'select_single'],\n state: {\n activeToolId: {\n type: 'string | null',\n descriptionKey: 'ui.aiToolsRail.agent.state.activeToolId',\n description:\n 'Id of the tool whose panel is currently open, or null when the rail is collapsed to its strip. No panel content is ever exposed.',\n read: (handle) => handle.getActiveToolId(),\n },\n },\n actions: {\n open: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.open',\n description: \"Open the given tool's panel.\",\n invoke: (handle, args: { id: string }) => {\n handle.open(args.id);\n },\n },\n close: {\n safety: 'read',\n descriptionKey: 'ui.aiToolsRail.agent.actions.close',\n description: 'Collapse the rail to its strip (no active tool).',\n invoke: (handle) => {\n handle.close();\n },\n },\n select_tool: {\n safety: 'read',\n argsType: '{ id: string | null }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.selectTool',\n description:\n 'Set the active tool by id (opens its panel), or pass null to collapse.',\n invoke: (handle, args: { id: string | null }) => {\n handle.setActiveTool(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'ai-tools-rail',\n description: 'Marks the AiToolsRail wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific rail from the agent.',\n },\n item: {\n attr: 'data-tool-id',\n description:\n 'Stable tool id emitted on each strip IconButton. The active icon also carries data-active set to its id.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { IconButton } from '../button/icon-button';\nimport { ScrollArea } from '../scroll-area/scroll-area';\nimport { TooltipProvider } from '../tooltip/tooltip';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useEdgeResize } from '../../hooks/use-edge-resize';\nimport { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { aiToolsRailAgent } from './ai-tools-rail.agent';\n\n/* ------------------------------------------------------------------ */\n/* Tokenised widths */\n/* */\n/* Fixed + tokenised so the rail stays constraint-compliant — no */\n/* prop-driven inline styles. Bare `[inline-size:…]` is NOT a */\n/* recognised utility under the `ds:` prefix; it must carry the */\n/* prefix or Tailwind emits no rule. Consumers needing a different */\n/* size wrap the rail and override via CSS. */\n/* ------------------------------------------------------------------ */\nconst RAIL_WIDTH = 'ds:[inline-size:3rem]';\n// Panel width rides a consumer-overridable custom property (set\n// `--ai-tools-rail-panel-inline-size` on the rail or a wrapper) so\n// content-heavy tools (e.g. an inbox feed) can widen their panel\n// without inline styles. Defaults to the 22rem tray-family width.\nconst PANEL_WIDTH =\n 'ds:[inline-size:var(--ai-tools-rail-panel-inline-size,22rem)]';\n// The custom property PANEL_WIDTH reads. The resize hook writes the\n// committed px width here (on the panel element, so it beats consumer\n// class overrides on the root) and removes it on reset.\nconst PANEL_SIZE_PROPERTY = '--ai-tools-rail-panel-inline-size';\n// Resize clamp + nominal default, px (352px = the 22rem token default).\nconst PANEL_MIN_SIZE = 288;\nconst PANEL_MAX_SIZE = 640;\nconst PANEL_DEFAULT_SIZE = 352;\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailTool {\n /** Stable tool id — used in `data-active`, the imperative handle, and\n * to label the open panel region. */\n id: string;\n /** Accessible name for the strip icon + tooltip, and the open panel's\n * region label. Never rendered as visible body text. */\n label: string;\n /** lucide-react glyph (rendered `aria-hidden` inside the IconButton). */\n icon: ReactNode;\n /** Panel body for this tool. Rendered inline-start of the strip when\n * `side='end'` (mirrored for `'start'`). The consumer composes it. */\n panel: ReactNode;\n /** Optional unseen / notification count shown on the strip icon. */\n badgeCount?: number;\n /** When true the icon is non-interactive and skipped by roving focus. */\n disabled?: boolean;\n}\n\n/**\n * Curated imperative handle for AiToolsRail. Exposed as the forwardRef\n * target so a future agent / MCP UI bridge can drive the rail without\n * touching the DOM. See `ai-tools-rail.agent.ts`.\n *\n * State surfaced here is strictly identifiers + counts — never any\n * tool's panel content (which may carry PHI).\n */\nexport interface AiToolsRailHandle {\n /** Currently-active tool id, or null when collapsed to strip-only. */\n getActiveToolId: () => string | null;\n /** Activate a tool by id (opens its panel), or null to collapse. */\n setActiveTool: (id: string | null) => void;\n /** Activate a tool by id (opens its panel). */\n open: (id: string) => void;\n /** Collapse to strip-only (no active tool). */\n close: () => void;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA — `side` drives the dock edge + which shadow faces main content */\n/* ------------------------------------------------------------------ */\n\nconst railVariants = cva(\n ['ds:flex ds:shrink-0 ds:h-full', 'ds:bg-[var(--background)]'].join(' '),\n {\n variants: {\n side: {\n // Strip docks inline-end; the panel mounts inline-start of it\n // (flex-row, panel rendered first). Shadow faces the main area —\n // no border against main (the rail stays line-free; the only seam\n // is the strip↔panel divider on the open panel, see render).\n // Accessible (AAA) theme swaps the soft chrome shadow for a solid 1px\n // border on the content-facing edge (border-s for end-docked).\n end: 'ds:flex-row ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n // Strip docks inline-start; the panel mounts inline-end of it.\n start:\n 'ds:flex-row-reverse ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n },\n },\n defaultVariants: { side: 'end' },\n },\n);\n\nconst stripVariants = cva(\n [\n 'ds:flex ds:shrink-0 ds:flex-col ds:items-center',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-xs)]',\n RAIL_WIDTH,\n ].join(' '),\n);\n\nconst panelVariants = cva(\n [\n 'ds:relative ds:flex ds:min-h-0 ds:flex-col ds:h-full',\n 'ds:bg-[var(--background)]',\n PANEL_WIDTH,\n // Defensive: the panel doesn't animate its width today, but if a\n // consumer adds a transition on the var, suppress it mid-drag.\n 'ds:data-[resizing]:transition-none',\n ].join(' '),\n);\n\n// Resize handle on the panel's inner edge — mirrors Resizable's Handle\n// (::before hit area to --min-target-size, hover/focus/drag accent, token\n// focus ring) but absolutely positioned over the panel edge. Like the\n// Sidebar's handle it stays TRANSPARENT at rest: the rail root's\n// `shadow-chrome-*` (which faces the main content) carries the resting seam,\n// exactly as the start-docked Sidebar leans on its own shadow — so the rail\n// never shows the constant coloured bar it used to. The strip only tints\n// accent on hover / focus / drag.\nconst panelResizeHandleVariants = cva(\n [\n 'ds:absolute ds:[inset-block:0] ds:z-[1] ds:select-none ds:touch-none',\n 'ds:[inline-size:var(--resizable-handle-size)] ds:cursor-col-resize',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:bg-[var(--accent)] ds:focus-visible:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n \"ds:before:absolute ds:before:content-['']\",\n 'ds:before:[block-size:100%]',\n 'ds:before:[inline-size:var(--min-target-size)]',\n 'ds:before:[inset-block:0]',\n 'ds:before:[inset-inline:calc(0px_-_(var(--min-target-size)_-_var(--resizable-handle-size))_/_2)]',\n ],\n {\n variants: {\n edge: {\n 'inline-start': 'ds:start-0',\n 'inline-end': 'ds:end-0',\n },\n },\n defaultVariants: { edge: 'inline-start' },\n },\n);\n\n// Hand-rolled overlay count chip — mirrors the AliaSidebar / MessageTray\n// badge markup. A <Badge> would re-introduce its own landmark/role; the\n// strip icon already carries the count in its accessible name, so this\n// span is purely decorative (`aria-hidden`).\nconst badgeVariants = cva(\n [\n 'ds:absolute ds:-top-[var(--spacing-xs)] ds:-end-[var(--spacing-xs)]',\n 'ds:inline-flex ds:items-center ds:justify-center',\n 'ds:min-w-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:h-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:ps-[calc(var(--spacing-xs)/1.5)] ds:pe-[calc(var(--spacing-xs)/1.5)]',\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[color:var(--destructive)] ds:text-[color:var(--destructive-foreground)]',\n 'ds:text-[length:var(--font-size-xs)] ds:font-semibold ds:leading-none',\n 'ds:pointer-events-none ds:select-none',\n 'ds:forced-colors:outline ds:forced-colors:outline-1 ds:forced-colors:outline-[CanvasText]',\n ].join(' '),\n);\n\n/* ------------------------------------------------------------------ */\n/* Props */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailProps extends VariantProps<typeof railVariants> {\n /** Stable instance id, surfaced on the root as `data-component-id`. */\n id?: string;\n /** The tools shown as icons on the strip. */\n tools: AiToolsRailTool[];\n /** Controlled active tool id (null = collapsed / strip-only). */\n activeToolId?: string | null;\n /** Uncontrolled initial active tool id (default null). */\n defaultActiveToolId?: string | null;\n /** Fires whenever the active tool changes (incl. → null on collapse). */\n onActiveToolChange?: (id: string | null) => void;\n /** Which edge the rail docks to. Default `'end'` (inline-end). */\n side?: 'start' | 'end';\n /** Accessible label for the toolbar strip. */\n railLabel?: string;\n /**\n * Opt-in resize affordance for the open tool panel. Renders a\n * `role=\"separator\"` drag handle on the panel's inner edge (the edge\n * facing the main content). The committed width is written to\n * `--ai-tools-rail-panel-inline-size` on the panel element (288–640px);\n * Enter / double-click resets to the token-driven default. Default `false`.\n */\n resizablePanel?: boolean;\n className?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatBadgeCount(count: number, overflowLabel: string): string {\n return count > 99 ? overflowLabel : String(count);\n}\n\n/* ------------------------------------------------------------------ */\n/* Root */\n/* ------------------------------------------------------------------ */\n\nexport const AiToolsRail = forwardRef<AiToolsRailHandle, AiToolsRailProps>(\n (\n {\n id,\n tools,\n activeToolId,\n defaultActiveToolId = null,\n onActiveToolChange,\n side = 'end',\n railLabel,\n resizablePanel = false,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n // Controlled / uncontrolled bridge — the same hook MessageTray uses.\n const [activeRaw, setActiveRaw] = useControllableState<string | null>({\n value: activeToolId,\n defaultValue: defaultActiveToolId,\n onChange: onActiveToolChange,\n });\n const activeId = activeRaw ?? null;\n\n // Roving-tabindex bookkeeping for the vertical toolbar strip.\n const buttonsRef = useRef<Array<HTMLButtonElement | null>>([]);\n const panelRef = useRef<HTMLElement | null>(null);\n const [focusIndex, setFocusIndex] = useState(0);\n // Distinguishes keyboard activation (auto-focus into the panel) from\n // pointer activation (focus stays on the icon — less jarring).\n const activatedByKeyboardRef = useRef(false);\n // Set on Enter/Space keydown, read by the click handler that follows,\n // cleared on keyup — so a subsequent mouse click never inherits it.\n const keyboardActivateRef = useRef(false);\n\n const activeIndex = useMemo(\n () => tools.findIndex((tl) => tl.id === activeId),\n [tools, activeId],\n );\n const activeTool = activeIndex >= 0 ? tools[activeIndex] : null;\n\n // First non-disabled index — the resting roving stop when none active.\n const firstEnabledIndex = useMemo(\n () => tools.findIndex((tl) => !tl.disabled),\n [tools],\n );\n\n // The roving tab stop: the active icon when a tool is open, else the\n // index the user last focused, else the first enabled icon.\n const rovingIndex =\n activeIndex >= 0\n ? activeIndex\n : focusIndex >= 0 && !tools[focusIndex]?.disabled\n ? focusIndex\n : firstEnabledIndex;\n\n const selectTool = useCallback(\n (toolId: string, fromKeyboard: boolean) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = fromKeyboard;\n // Clicking / activating the active tool collapses to strip-only.\n setActiveRaw(activeId === toolId ? null : toolId);\n },\n [tools, activeId, setActiveRaw],\n );\n\n /* ---- Roving arrow-key navigation (vertical toolbar) ----------- */\n const handleStripKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const count = tools.length;\n if (count === 0) return;\n\n let next: number | null = null;\n if (event.key === 'ArrowDown') next = (rovingIndex + 1) % count;\n else if (event.key === 'ArrowUp')\n next = (rovingIndex - 1 + count) % count;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = count - 1;\n if (next === null) return;\n\n // Skip disabled icons in the travel direction.\n const goingBack = event.key === 'ArrowUp' || event.key === 'End';\n let candidate = next;\n let attempts = 0;\n while (attempts < count && tools[candidate]?.disabled) {\n candidate = goingBack\n ? (candidate - 1 + count) % count\n : (candidate + 1) % count;\n attempts += 1;\n }\n if (tools[candidate]?.disabled) return;\n\n event.preventDefault();\n setFocusIndex(candidate);\n buttonsRef.current[candidate]?.focus();\n },\n [tools, rovingIndex],\n );\n\n /* ---- Esc collapses + returns focus to the active tool's icon -- */\n // Attached imperatively via the root ref (not a JSX onKeyDown on the\n // static container) so the Escape shortcut works from anywhere inside the\n // open panel without an interaction handler on a non-interactive element.\n const rootRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n const node = rootRef.current;\n if (!node) return undefined;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (activeIndex < 0) return;\n event.preventDefault();\n const returnIndex = activeIndex;\n setActiveRaw(null);\n setFocusIndex(returnIndex);\n // Defer until after the panel unmounts so focus lands cleanly.\n requestAnimationFrame(() => {\n buttonsRef.current[returnIndex]?.focus();\n });\n };\n node.addEventListener('keydown', onKeyDown);\n return () => node.removeEventListener('keydown', onKeyDown);\n }, [activeIndex, setActiveRaw]);\n\n /* ---- Move focus into the panel when opened by keyboard -------- */\n useIsomorphicLayoutEffect(() => {\n if (activeTool && activatedByKeyboardRef.current) {\n panelRef.current?.focus();\n }\n activatedByKeyboardRef.current = false;\n }, [activeTool]);\n\n /* ---- Opt-in panel resize (inner edge) -------------------------- */\n // The handle sits on the edge facing the main content: the panel's\n // inline-start edge for an end-docked rail, inline-end for start.\n const panelEdge = side === 'end' ? 'inline-start' : 'inline-end';\n const { separatorProps, isResizing, isResized } = useEdgeResize({\n edge: panelEdge,\n targetRef: panelRef,\n cssProperty: PANEL_SIZE_PROPERTY,\n minSize: PANEL_MIN_SIZE,\n maxSize: PANEL_MAX_SIZE,\n defaultSize: PANEL_DEFAULT_SIZE,\n disabled: !resizablePanel,\n });\n\n // The aside unmounts on collapse, dropping the hook's inline override.\n // Re-assert the committed width when a panel (re)mounts so a reopened\n // panel keeps the user's chosen size.\n const committedPanelWidth = separatorProps['aria-valuenow'];\n useIsomorphicLayoutEffect(() => {\n if (!resizablePanel || !isResized || !activeTool) return;\n panelRef.current?.style.setProperty(\n PANEL_SIZE_PROPERTY,\n `${committedPanelWidth}px`,\n );\n }, [activeTool, resizablePanel, isResized, committedPanelWidth]);\n\n /* ---- Curated imperative handle for agent integration ---------- */\n const activeIdRef = useRef<string | null>(activeId);\n activeIdRef.current = activeId;\n\n const handle = useMemo<AiToolsRailHandle>(\n () => ({\n getActiveToolId: () => activeIdRef.current,\n setActiveTool: (toolId) => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n open: (toolId) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n close: () => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(null);\n },\n }),\n [tools, setActiveRaw],\n );\n\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(aiToolsRailAgent, handle, id);\n\n const overflowLabel = t('aiToolsRail.badgeCountOverflow', {\n defaultValue: '99+',\n });\n\n return (\n <div\n ref={rootRef}\n data-component=\"ai-tools-rail\"\n data-component-id={id}\n data-side={side}\n className={[railVariants({ side }), className]\n .filter(Boolean)\n .join(' ')}\n >\n {/* The OPEN panel is the single complementary landmark, named by\n the active tool. Rendered only while a tool is active so two\n unnamed regions never coexist. */}\n {activeTool ? (\n <aside\n ref={panelRef}\n tabIndex={-1}\n aria-label={t('aiToolsRail.panelLabel', {\n tool: activeTool.label,\n defaultValue: '{{tool}} panel',\n })}\n data-ai-tools-rail-state=\"expanded\"\n data-active-tool={activeTool.id}\n data-resizing={isResizing ? '' : undefined}\n className={[\n panelVariants(),\n // Subtle shadow — not a line — separating the panel from the\n // icon strip (the tab row). The panel casts a soft chrome\n // shadow toward the strip on its strip-facing edge. `z-[1]`\n // lifts it above the adjacent strip (the panel is already\n // `relative`) so the shadow paints at the seam instead of being\n // covered by the strip's opaque background. Only renders while\n // the panel is open (the aside is unmounted when collapsed), so\n // it never doubles as a rail↔main seam. In the accessible (AAA)\n // theme the soft shadow becomes a solid 1px border on that same\n // strip-facing edge.\n 'ds:z-[1]',\n side === 'end'\n ? 'ds:shadow-[var(--shadow-chrome-end)] ds:[.theme-accessible_&]:border-e ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none'\n : 'ds:shadow-[var(--shadow-chrome-start)] ds:[.theme-accessible_&]:border-s ds:[.theme-accessible_&]:border-[var(--border)] ds:[.theme-accessible_&]:shadow-none',\n 'ds:focus-visible:outline-none',\n ].join(' ')}\n >\n <ScrollArea orientation=\"vertical\" className=\"ds:flex-1 ds:min-h-0\">\n {activeTool.panel}\n </ScrollArea>\n {/* Inside the aside (so it belongs to the complementary\n landmark) and outside the toolbar strip — the strip's\n roving handlers never see its key events, and Escape\n still bubbles to the root to collapse, as everywhere\n else in the panel. No stopPropagation needed. */}\n {resizablePanel ? (\n <div\n {...separatorProps}\n aria-label={t('aiToolsRail.resizePanel', {\n defaultValue: 'Resize tool panel',\n })}\n data-resizing={isResizing ? '' : undefined}\n className={panelResizeHandleVariants({ edge: panelEdge })}\n />\n ) : null}\n </aside>\n ) : null}\n\n {/* The 3rem icon strip — a vertical toolbar with roving tabindex.\n Self-contained TooltipProvider so consumers needn't mount one;\n Radix providers compose, so a host-level provider still works. */}\n <TooltipProvider>\n <div\n role=\"toolbar\"\n aria-orientation=\"vertical\"\n aria-label={\n railLabel ??\n t('aiToolsRail.railLabel', { defaultValue: 'AI tools' })\n }\n className={stripVariants()}\n onKeyDown={handleStripKeyDown}\n >\n {tools.map((tool, index) => {\n const isActive = tool.id === activeId;\n const showBadge =\n typeof tool.badgeCount === 'number' && tool.badgeCount > 0;\n // Fold the count into the accessible name so SR users hear it\n // without relying on the decorative chip.\n const accessibleName = showBadge\n ? t('aiToolsRail.toolWithCount', {\n tool: tool.label,\n count: tool.badgeCount,\n defaultValue: '{{tool}}, {{count}} updates',\n })\n : tool.label;\n return (\n <div key={tool.id} className=\"ds:relative\">\n <IconButton\n ref={(el) => {\n buttonsRef.current[index] = el;\n }}\n icon={tool.icon}\n tooltip={tool.label}\n aria-label={accessibleName}\n intent=\"ghost\"\n size=\"sm\"\n disabled={tool.disabled}\n aria-pressed={isActive}\n aria-expanded={isActive}\n data-active={isActive ? tool.id : undefined}\n data-tool-id={tool.id}\n tabIndex={index === rovingIndex ? 0 : -1}\n onFocus={() => {\n if (!tool.disabled) setFocusIndex(index);\n }}\n onClick={() =>\n selectTool(tool.id, keyboardActivateRef.current)\n }\n onKeyDown={(event) => {\n // Enter/Space activate via the native button's click,\n // which fires AFTER this keydown. Flag the upcoming\n // click as keyboard-driven so focus moves into the\n // panel; reset on keyup so a later mouse click doesn't\n // inherit the flag.\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = true;\n }\n }}\n onKeyUp={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = false;\n }\n }}\n />\n {showBadge ? (\n <span aria-hidden=\"true\" className={badgeVariants()}>\n {formatBadgeCount(\n tool.badgeCount as number,\n overflowLabel,\n )}\n </span>\n ) : null}\n </div>\n );\n })}\n </div>\n </TooltipProvider>\n </div>\n );\n },\n);\n\nAiToolsRail.displayName = 'AiToolsRail';\n"],"names":["aiToolsRailAgent","handle","args","RAIL_WIDTH","PANEL_WIDTH","PANEL_SIZE_PROPERTY","PANEL_MIN_SIZE","PANEL_MAX_SIZE","PANEL_DEFAULT_SIZE","railVariants","cva","stripVariants","panelVariants","panelResizeHandleVariants","badgeVariants","formatBadgeCount","count","overflowLabel","AiToolsRail","forwardRef","id","tools","activeToolId","defaultActiveToolId","onActiveToolChange","side","railLabel","resizablePanel","className","ref","t","useTranslation","activeRaw","setActiveRaw","useControllableState","activeId","buttonsRef","useRef","panelRef","focusIndex","setFocusIndex","useState","activatedByKeyboardRef","keyboardActivateRef","activeIndex","useMemo","tl","activeTool","firstEnabledIndex","rovingIndex","_a","selectTool","useCallback","toolId","fromKeyboard","tool","handleStripKeyDown","event","next","goingBack","candidate","attempts","_b","_c","rootRef","useEffect","node","onKeyDown","returnIndex","useIsomorphicLayoutEffect","panelEdge","separatorProps","isResizing","isResized","useEdgeResize","committedPanelWidth","activeIdRef","useImperativeHandle","useAgentRegistration","jsxs","jsx","ScrollArea","TooltipProvider","index","isActive","showBadge","accessibleName","IconButton","el"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { A as U } from "./avatar-BNQNhoyL.js";
|
|
|
9
9
|
import { B as P } from "./badge-CqGsX32l.js";
|
|
10
10
|
import { T as ee } from "./tag-BQXwuJq1.js";
|
|
11
11
|
import { T as ae } from "./timestamp-BV2lC-wV.js";
|
|
12
|
-
import { T as se } from "./transaction-chip-
|
|
12
|
+
import { T as se } from "./transaction-chip-DK84XCBU.js";
|
|
13
13
|
import { i as re } from "./is-safe-url-DkETxeHz.js";
|
|
14
14
|
import { V as te } from "./video-BAeICxCT.js";
|
|
15
15
|
import { M as ne } from "./map-pin-B8STOPMJ.js";
|
|
@@ -416,4 +416,4 @@ export {
|
|
|
416
416
|
$e as b,
|
|
417
417
|
he as c
|
|
418
418
|
};
|
|
419
|
-
//# sourceMappingURL=appointment-tray-
|
|
419
|
+
//# sourceMappingURL=appointment-tray-BMl89NPW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appointment-tray-CQJ3Nbi5.js","sources":["../../src/components/appointment-card/appointment-card.tsx","../../src/components/appointment-card/appointment-card.agent.ts","../../src/components/appointment-card/appointment-tray.agent.ts","../../src/components/appointment-card/appointment-tray.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { ChevronRight, Globe, MapPin, Star, Video } from 'lucide-react';\nimport { Avatar } from '../avatar/avatar';\nimport { Badge } from '../badge/badge';\nimport { Tag } from '../tag/tag';\nimport { Timestamp } from '../timestamp/timestamp';\nimport { TransactionChip } from '../transaction-chip/transaction-chip';\nimport {\n appointmentStateBadge,\n colorIdToToken,\n isSafeEntityUrl,\n} from '../_shared/entity-card';\nimport type { AppointmentState } from '../_shared/entity-card';\n\n/*\n * Closed colour-dot class map. `colorIdToToken` resolves the operator's\n * numeric colour id to one of a fixed set of palette tokens; the operator\n * dot needs a *static* Tailwind class for each so the compiler can emit it\n * (dynamic `ds:bg-[…]` strings are not seen by the JIT). The keys mirror the\n * exact token strings the shared helper returns.\n */\nconst OPERATOR_DOT_CLASS: Record<string, string> = {\n 'var(--color-violet-500)': 'ds:bg-[var(--color-violet-500)]',\n 'var(--color-purple-500)': 'ds:bg-[var(--color-purple-500)]',\n 'var(--color-magenta-500)': 'ds:bg-[var(--color-magenta-500)]',\n 'var(--color-blue-500)': 'ds:bg-[var(--color-blue-500)]',\n 'var(--color-green-500)': 'ds:bg-[var(--color-green-500)]',\n 'var(--color-red-500)': 'ds:bg-[var(--color-red-500)]',\n 'var(--color-yellow-500)': 'ds:bg-[var(--color-yellow-500)]',\n 'var(--color-indigo-500)': 'ds:bg-[var(--color-indigo-500)]',\n 'var(--color-orange-500)': 'ds:bg-[var(--color-orange-500)]',\n 'var(--color-fuchsia-500)': 'ds:bg-[var(--color-fuchsia-500)]',\n 'var(--color-grey-700)': 'ds:bg-[var(--color-grey-700)]',\n};\n\nfunction operatorDotClass(colorId: number | string | undefined): string {\n return (\n OPERATOR_DOT_CLASS[colorIdToToken(colorId)] ??\n 'ds:bg-[var(--color-grey-700)]'\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* AppointmentCard */\n/* */\n/* Single-row appointment surface — sibling of AgendaCard, but richer: */\n/* leading timestamp, operator + patient avatars with the operator */\n/* colour dot, a lifecycle-state Badge (announced by label, never colour */\n/* alone), treatment / tooth Tags, and an optional advance-deposit */\n/* TransactionChip. State→Badge-variant + colour-id→token + URL-safety */\n/* come from the shared `_shared/entity-card` helpers. */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `appointmentCard.*` namespace (plus shared `financial.*` for money). */\n/* -------------------------------------------------------------------- */\n\n// Back-compat / one-stop import — the URL guard lives in the shared helper.\nexport { isSafeEntityUrl as isSafeAppointmentUrl } from '../_shared/is-safe-url';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** Appointment delivery channel. */\nexport type AppointmentType = 'inPractice' | 'telehealth';\n\n/** First-visit tri-state flag. */\nexport type AppointmentFirstVisit = 'yes' | 'no' | 'unknown';\n\n/** Operator (clinician) summary — name + optional palette colour id. */\nexport interface AppointmentOperator {\n /** Operator display name — drives the avatar initials + accessible name. */\n name: string;\n /** Numeric palette colour id, resolved to a kit token for the colour dot. */\n colorId?: number | string;\n}\n\n/** Patient summary — identity only, drives the avatar. */\nexport interface AppointmentPatient {\n /** Patient display name. */\n name: string;\n /** Optional avatar image URL. */\n avatarUrl?: string;\n}\n\n/** One treatment line within the appointment. */\nexport interface AppointmentTreatment {\n /** Localised care / treatment name. */\n careName: string;\n /** Optional tooth elements (e.g. FDI ids) the treatment targets. */\n elements?: string[];\n}\n\n/** Booking-reason metadata captured at online-booking time. */\nexport interface AppointmentBookingReason {\n /** Localised booking-reason description. */\n description: string;\n /** Expected duration in minutes. */\n durationMin?: number;\n /** Advance / deposit amount in cents (non-negative). */\n advanceAmountCents?: number;\n /** Localised specialty label. */\n specialty?: string;\n}\n\n/** Boolean appointment flags surfaced as chips / indicators. */\nexport interface AppointmentFlags {\n bookedOnline?: boolean;\n noPatientAssigned?: boolean;\n highlightedAnamnesis?: boolean;\n billable?: boolean;\n}\n\nexport interface AppointmentItem {\n /** Stable appointment identifier. */\n id: string;\n /** ISO-8601 start instant. */\n startIso: string;\n /** Duration in minutes. */\n durationMin: number;\n /** Lifecycle state — drives the Badge variant + label. */\n state: AppointmentState;\n /** Delivery channel — drives the leading type glyph. */\n type: AppointmentType;\n /** First-visit flag. */\n firstVisit?: AppointmentFirstVisit;\n /** Assigned operator. */\n operator?: AppointmentOperator;\n /** Chair / room label. */\n chair?: string;\n /** Patient identity. */\n patient?: AppointmentPatient;\n /** Treatment lines. */\n treatments?: AppointmentTreatment[];\n /** Online booking-reason metadata. */\n bookingReason?: AppointmentBookingReason;\n /** Boolean flags. */\n flags?: AppointmentFlags;\n /** Optional deep link — when present (and safe) the row renders as an anchor. */\n url?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* CVA — row */\n/* -------------------------------------------------------------------- */\n\nconst appointmentCardVariants = cva(\n [\n 'ds:relative ds:flex ds:items-start ds:gap-[var(--spacing-sm)]',\n 'ds:rounded-[var(--radius-md)]',\n 'ds:text-start ds:group',\n 'ds:bg-[var(--card)] ds:text-[color:var(--card-foreground)]',\n 'ds:transition-colors ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n 'ds:forced-colors:border ds:forced-colors:border-[CanvasText]',\n ].join(' '),\n {\n variants: {\n size: {\n sm: 'ds:p-[var(--spacing-sm)]',\n md: 'ds:p-[var(--spacing-md)]',\n },\n interactive: {\n true: 'ds:hover:bg-[color:var(--muted)]/40 ds:cursor-pointer',\n false: '',\n },\n },\n defaultVariants: {\n size: 'sm',\n interactive: false,\n },\n },\n);\n\nconst stretchedLinkClass = [\n 'ds:focus-visible:outline-none',\n \"ds:after:content-[''] ds:after:absolute ds:after:inset-0 ds:after:rounded-[var(--radius-md)]\",\n 'ds:after:pointer-events-auto',\n 'ds:focus-visible:after:outline-[length:var(--focus-ring-width)]',\n 'ds:focus-visible:after:outline-solid',\n 'ds:focus-visible:after:outline-[color:var(--ring)]',\n 'ds:focus-visible:after:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:after:outline-[CanvasText]',\n].join(' ');\n\nconst TYPE_ICON: Record<AppointmentType, typeof MapPin> = {\n inPractice: MapPin,\n telehealth: Video,\n};\n\n/* -------------------------------------------------------------------- */\n/* Props */\n/* -------------------------------------------------------------------- */\n\nexport interface AppointmentCardProps\n extends\n Omit<HTMLAttributes<HTMLDivElement>, 'onClick' | 'role' | 'title'>,\n VariantProps<typeof appointmentCardVariants> {\n /** The appointment to render. */\n item: AppointmentItem;\n /** Visual density. */\n size?: 'sm' | 'md';\n /** BCP-47 locale override for the timestamp + currency. */\n locale?: string;\n /** ISO-4217 currency code for the advance-deposit chip. Defaults to `EUR`. */\n currency?: string;\n /** Fires when the row is activated. */\n onActivate?: (item: AppointmentItem) => void;\n}\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const AppointmentCard = forwardRef<HTMLDivElement, AppointmentCardProps>(\n (\n {\n item,\n size = 'sm',\n locale,\n currency = 'EUR',\n onActivate,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const interactive = !!onActivate;\n const hasSafeUrl = !!item.url && isSafeEntityUrl(item.url);\n\n const patientName = item.patient?.name ?? t('appointmentCard.noPatient');\n const operatorName = item.operator?.name;\n const stateLabel = t(`appointmentCard.state.${item.state}`);\n\n const avatarSize = size === 'md' ? 'md' : 'sm';\n const TypeIcon = TYPE_ICON[item.type];\n\n const ariaLabel = t('appointmentCard.itemLabel', {\n patient: patientName,\n state: stateLabel,\n });\n\n const titleNode = (\n <span className=\"ds:min-w-0 ds:truncate type-title-item ds:text-[color:var(--foreground)]\">\n {patientName}\n </span>\n );\n\n let activator: ReactNode;\n if (interactive && hasSafeUrl) {\n activator = (\n <a\n href={item.url}\n aria-label={ariaLabel}\n className={stretchedLinkClass + ' ds:contents'}\n onClick={(e) => {\n if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey)\n return;\n onActivate?.(item);\n }}\n >\n {titleNode}\n </a>\n );\n } else if (interactive) {\n activator = (\n <button\n type=\"button\"\n aria-label={ariaLabel}\n className={\n stretchedLinkClass +\n ' ds:contents ds:bg-transparent ds:border-0 ds:p-0 ds:text-start'\n }\n onClick={() => onActivate?.(item)}\n >\n {titleNode}\n </button>\n );\n } else {\n activator = titleNode;\n }\n\n // Treatment / tooth-element Tags. Each treatment becomes one Tag; the\n // tooth elements (when present) are appended to the visible label.\n const treatmentTags = (item.treatments ?? []).map((tx, index) => {\n const label =\n tx.elements && tx.elements.length > 0\n ? t('appointmentCard.treatmentWithElements', {\n care: tx.careName,\n elements: tx.elements.join(', '),\n })\n : tx.careName;\n return (\n <Tag\n key={`${tx.careName}-${index}`}\n label={label}\n variant=\"neutral\"\n fill=\"outline\"\n size=\"sm\"\n maxInlineSize=\"12rem\"\n />\n );\n });\n\n const advanceCents = item.bookingReason?.advanceAmountCents;\n const hasAdvance = typeof advanceCents === 'number' && advanceCents > 0;\n\n return (\n <div\n ref={ref}\n role=\"listitem\"\n data-component=\"appointment-card\"\n data-component-id={item.id}\n data-state={item.state}\n data-type={item.type}\n className={appointmentCardVariants({ size, interactive, className })}\n {...rest}\n >\n {/* Leading rail: type glyph + start time + duration */}\n <span className=\"ds:flex ds:w-16 ds:shrink-0 ds:flex-col ds:items-start ds:gap-[var(--spacing-2xs)]\">\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] ds:text-[color:var(--muted-foreground)]\">\n <TypeIcon aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n <span className=\"ds:sr-only\">\n {t(`appointmentCard.type.${item.type}`)}\n </span>\n <Timestamp\n value={item.startIso}\n format=\"absolute\"\n shape=\"bare\"\n locale={locale}\n absoluteFormat={{ hour: 'numeric', minute: '2-digit' }}\n className=\"type-body-sm ds:font-medium ds:text-[color:var(--foreground)] ds:tabular-nums\"\n />\n </span>\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {t('appointmentCard.duration', { count: item.durationMin })}\n </span>\n </span>\n\n {/* Operator avatar with colour dot */}\n {operatorName ? (\n <span className=\"ds:relative ds:shrink-0\">\n <Avatar\n name={operatorName}\n color={colorIdToToken(item.operator?.colorId)}\n size={avatarSize}\n aria-hidden=\"true\"\n role=\"presentation\"\n aria-label={undefined}\n />\n <span\n aria-hidden=\"true\"\n className={\n 'ds:absolute ds:bottom-0 ds:end-0 ds:size-2.5 ds:rounded-[var(--radius-full)] ds:border ds:border-[color:var(--card)] ' +\n operatorDotClass(item.operator?.colorId)\n }\n />\n </span>\n ) : null}\n\n {/* Main content column */}\n <div className=\"ds:flex-1 ds:min-w-0 ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex-1 ds:min-w-0 ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n {item.patient?.avatarUrl ? (\n <Avatar\n name={patientName}\n src={item.patient.avatarUrl}\n size={avatarSize}\n className=\"ds:shrink-0\"\n aria-hidden=\"true\"\n role=\"presentation\"\n aria-label={undefined}\n />\n ) : null}\n {activator}\n </div>\n <Badge\n variant={appointmentStateBadge(item.state)}\n size=\"sm\"\n withDot\n className=\"ds:shrink-0\"\n >\n {stateLabel}\n </Badge>\n </div>\n\n {/* Meta line: operator name · chair · specialty · first visit */}\n {operatorName ||\n item.chair ||\n item.bookingReason?.specialty ||\n item.firstVisit === 'yes' ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-x-[var(--spacing-sm)] ds:gap-y-[var(--spacing-2xs)] type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {operatorName ? <span>{operatorName}</span> : null}\n {item.chair ? (\n <span>{t('appointmentCard.chair', { chair: item.chair })}</span>\n ) : null}\n {item.bookingReason?.specialty ? (\n <span>{item.bookingReason.specialty}</span>\n ) : null}\n {item.firstVisit === 'yes' ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\">\n <Star aria-hidden=\"true\" className=\"ds:size-3 ds:shrink-0\" />\n {t('appointmentCard.firstVisit')}\n </span>\n ) : null}\n </div>\n ) : null}\n\n {/* Booking reason */}\n {item.bookingReason?.description ? (\n <span className=\"type-body-sm ds:text-[color:var(--foreground)] ds:truncate\">\n {item.bookingReason.description}\n </span>\n ) : null}\n\n {/* Treatment / tooth Tags */}\n {treatmentTags.length > 0 ? (\n <div className=\"ds:flex ds:flex-wrap ds:gap-[var(--spacing-2xs)]\">\n {treatmentTags}\n </div>\n ) : null}\n\n {/* Flag chips + advance-deposit chip */}\n {item.flags?.bookedOnline ||\n item.flags?.highlightedAnamnesis ||\n hasAdvance ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {item.flags?.bookedOnline ? (\n <Badge variant=\"info\" size=\"sm\" leading={<Globe />}>\n {t('appointmentCard.flag.bookedOnline')}\n </Badge>\n ) : null}\n {item.flags?.highlightedAnamnesis ? (\n <Badge variant=\"warning\" size=\"sm\" withDot>\n {t('appointmentCard.flag.highlightedAnamnesis')}\n </Badge>\n ) : null}\n {hasAdvance ? (\n <TransactionChip\n state=\"to-invoice\"\n amount={(advanceCents as number) / 100}\n currency={currency}\n locale={locale}\n size=\"sm\"\n showLabel\n />\n ) : null}\n </div>\n ) : null}\n </div>\n\n {interactive ? (\n <ChevronRight\n aria-hidden=\"true\"\n className=\"ds:size-4 ds:shrink-0 ds:self-center ds:text-[color:var(--muted-foreground)] ds:opacity-0 ds:transition-opacity ds:motion-reduce:transition-none ds:group-hover:opacity-100 ds:group-focus-within:opacity-100 ds:rtl:rotate-180\"\n />\n ) : null}\n </div>\n );\n },\n);\n\nAppointmentCard.displayName = 'AppointmentCard';\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — AppointmentCard. */\n/* */\n/* Single-appointment surface. Activation is owned by the consumer / */\n/* host tray; the card emits a DOM hook so a future MCP UI bridge can */\n/* address one rendered card by its appointment id. Read-only — the card */\n/* exposes no imperative handle of its own (the tray owns activation). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\n\nexport const appointmentCardAgent: AgentAdapter = {\n id: 'appointment-card',\n capabilities: ['pick'],\n state: {},\n actions: {},\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'appointment-card',\n description:\n 'Marks the element as a kit AppointmentCard. The root also emits data-component-id sourced from item.id, data-state with the appointment lifecycle, and data-type with the delivery channel.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'item.id',\n description: 'Sourced from item.id — the stable appointment identifier.',\n },\n },\n};\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — AppointmentTray. */\n/* */\n/* Tray-level surface: read appointment ids + lifecycle state (never */\n/* patient names / treatments), and route an activation through the */\n/* curated handle. AppointmentTray is in-page (no open/close). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { AppointmentTrayHandle } from './appointment-tray';\n\nexport const appointmentTrayAgent: AgentAdapter<AppointmentTrayHandle> = {\n id: 'appointment-tray',\n capabilities: ['select_single'],\n state: {\n items: {\n type: 'Array<{ id: string; state: string }>',\n descriptionKey: 'ui.agent.appointmentTray.state.items',\n description:\n 'Currently-displayed appointments. Ids + lifecycle state only — never patient identity.',\n read: (handle) => handle.getItems(),\n },\n },\n actions: {\n select_item: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.agent.appointmentTray.actions.selectItem',\n description: 'Activate the appointment with the given id.',\n invoke: (handle, args: { id: string }) => {\n handle.selectItem(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'appointment-tray',\n description: 'Marks the AppointmentTray wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific tray from the agent.',\n },\n item: {\n attr: 'data-appointment-id',\n description:\n 'Stable appointment id emitted on each rendered AppointmentCard inside the tray.',\n },\n },\n};\n","import {\n forwardRef,\n useImperativeHandle,\n useMemo,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { CalendarDays, Plus } from 'lucide-react';\nimport { IconButton } from '../button/icon-button';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { EmptyState } from '../empty-state/empty-state';\nimport { AppointmentCard, type AppointmentItem } from './appointment-card';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { appointmentTrayAgent } from './appointment-tray.agent';\n\n/* -------------------------------------------------------------------- */\n/* AppointmentTray */\n/* */\n/* Bordered panel that hosts a list of AppointmentCards — header with a */\n/* title + optional caption + an \"Add\" affordance, a body that branches */\n/* on loading (Skeleton) / empty (EmptyState) / list. Mirrors AgendaTray */\n/* but composes the richer AppointmentCard row. */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `appointmentTray.*` namespace. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* CVA — panel surface */\n/* -------------------------------------------------------------------- */\n\nconst appointmentTrayVariants = cva(\n [\n 'ds:flex ds:flex-col',\n 'ds:rounded-[var(--radius-md)] ds:shadow-[var(--shadow-card)]',\n 'ds:border ds:border-[color:var(--card-border)] ds:[.theme-accessible_&]:border-2',\n 'ds:bg-[color:var(--card)] ds:text-[color:var(--card-foreground)]',\n 'ds:overflow-hidden',\n ].join(' '),\n {\n variants: {\n size: {\n sm: '',\n md: '',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/**\n * Curated imperative handle for AppointmentTray — the forwardRef target so a\n * future agent / MCP UI bridge can drive the tray without touching the DOM.\n * Reads identifiers + lifecycle state only, never patient identity. See\n * `appointment-tray.agent.ts`.\n */\nexport interface AppointmentTrayHandle {\n getItems: () => Array<{ id: string; state: string }>;\n selectItem: (id: string) => void;\n}\n\nexport interface AppointmentTrayProps\n extends\n Omit<HTMLAttributes<HTMLElement>, 'onClick' | 'id'>,\n VariantProps<typeof appointmentTrayVariants> {\n /**\n * Stable instance id. Surfaced on the root as `data-component-id` so an\n * agent / MCP UI bridge can address this specific tray.\n */\n id?: string;\n /** The appointments to render. */\n items: AppointmentItem[];\n /** Section title — rendered in the header. Localised by the consumer. */\n title?: string;\n /** Optional small caption (e.g. localised date) shown next to the title. */\n caption?: string;\n /** Visual density passed through to each AppointmentCard. */\n size?: 'sm' | 'md';\n /** BCP-47 locale override passed to each AppointmentCard. */\n locale?: string;\n /** ISO-4217 currency code passed to each AppointmentCard. */\n currency?: string;\n onOpenAppointment?: (item: AppointmentItem) => void;\n onAddAppointment?: () => void;\n /** Localised label for the \"Add\" affordance. */\n addLabel?: string;\n loading?: boolean;\n emptyTitle?: string;\n emptyDescription?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const AppointmentTray = forwardRef<HTMLElement, AppointmentTrayProps>(\n (\n {\n id,\n items,\n title,\n caption,\n size = 'sm',\n locale,\n currency,\n onOpenAppointment,\n onAddAppointment,\n addLabel,\n loading = false,\n emptyTitle,\n emptyDescription,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const heading = title ?? t('appointmentTray.title');\n const resolvedAddLabel = addLabel ?? t('appointmentTray.add');\n const isEmpty = !loading && items.length === 0;\n\n let body: ReactNode;\n if (loading) {\n body = (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)]\">\n {[0, 1, 2].map((i) => (\n <Skeleton key={i} variant=\"rectangular\" height=\"3.5rem\" />\n ))}\n </div>\n );\n } else if (isEmpty) {\n body = (\n <div className=\"ds:p-[var(--spacing-md)]\">\n <EmptyState\n variant=\"first-use\"\n size=\"sm\"\n title={emptyTitle ?? t('appointmentTray.empty.title')}\n description={\n emptyDescription ?? t('appointmentTray.empty.description')\n }\n />\n </div>\n );\n } else {\n body = (\n <ul className=\"ds:flex ds:list-none ds:flex-col ds:gap-[var(--spacing-2xs)] ds:p-[var(--spacing-2xs)]\">\n {items.map((item) => (\n <AppointmentCard\n key={item.id}\n item={item}\n size={size}\n locale={locale}\n currency={currency}\n onActivate={onOpenAppointment}\n data-appointment-id={item.id}\n />\n ))}\n </ul>\n );\n }\n\n /* Curated imperative handle for agent integration. See\n * appointment-tray.agent.ts. */\n const itemsRef = useRef<AppointmentItem[]>(items);\n itemsRef.current = items;\n\n const innerRef = useRef<HTMLElement>(null);\n useImperativeHandle(ref, () => innerRef.current as HTMLElement, []);\n\n const handle = useMemo<AppointmentTrayHandle>(\n () => ({\n getItems: () =>\n itemsRef.current.map((i) => ({ id: i.id, state: i.state })),\n selectItem: (targetId: string) => {\n const found = itemsRef.current.find((i) => i.id === targetId);\n if (found) onOpenAppointment?.(found);\n },\n }),\n [onOpenAppointment],\n );\n\n useAgentRegistration(appointmentTrayAgent, handle, id);\n\n return (\n <section\n ref={innerRef}\n aria-label={heading}\n data-component=\"appointment-tray\"\n data-component-id={id}\n className={appointmentTrayVariants({ size, className })}\n {...rest}\n >\n <header className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-2xs)] ds:min-w-0\">\n <h3 className=\"ds:m-0 ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] type-title-card\">\n <CalendarDays aria-hidden=\"true\" className=\"ds:size-4\" />\n {heading}\n </h3>\n {caption ? (\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {caption}\n </span>\n ) : null}\n </div>\n {onAddAppointment ? (\n <IconButton\n icon={<Plus />}\n intent=\"outline\"\n size=\"sm\"\n aria-label={resolvedAddLabel}\n onClick={onAddAppointment}\n />\n ) : null}\n </header>\n {body}\n </section>\n );\n },\n);\n\nAppointmentTray.displayName = 'AppointmentTray';\n"],"names":["OPERATOR_DOT_CLASS","operatorDotClass","colorId","colorIdToToken","appointmentCardVariants","cva","stretchedLinkClass","TYPE_ICON","MapPin","Video","AppointmentCard","forwardRef","item","size","locale","currency","onActivate","className","rest","ref","t","useTranslation","interactive","hasSafeUrl","isSafeEntityUrl","patientName","_a","operatorName","_b","stateLabel","avatarSize","TypeIcon","ariaLabel","titleNode","jsx","activator","e","treatmentTags","tx","index","label","Tag","advanceCents","_c","hasAdvance","jsxs","Timestamp","Avatar","_d","_e","_f","Badge","appointmentStateBadge","_g","_h","Star","_i","_j","_k","_l","Globe","_m","TransactionChip","ChevronRight","appointmentCardAgent","appointmentTrayAgent","handle","args","appointmentTrayVariants","AppointmentTray","id","items","title","caption","onOpenAppointment","onAddAppointment","addLabel","loading","emptyTitle","emptyDescription","heading","resolvedAddLabel","isEmpty","body","i","Skeleton","EmptyState","itemsRef","useRef","innerRef","useImperativeHandle","useMemo","targetId","found","useAgentRegistration","CalendarDays","IconButton","Plus"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"appointment-tray-BMl89NPW.js","sources":["../../src/components/appointment-card/appointment-card.tsx","../../src/components/appointment-card/appointment-card.agent.ts","../../src/components/appointment-card/appointment-tray.agent.ts","../../src/components/appointment-card/appointment-tray.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { ChevronRight, Globe, MapPin, Star, Video } from 'lucide-react';\nimport { Avatar } from '../avatar/avatar';\nimport { Badge } from '../badge/badge';\nimport { Tag } from '../tag/tag';\nimport { Timestamp } from '../timestamp/timestamp';\nimport { TransactionChip } from '../transaction-chip/transaction-chip';\nimport {\n appointmentStateBadge,\n colorIdToToken,\n isSafeEntityUrl,\n} from '../_shared/entity-card';\nimport type { AppointmentState } from '../_shared/entity-card';\n\n/*\n * Closed colour-dot class map. `colorIdToToken` resolves the operator's\n * numeric colour id to one of a fixed set of palette tokens; the operator\n * dot needs a *static* Tailwind class for each so the compiler can emit it\n * (dynamic `ds:bg-[…]` strings are not seen by the JIT). The keys mirror the\n * exact token strings the shared helper returns.\n */\nconst OPERATOR_DOT_CLASS: Record<string, string> = {\n 'var(--color-violet-500)': 'ds:bg-[var(--color-violet-500)]',\n 'var(--color-purple-500)': 'ds:bg-[var(--color-purple-500)]',\n 'var(--color-magenta-500)': 'ds:bg-[var(--color-magenta-500)]',\n 'var(--color-blue-500)': 'ds:bg-[var(--color-blue-500)]',\n 'var(--color-green-500)': 'ds:bg-[var(--color-green-500)]',\n 'var(--color-red-500)': 'ds:bg-[var(--color-red-500)]',\n 'var(--color-yellow-500)': 'ds:bg-[var(--color-yellow-500)]',\n 'var(--color-indigo-500)': 'ds:bg-[var(--color-indigo-500)]',\n 'var(--color-orange-500)': 'ds:bg-[var(--color-orange-500)]',\n 'var(--color-fuchsia-500)': 'ds:bg-[var(--color-fuchsia-500)]',\n 'var(--color-grey-700)': 'ds:bg-[var(--color-grey-700)]',\n};\n\nfunction operatorDotClass(colorId: number | string | undefined): string {\n return (\n OPERATOR_DOT_CLASS[colorIdToToken(colorId)] ??\n 'ds:bg-[var(--color-grey-700)]'\n );\n}\n\n/* -------------------------------------------------------------------- */\n/* AppointmentCard */\n/* */\n/* Single-row appointment surface — sibling of AgendaCard, but richer: */\n/* leading timestamp, operator + patient avatars with the operator */\n/* colour dot, a lifecycle-state Badge (announced by label, never colour */\n/* alone), treatment / tooth Tags, and an optional advance-deposit */\n/* TransactionChip. State→Badge-variant + colour-id→token + URL-safety */\n/* come from the shared `_shared/entity-card` helpers. */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `appointmentCard.*` namespace (plus shared `financial.*` for money). */\n/* -------------------------------------------------------------------- */\n\n// Back-compat / one-stop import — the URL guard lives in the shared helper.\nexport { isSafeEntityUrl as isSafeAppointmentUrl } from '../_shared/is-safe-url';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** Appointment delivery channel. */\nexport type AppointmentType = 'inPractice' | 'telehealth';\n\n/** First-visit tri-state flag. */\nexport type AppointmentFirstVisit = 'yes' | 'no' | 'unknown';\n\n/** Operator (clinician) summary — name + optional palette colour id. */\nexport interface AppointmentOperator {\n /** Operator display name — drives the avatar initials + accessible name. */\n name: string;\n /** Numeric palette colour id, resolved to a kit token for the colour dot. */\n colorId?: number | string;\n}\n\n/** Patient summary — identity only, drives the avatar. */\nexport interface AppointmentPatient {\n /** Patient display name. */\n name: string;\n /** Optional avatar image URL. */\n avatarUrl?: string;\n}\n\n/** One treatment line within the appointment. */\nexport interface AppointmentTreatment {\n /** Localised care / treatment name. */\n careName: string;\n /** Optional tooth elements (e.g. FDI ids) the treatment targets. */\n elements?: string[];\n}\n\n/** Booking-reason metadata captured at online-booking time. */\nexport interface AppointmentBookingReason {\n /** Localised booking-reason description. */\n description: string;\n /** Expected duration in minutes. */\n durationMin?: number;\n /** Advance / deposit amount in cents (non-negative). */\n advanceAmountCents?: number;\n /** Localised specialty label. */\n specialty?: string;\n}\n\n/** Boolean appointment flags surfaced as chips / indicators. */\nexport interface AppointmentFlags {\n bookedOnline?: boolean;\n noPatientAssigned?: boolean;\n highlightedAnamnesis?: boolean;\n billable?: boolean;\n}\n\nexport interface AppointmentItem {\n /** Stable appointment identifier. */\n id: string;\n /** ISO-8601 start instant. */\n startIso: string;\n /** Duration in minutes. */\n durationMin: number;\n /** Lifecycle state — drives the Badge variant + label. */\n state: AppointmentState;\n /** Delivery channel — drives the leading type glyph. */\n type: AppointmentType;\n /** First-visit flag. */\n firstVisit?: AppointmentFirstVisit;\n /** Assigned operator. */\n operator?: AppointmentOperator;\n /** Chair / room label. */\n chair?: string;\n /** Patient identity. */\n patient?: AppointmentPatient;\n /** Treatment lines. */\n treatments?: AppointmentTreatment[];\n /** Online booking-reason metadata. */\n bookingReason?: AppointmentBookingReason;\n /** Boolean flags. */\n flags?: AppointmentFlags;\n /** Optional deep link — when present (and safe) the row renders as an anchor. */\n url?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* CVA — row */\n/* -------------------------------------------------------------------- */\n\nconst appointmentCardVariants = cva(\n [\n 'ds:relative ds:flex ds:items-start ds:gap-[var(--spacing-sm)]',\n 'ds:rounded-[var(--radius-md)]',\n 'ds:text-start ds:group',\n 'ds:bg-[var(--card)] ds:text-[color:var(--card-foreground)]',\n 'ds:transition-colors ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n 'ds:forced-colors:border ds:forced-colors:border-[CanvasText]',\n ].join(' '),\n {\n variants: {\n size: {\n sm: 'ds:p-[var(--spacing-sm)]',\n md: 'ds:p-[var(--spacing-md)]',\n },\n interactive: {\n true: 'ds:hover:bg-[color:var(--muted)]/40 ds:cursor-pointer',\n false: '',\n },\n },\n defaultVariants: {\n size: 'sm',\n interactive: false,\n },\n },\n);\n\nconst stretchedLinkClass = [\n 'ds:focus-visible:outline-none',\n \"ds:after:content-[''] ds:after:absolute ds:after:inset-0 ds:after:rounded-[var(--radius-md)]\",\n 'ds:after:pointer-events-auto',\n 'ds:focus-visible:after:outline-[length:var(--focus-ring-width)]',\n 'ds:focus-visible:after:outline-solid',\n 'ds:focus-visible:after:outline-[color:var(--ring)]',\n 'ds:focus-visible:after:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:after:outline-[CanvasText]',\n].join(' ');\n\nconst TYPE_ICON: Record<AppointmentType, typeof MapPin> = {\n inPractice: MapPin,\n telehealth: Video,\n};\n\n/* -------------------------------------------------------------------- */\n/* Props */\n/* -------------------------------------------------------------------- */\n\nexport interface AppointmentCardProps\n extends\n Omit<HTMLAttributes<HTMLDivElement>, 'onClick' | 'role' | 'title'>,\n VariantProps<typeof appointmentCardVariants> {\n /** The appointment to render. */\n item: AppointmentItem;\n /** Visual density. */\n size?: 'sm' | 'md';\n /** BCP-47 locale override for the timestamp + currency. */\n locale?: string;\n /** ISO-4217 currency code for the advance-deposit chip. Defaults to `EUR`. */\n currency?: string;\n /** Fires when the row is activated. */\n onActivate?: (item: AppointmentItem) => void;\n}\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const AppointmentCard = forwardRef<HTMLDivElement, AppointmentCardProps>(\n (\n {\n item,\n size = 'sm',\n locale,\n currency = 'EUR',\n onActivate,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const interactive = !!onActivate;\n const hasSafeUrl = !!item.url && isSafeEntityUrl(item.url);\n\n const patientName = item.patient?.name ?? t('appointmentCard.noPatient');\n const operatorName = item.operator?.name;\n const stateLabel = t(`appointmentCard.state.${item.state}`);\n\n const avatarSize = size === 'md' ? 'md' : 'sm';\n const TypeIcon = TYPE_ICON[item.type];\n\n const ariaLabel = t('appointmentCard.itemLabel', {\n patient: patientName,\n state: stateLabel,\n });\n\n const titleNode = (\n <span className=\"ds:min-w-0 ds:truncate type-title-item ds:text-[color:var(--foreground)]\">\n {patientName}\n </span>\n );\n\n let activator: ReactNode;\n if (interactive && hasSafeUrl) {\n activator = (\n <a\n href={item.url}\n aria-label={ariaLabel}\n className={stretchedLinkClass + ' ds:contents'}\n onClick={(e) => {\n if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey)\n return;\n onActivate?.(item);\n }}\n >\n {titleNode}\n </a>\n );\n } else if (interactive) {\n activator = (\n <button\n type=\"button\"\n aria-label={ariaLabel}\n className={\n stretchedLinkClass +\n ' ds:contents ds:bg-transparent ds:border-0 ds:p-0 ds:text-start'\n }\n onClick={() => onActivate?.(item)}\n >\n {titleNode}\n </button>\n );\n } else {\n activator = titleNode;\n }\n\n // Treatment / tooth-element Tags. Each treatment becomes one Tag; the\n // tooth elements (when present) are appended to the visible label.\n const treatmentTags = (item.treatments ?? []).map((tx, index) => {\n const label =\n tx.elements && tx.elements.length > 0\n ? t('appointmentCard.treatmentWithElements', {\n care: tx.careName,\n elements: tx.elements.join(', '),\n })\n : tx.careName;\n return (\n <Tag\n key={`${tx.careName}-${index}`}\n label={label}\n variant=\"neutral\"\n fill=\"outline\"\n size=\"sm\"\n maxInlineSize=\"12rem\"\n />\n );\n });\n\n const advanceCents = item.bookingReason?.advanceAmountCents;\n const hasAdvance = typeof advanceCents === 'number' && advanceCents > 0;\n\n return (\n <div\n ref={ref}\n role=\"listitem\"\n data-component=\"appointment-card\"\n data-component-id={item.id}\n data-state={item.state}\n data-type={item.type}\n className={appointmentCardVariants({ size, interactive, className })}\n {...rest}\n >\n {/* Leading rail: type glyph + start time + duration */}\n <span className=\"ds:flex ds:w-16 ds:shrink-0 ds:flex-col ds:items-start ds:gap-[var(--spacing-2xs)]\">\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] ds:text-[color:var(--muted-foreground)]\">\n <TypeIcon aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n <span className=\"ds:sr-only\">\n {t(`appointmentCard.type.${item.type}`)}\n </span>\n <Timestamp\n value={item.startIso}\n format=\"absolute\"\n shape=\"bare\"\n locale={locale}\n absoluteFormat={{ hour: 'numeric', minute: '2-digit' }}\n className=\"type-body-sm ds:font-medium ds:text-[color:var(--foreground)] ds:tabular-nums\"\n />\n </span>\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {t('appointmentCard.duration', { count: item.durationMin })}\n </span>\n </span>\n\n {/* Operator avatar with colour dot */}\n {operatorName ? (\n <span className=\"ds:relative ds:shrink-0\">\n <Avatar\n name={operatorName}\n color={colorIdToToken(item.operator?.colorId)}\n size={avatarSize}\n aria-hidden=\"true\"\n role=\"presentation\"\n aria-label={undefined}\n />\n <span\n aria-hidden=\"true\"\n className={\n 'ds:absolute ds:bottom-0 ds:end-0 ds:size-2.5 ds:rounded-[var(--radius-full)] ds:border ds:border-[color:var(--card)] ' +\n operatorDotClass(item.operator?.colorId)\n }\n />\n </span>\n ) : null}\n\n {/* Main content column */}\n <div className=\"ds:flex-1 ds:min-w-0 ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex-1 ds:min-w-0 ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n {item.patient?.avatarUrl ? (\n <Avatar\n name={patientName}\n src={item.patient.avatarUrl}\n size={avatarSize}\n className=\"ds:shrink-0\"\n aria-hidden=\"true\"\n role=\"presentation\"\n aria-label={undefined}\n />\n ) : null}\n {activator}\n </div>\n <Badge\n variant={appointmentStateBadge(item.state)}\n size=\"sm\"\n withDot\n className=\"ds:shrink-0\"\n >\n {stateLabel}\n </Badge>\n </div>\n\n {/* Meta line: operator name · chair · specialty · first visit */}\n {operatorName ||\n item.chair ||\n item.bookingReason?.specialty ||\n item.firstVisit === 'yes' ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-x-[var(--spacing-sm)] ds:gap-y-[var(--spacing-2xs)] type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {operatorName ? <span>{operatorName}</span> : null}\n {item.chair ? (\n <span>{t('appointmentCard.chair', { chair: item.chair })}</span>\n ) : null}\n {item.bookingReason?.specialty ? (\n <span>{item.bookingReason.specialty}</span>\n ) : null}\n {item.firstVisit === 'yes' ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-2xs)]\">\n <Star aria-hidden=\"true\" className=\"ds:size-3 ds:shrink-0\" />\n {t('appointmentCard.firstVisit')}\n </span>\n ) : null}\n </div>\n ) : null}\n\n {/* Booking reason */}\n {item.bookingReason?.description ? (\n <span className=\"type-body-sm ds:text-[color:var(--foreground)] ds:truncate\">\n {item.bookingReason.description}\n </span>\n ) : null}\n\n {/* Treatment / tooth Tags */}\n {treatmentTags.length > 0 ? (\n <div className=\"ds:flex ds:flex-wrap ds:gap-[var(--spacing-2xs)]\">\n {treatmentTags}\n </div>\n ) : null}\n\n {/* Flag chips + advance-deposit chip */}\n {item.flags?.bookedOnline ||\n item.flags?.highlightedAnamnesis ||\n hasAdvance ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-xs)]\">\n {item.flags?.bookedOnline ? (\n <Badge variant=\"info\" size=\"sm\" leading={<Globe />}>\n {t('appointmentCard.flag.bookedOnline')}\n </Badge>\n ) : null}\n {item.flags?.highlightedAnamnesis ? (\n <Badge variant=\"warning\" size=\"sm\" withDot>\n {t('appointmentCard.flag.highlightedAnamnesis')}\n </Badge>\n ) : null}\n {hasAdvance ? (\n <TransactionChip\n state=\"to-invoice\"\n amount={(advanceCents as number) / 100}\n currency={currency}\n locale={locale}\n size=\"sm\"\n showLabel\n />\n ) : null}\n </div>\n ) : null}\n </div>\n\n {interactive ? (\n <ChevronRight\n aria-hidden=\"true\"\n className=\"ds:size-4 ds:shrink-0 ds:self-center ds:text-[color:var(--muted-foreground)] ds:opacity-0 ds:transition-opacity ds:motion-reduce:transition-none ds:group-hover:opacity-100 ds:group-focus-within:opacity-100 ds:rtl:rotate-180\"\n />\n ) : null}\n </div>\n );\n },\n);\n\nAppointmentCard.displayName = 'AppointmentCard';\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — AppointmentCard. */\n/* */\n/* Single-appointment surface. Activation is owned by the consumer / */\n/* host tray; the card emits a DOM hook so a future MCP UI bridge can */\n/* address one rendered card by its appointment id. Read-only — the card */\n/* exposes no imperative handle of its own (the tray owns activation). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\n\nexport const appointmentCardAgent: AgentAdapter = {\n id: 'appointment-card',\n capabilities: ['pick'],\n state: {},\n actions: {},\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'appointment-card',\n description:\n 'Marks the element as a kit AppointmentCard. The root also emits data-component-id sourced from item.id, data-state with the appointment lifecycle, and data-type with the delivery channel.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'item.id',\n description: 'Sourced from item.id — the stable appointment identifier.',\n },\n },\n};\n","/* -------------------------------------------------------------------- */\n/* Agent adapter — AppointmentTray. */\n/* */\n/* Tray-level surface: read appointment ids + lifecycle state (never */\n/* patient names / treatments), and route an activation through the */\n/* curated handle. AppointmentTray is in-page (no open/close). */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { AppointmentTrayHandle } from './appointment-tray';\n\nexport const appointmentTrayAgent: AgentAdapter<AppointmentTrayHandle> = {\n id: 'appointment-tray',\n capabilities: ['select_single'],\n state: {\n items: {\n type: 'Array<{ id: string; state: string }>',\n descriptionKey: 'ui.agent.appointmentTray.state.items',\n description:\n 'Currently-displayed appointments. Ids + lifecycle state only — never patient identity.',\n read: (handle) => handle.getItems(),\n },\n },\n actions: {\n select_item: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.agent.appointmentTray.actions.selectItem',\n description: 'Activate the appointment with the given id.',\n invoke: (handle, args: { id: string }) => {\n handle.selectItem(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'appointment-tray',\n description: 'Marks the AppointmentTray wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific tray from the agent.',\n },\n item: {\n attr: 'data-appointment-id',\n description:\n 'Stable appointment id emitted on each rendered AppointmentCard inside the tray.',\n },\n },\n};\n","import {\n forwardRef,\n useImperativeHandle,\n useMemo,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { CalendarDays, Plus } from 'lucide-react';\nimport { IconButton } from '../button/icon-button';\nimport { Skeleton } from '../skeleton/skeleton';\nimport { EmptyState } from '../empty-state/empty-state';\nimport { AppointmentCard, type AppointmentItem } from './appointment-card';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { appointmentTrayAgent } from './appointment-tray.agent';\n\n/* -------------------------------------------------------------------- */\n/* AppointmentTray */\n/* */\n/* Bordered panel that hosts a list of AppointmentCards — header with a */\n/* title + optional caption + an \"Add\" affordance, a body that branches */\n/* on loading (Skeleton) / empty (EmptyState) / list. Mirrors AgendaTray */\n/* but composes the richer AppointmentCard row. */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `appointmentTray.*` namespace. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* CVA — panel surface */\n/* -------------------------------------------------------------------- */\n\nconst appointmentTrayVariants = cva(\n [\n 'ds:flex ds:flex-col',\n 'ds:rounded-[var(--radius-md)] ds:shadow-[var(--shadow-card)]',\n 'ds:border ds:border-[color:var(--card-border)] ds:[.theme-accessible_&]:border-2',\n 'ds:bg-[color:var(--card)] ds:text-[color:var(--card-foreground)]',\n 'ds:overflow-hidden',\n ].join(' '),\n {\n variants: {\n size: {\n sm: '',\n md: '',\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n },\n);\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/**\n * Curated imperative handle for AppointmentTray — the forwardRef target so a\n * future agent / MCP UI bridge can drive the tray without touching the DOM.\n * Reads identifiers + lifecycle state only, never patient identity. See\n * `appointment-tray.agent.ts`.\n */\nexport interface AppointmentTrayHandle {\n getItems: () => Array<{ id: string; state: string }>;\n selectItem: (id: string) => void;\n}\n\nexport interface AppointmentTrayProps\n extends\n Omit<HTMLAttributes<HTMLElement>, 'onClick' | 'id'>,\n VariantProps<typeof appointmentTrayVariants> {\n /**\n * Stable instance id. Surfaced on the root as `data-component-id` so an\n * agent / MCP UI bridge can address this specific tray.\n */\n id?: string;\n /** The appointments to render. */\n items: AppointmentItem[];\n /** Section title — rendered in the header. Localised by the consumer. */\n title?: string;\n /** Optional small caption (e.g. localised date) shown next to the title. */\n caption?: string;\n /** Visual density passed through to each AppointmentCard. */\n size?: 'sm' | 'md';\n /** BCP-47 locale override passed to each AppointmentCard. */\n locale?: string;\n /** ISO-4217 currency code passed to each AppointmentCard. */\n currency?: string;\n onOpenAppointment?: (item: AppointmentItem) => void;\n onAddAppointment?: () => void;\n /** Localised label for the \"Add\" affordance. */\n addLabel?: string;\n loading?: boolean;\n emptyTitle?: string;\n emptyDescription?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const AppointmentTray = forwardRef<HTMLElement, AppointmentTrayProps>(\n (\n {\n id,\n items,\n title,\n caption,\n size = 'sm',\n locale,\n currency,\n onOpenAppointment,\n onAddAppointment,\n addLabel,\n loading = false,\n emptyTitle,\n emptyDescription,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation();\n const heading = title ?? t('appointmentTray.title');\n const resolvedAddLabel = addLabel ?? t('appointmentTray.add');\n const isEmpty = !loading && items.length === 0;\n\n let body: ReactNode;\n if (loading) {\n body = (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)]\">\n {[0, 1, 2].map((i) => (\n <Skeleton key={i} variant=\"rectangular\" height=\"3.5rem\" />\n ))}\n </div>\n );\n } else if (isEmpty) {\n body = (\n <div className=\"ds:p-[var(--spacing-md)]\">\n <EmptyState\n variant=\"first-use\"\n size=\"sm\"\n title={emptyTitle ?? t('appointmentTray.empty.title')}\n description={\n emptyDescription ?? t('appointmentTray.empty.description')\n }\n />\n </div>\n );\n } else {\n body = (\n <ul className=\"ds:flex ds:list-none ds:flex-col ds:gap-[var(--spacing-2xs)] ds:p-[var(--spacing-2xs)]\">\n {items.map((item) => (\n <AppointmentCard\n key={item.id}\n item={item}\n size={size}\n locale={locale}\n currency={currency}\n onActivate={onOpenAppointment}\n data-appointment-id={item.id}\n />\n ))}\n </ul>\n );\n }\n\n /* Curated imperative handle for agent integration. See\n * appointment-tray.agent.ts. */\n const itemsRef = useRef<AppointmentItem[]>(items);\n itemsRef.current = items;\n\n const innerRef = useRef<HTMLElement>(null);\n useImperativeHandle(ref, () => innerRef.current as HTMLElement, []);\n\n const handle = useMemo<AppointmentTrayHandle>(\n () => ({\n getItems: () =>\n itemsRef.current.map((i) => ({ id: i.id, state: i.state })),\n selectItem: (targetId: string) => {\n const found = itemsRef.current.find((i) => i.id === targetId);\n if (found) onOpenAppointment?.(found);\n },\n }),\n [onOpenAppointment],\n );\n\n useAgentRegistration(appointmentTrayAgent, handle, id);\n\n return (\n <section\n ref={innerRef}\n aria-label={heading}\n data-component=\"appointment-tray\"\n data-component-id={id}\n className={appointmentTrayVariants({ size, className })}\n {...rest}\n >\n <header className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)] ds:p-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-2xs)] ds:min-w-0\">\n <h3 className=\"ds:m-0 ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] type-title-card\">\n <CalendarDays aria-hidden=\"true\" className=\"ds:size-4\" />\n {heading}\n </h3>\n {caption ? (\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {caption}\n </span>\n ) : null}\n </div>\n {onAddAppointment ? (\n <IconButton\n icon={<Plus />}\n intent=\"outline\"\n size=\"sm\"\n aria-label={resolvedAddLabel}\n onClick={onAddAppointment}\n />\n ) : null}\n </header>\n {body}\n </section>\n );\n },\n);\n\nAppointmentTray.displayName = 'AppointmentTray';\n"],"names":["OPERATOR_DOT_CLASS","operatorDotClass","colorId","colorIdToToken","appointmentCardVariants","cva","stretchedLinkClass","TYPE_ICON","MapPin","Video","AppointmentCard","forwardRef","item","size","locale","currency","onActivate","className","rest","ref","t","useTranslation","interactive","hasSafeUrl","isSafeEntityUrl","patientName","_a","operatorName","_b","stateLabel","avatarSize","TypeIcon","ariaLabel","titleNode","jsx","activator","e","treatmentTags","tx","index","label","Tag","advanceCents","_c","hasAdvance","jsxs","Timestamp","Avatar","_d","_e","_f","Badge","appointmentStateBadge","_g","_h","Star","_i","_j","_k","_l","Globe","_m","TransactionChip","ChevronRight","appointmentCardAgent","appointmentTrayAgent","handle","args","appointmentTrayVariants","AppointmentTray","id","items","title","caption","onOpenAppointment","onAddAppointment","addLabel","loading","emptyTitle","emptyDescription","heading","resolvedAddLabel","isEmpty","body","i","Skeleton","EmptyState","itemsRef","useRef","innerRef","useImperativeHandle","useMemo","targetId","found","useAgentRegistration","CalendarDays","IconButton","Plus"],"mappings":"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"}
|
|
@@ -5,9 +5,9 @@ import { B as N } from "./badge-CqGsX32l.js";
|
|
|
5
5
|
import { L as F } from "./link-DrD_cRUg.js";
|
|
6
6
|
import { P as A } from "./progress-kzIRcdaq.js";
|
|
7
7
|
import { T as C } from "./timestamp-BV2lC-wV.js";
|
|
8
|
-
import { T as L } from "./transaction-chip-
|
|
8
|
+
import { T as L } from "./transaction-chip-DK84XCBU.js";
|
|
9
9
|
import { A as O, b as G, c as Y, a as $ } from "./accordion-9lJSMMsZ.js";
|
|
10
|
-
import { C as q } from "./care-plan-entry-card-
|
|
10
|
+
import { C as q } from "./care-plan-entry-card-VzdQHcOL.js";
|
|
11
11
|
import { u as J } from "./registry-nPAVE19X.js";
|
|
12
12
|
import { C as Q, e as W } from "./entity-card-COhmqHly.js";
|
|
13
13
|
import { i as X } from "./is-safe-url-DkETxeHz.js";
|
|
@@ -249,4 +249,4 @@ export {
|
|
|
249
249
|
de as C,
|
|
250
250
|
Z as c
|
|
251
251
|
};
|
|
252
|
-
//# sourceMappingURL=care-plan-card-
|
|
252
|
+
//# sourceMappingURL=care-plan-card-BwVUyUBb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"care-plan-card-CJei-nl9.js","sources":["../../src/components/care-plan-card/care-plan-card.agent.ts","../../src/components/care-plan-card/care-plan-card.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — CarePlanCard. */\n/* */\n/* Read-only surface: expose the plan's lifecycle state and line-progress */\n/* counts so an agent can introspect a care plan's status. State enum + */\n/* counts only — never the plan name, operator, treatment names, or any */\n/* patient identity. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { CarePlanCardHandle } from './care-plan-card';\n\nexport const carePlanCardAgent: AgentAdapter<CarePlanCardHandle> = {\n id: 'care-plan-card',\n capabilities: ['view_change', 'expand'],\n state: {\n state: {\n type: \"'estimate' | 'accepted' | 'discarded' | 'closed'\",\n descriptionKey: 'ui.agent.carePlanCard.state.state',\n description:\n 'The plan lifecycle state. Enum value only — never the localised label or any PHI.',\n read: (handle) => handle.getState(),\n },\n progress: {\n type: '{ done: number; total: number }',\n descriptionKey: 'ui.agent.carePlanCard.state.progress',\n description:\n 'Line progress as done / total counts. Counts only — never treatment names or operator identity.',\n read: (handle) => handle.getProgress(),\n },\n },\n actions: {},\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'care-plan-card',\n description: 'Marks the CarePlanCard root region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop. Addresses a specific plan.',\n },\n },\n};\n","import { forwardRef, useId, type HTMLAttributes } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Badge } from '../badge/badge';\nimport { Link } from '../link/link';\nimport { Progress } from '../progress/progress';\nimport { Timestamp } from '../timestamp/timestamp';\nimport { TransactionChip } from '../transaction-chip/transaction-chip';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '../accordion/accordion';\nimport {\n CarePlanEntryCard,\n type CarePlanEntryCardProps,\n} from '../care-plan-entry-card';\nimport {\n entitySurfaceVariants,\n CARE_PLAN_STATE_BADGE,\n isSafeEntityUrl,\n type CarePlanState,\n} from '../_shared/entity-card';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { carePlanCardAgent } from './care-plan-card.agent';\n\n/* -------------------------------------------------------------------- */\n/* CarePlanCard */\n/* */\n/* The summary card for a whole care plan / estimate (\"preventivo\"). */\n/* Replaces the hand-rolled platform care-plan header that interleaves */\n/* the plan number + name, a lifecycle badge, the financial figures */\n/* (billed / due / overdue), an acceptance/closure date, a phase- */\n/* progress bar, an optional deep link, and an expandable list of the */\n/* plan's treatment lines. */\n/* */\n/* Composes: entitySurfaceVariants (shared card surface) + Badge */\n/* (lifecycle state) + TransactionChip (money) + Progress (done/total) */\n/* + Timestamp (dates) + Link (deep link) + Accordion (entries rendered */\n/* as <CarePlanEntryCard> rows). */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `carePlanCard.*` namespace — the lifecycle state is ALWAYS shown as a */\n/* localised label, never the raw server string. Identifiers + structural */\n/* state only reach the agent layer — never patient identity. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\n/**\n * A single treatment line within the plan. Each ref is forwarded straight\n * to a `<CarePlanEntryCard>` rendered inside the Accordion. `entryId` keys\n * the row and surfaces as the line's `data-component-id`.\n */\nexport interface CarePlanEntryRef extends Omit<\n CarePlanEntryCardProps,\n 'id' | 'surface'\n> {\n /** Stable line id — identifier only, never PHI. */\n entryId: string;\n}\n\n/** Financial figures for the plan, all amounts in integer cents. */\nexport interface CarePlanAmounts {\n /** Full list price in cents (non-negative). */\n priceCents?: number;\n /** Discounted / agreed price in cents (non-negative). */\n discountedPriceCents?: number;\n /** Amount already invoiced in cents (non-negative). */\n billedAmountCents?: number;\n /** Outstanding amount still due in cents (non-negative). */\n dueCents?: number;\n /** How many days the due amount is overdue (non-negative). */\n overdueDays?: number;\n}\n\n/** Phase / line progress for the plan. */\nexport interface CarePlanProgress {\n /** Number of executed / completed lines. */\n done: number;\n /** Total number of lines. */\n total: number;\n}\n\n/** Lifecycle dates for the plan, all ISO-8601 strings. */\nexport interface CarePlanDates {\n /** When the plan was accepted. */\n acceptedIso?: string;\n /** When the plan was closed. */\n closedIso?: string;\n /** When the plan was discarded / rejected. */\n discardedIso?: string;\n}\n\n/** Curated read-only handle exposed to the agent registry. */\nexport interface CarePlanCardHandle {\n /** The plan's lifecycle state. */\n getState: () => CarePlanState;\n /** Line progress as `{ done, total }` — counts only, no PHI. */\n getProgress: () => { done: number; total: number };\n}\n\nexport interface CarePlanCardProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'aria-label' | 'title' | 'children'\n> {\n /**\n * Stable instance id — surfaced as `data-component-id` for the agent\n * registry and mirrored to the DOM `id`; also wires the heading ↔ region\n * relationship.\n */\n id?: string;\n /** Plan number (e.g. estimate number). Rendered beside the name. */\n number: string | number;\n /** Plan name / title. Rendered as the card heading. */\n name?: string;\n /** Lifecycle state — drives a localised Badge label. */\n state: CarePlanState;\n /** Optional deep link to the full plan. Guarded by `isSafeEntityUrl`. */\n url?: string;\n /** Financial figures (billed / due / overdue …). */\n amounts?: CarePlanAmounts;\n /** Line progress (done / total). */\n progress?: CarePlanProgress;\n /** Lifecycle dates (accepted / closed / discarded). */\n dates?: CarePlanDates;\n /** Reason the plan was discarded — shown only when `state === 'discarded'`. */\n discardReason?: string;\n /**\n * Dentition the plan applies to — forwarded to each entry card's tooth\n * display where relevant. Structural only.\n */\n dentitionMode?: 'permanent' | 'deciduous';\n /** Treatment lines — rendered as `<CarePlanEntryCard>` rows in an Accordion. */\n entries?: CarePlanEntryRef[];\n /** BCP 47 locale override for dates / currency. Defaults to i18n.language. */\n locale?: string;\n /** ISO 4217 currency code for the money chips. Defaults to `EUR`. */\n currency?: string;\n /**\n * `elevated` (default) paints the kit card chrome; `flat` drops it for use\n * inside an existing Card / Sheet.\n */\n surface?: 'flat' | 'elevated';\n /** Fires when the plan deep link / heading is activated. */\n onActivate?: () => void;\n /** Accessible label override for the plan region. */\n 'aria-label'?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\nconst DEFAULT_CURRENCY = 'EUR';\n\n/**\n * English fallbacks for the lifecycle-state labels. The canonical strings\n * live in the `ui.carePlanCard.state.*` bundle; these inline defaults guard\n * against a stale / not-yet-wired bundle so a raw key never reaches the UI.\n */\nconst STATE_LABEL_FALLBACK: Record<CarePlanState, string> = {\n estimate: 'Estimate',\n accepted: 'Accepted',\n discarded: 'Discarded',\n closed: 'Closed',\n};\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const CarePlanCard = forwardRef<HTMLDivElement, CarePlanCardProps>(\n (\n {\n id,\n number,\n name,\n state,\n url,\n amounts,\n progress,\n dates,\n discardReason,\n dentitionMode: _dentitionMode,\n entries,\n locale,\n currency = DEFAULT_CURRENCY,\n surface = 'elevated',\n onActivate,\n 'aria-label': ariaLabel,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n const headingId = useId();\n const progressLabelId = useId();\n const activeLocale = locale ?? i18n.language ?? 'en';\n\n useAgentRegistration(\n carePlanCardAgent,\n {\n getState: () => state,\n getProgress: () => ({\n done: progress?.done ?? 0,\n total: progress?.total ?? 0,\n }),\n },\n id,\n );\n\n const stateLabel = t(`carePlanCard.state.${state}`, {\n defaultValue: STATE_LABEL_FALLBACK[state],\n });\n const stateBadgeVariant = CARE_PLAN_STATE_BADGE[state];\n const hasSafeUrl = !!url && isSafeEntityUrl(url);\n const hasEntries = !!entries && entries.length > 0;\n\n // Money: prefer the outstanding \"due\" chip; mark it overdue when it has\n // run past its term. Anything billed shows a settled chip alongside.\n const dueCents = amounts?.dueCents;\n const billedCents = amounts?.billedAmountCents;\n const overdueDays = amounts?.overdueDays;\n const isOverdue =\n typeof overdueDays === 'number' &&\n overdueDays > 0 &&\n typeof dueCents === 'number' &&\n dueCents > 0;\n\n const headingNode = (\n <h3\n id={headingId}\n className=\"type-title-card ds:m-0 ds:text-[color:var(--foreground)]\"\n >\n {name ?? t('carePlanCard.untitled', 'Untitled plan')}\n </h3>\n );\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabel ? undefined : headingId}\n data-component=\"care-plan-card\"\n data-component-id={id}\n data-state={state}\n id={id}\n className={entitySurfaceVariants({ surface, className })}\n {...rest}\n >\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n {/* Heading row: number + name + lifecycle badge */}\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)] ds:tabular-nums\">\n {t('carePlanCard.number', {\n number,\n defaultValue: 'Plan #{{number}}',\n })}\n </span>\n {hasSafeUrl ? (\n <Link\n href={url as string}\n weight=\"strong\"\n onClick={(event) => {\n if (\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey\n ) {\n return;\n }\n onActivate?.();\n }}\n >\n {headingNode}\n </Link>\n ) : (\n headingNode\n )}\n </div>\n\n <Badge\n variant={stateBadgeVariant}\n size=\"sm\"\n withDot\n className=\"ds:shrink-0\"\n >\n {stateLabel}\n </Badge>\n </div>\n\n {/* Discard reason — only when discarded */}\n {state === 'discarded' && discardReason ? (\n <p className=\"type-body-sm ds:m-0 ds:text-[color:var(--muted-foreground)]\">\n <span className=\"ds:font-medium ds:text-[color:var(--foreground)]\">\n {t('carePlanCard.discardReasonLabel', 'Discard reason:')}\n </span>{' '}\n {discardReason}\n </p>\n ) : null}\n\n {/* Lifecycle date line */}\n {dates ? (\n <div className=\"type-body-sm ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-md)] ds:text-[color:var(--muted-foreground)]\">\n {dates.acceptedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.accepted', 'Accepted')}</span>\n <Timestamp\n value={dates.acceptedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n {dates.closedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.closed', 'Closed')}</span>\n <Timestamp\n value={dates.closedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n {dates.discardedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.discarded', 'Discarded')}</span>\n <Timestamp\n value={dates.discardedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n </div>\n ) : null}\n\n {/* Money: due / overdue / billed chips */}\n {typeof dueCents === 'number' || typeof billedCents === 'number' ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-sm)]\">\n {typeof dueCents === 'number' && dueCents > 0 ? (\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <TransactionChip\n state=\"debt\"\n amount={dueCents / 100}\n currency={currency}\n locale={activeLocale}\n showLabel\n size=\"md\"\n />\n {isOverdue ? (\n <Badge variant=\"error\" size=\"sm\" withDot>\n {t('carePlanCard.overdue', {\n count: overdueDays,\n defaultValue: 'Overdue {{count}}d',\n })}\n </Badge>\n ) : null}\n </div>\n ) : null}\n {typeof billedCents === 'number' && billedCents > 0 ? (\n <TransactionChip\n state=\"settled\"\n amount={billedCents / 100}\n currency={currency}\n locale={activeLocale}\n showLabel\n size=\"md\"\n />\n ) : null}\n </div>\n ) : null}\n\n {/* Line / phase progress */}\n {progress && progress.total > 0 ? (\n <Progress\n value={progress.done}\n max={progress.total}\n size=\"sm\"\n aria-label={t('carePlanCard.progressLabel', {\n done: progress.done,\n total: progress.total,\n defaultValue: 'Progress: {{done}} of {{total}} completed',\n })}\n >\n <div className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <Progress.Label id={progressLabelId}>\n {t('carePlanCard.progressHeading', 'Progress')}\n </Progress.Label>\n <span className=\"type-body-sm ds:tabular-nums ds:text-[color:var(--muted-foreground)]\">\n {t('carePlanCard.progressCount', {\n done: progress.done,\n total: progress.total,\n defaultValue: '{{done}} / {{total}}',\n })}\n </span>\n </div>\n </Progress>\n ) : null}\n\n {/* Entries — expandable list of treatment lines */}\n {hasEntries ? (\n <Accordion type=\"single\" collapsible variant=\"separated\">\n <AccordionItem value=\"entries\">\n <AccordionTrigger>\n {t('carePlanCard.entries.toggle', {\n count: entries.length,\n defaultValue: 'Treatment lines ({{count}})',\n })}\n </AccordionTrigger>\n <AccordionContent>\n <ul className=\"ds:m-0 ds:flex ds:list-none ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-0\">\n {entries.map(({ entryId, ...entry }) => (\n <li key={entryId}>\n <CarePlanEntryCard\n id={entryId}\n surface=\"flat\"\n locale={activeLocale}\n currency={currency}\n {...entry}\n />\n </li>\n ))}\n </ul>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n ) : null}\n </div>\n </div>\n );\n },\n);\n\nCarePlanCard.displayName = 'CarePlanCard';\n\nexport type CarePlanCardSurface = NonNullable<\n VariantProps<typeof entitySurfaceVariants>['surface']\n>;\n\n/** The dentition the plan applies to. */\nexport type CarePlanCardDentition = NonNullable<\n CarePlanCardProps['dentitionMode']\n>;\n\n/* Re-export the shared lifecycle-state type for downstream typing. */\nexport type { CarePlanState } from '../_shared/entity-card';\n"],"names":["carePlanCardAgent","handle","DEFAULT_CURRENCY","STATE_LABEL_FALLBACK","CarePlanCard","forwardRef","id","number","name","state","url","amounts","progress","dates","discardReason","_dentitionMode","entries","locale","currency","surface","onActivate","ariaLabel","className","rest","ref","t","i18n","useTranslation","headingId","useId","progressLabelId","activeLocale","useAgentRegistration","stateLabel","stateBadgeVariant","CARE_PLAN_STATE_BADGE","hasSafeUrl","isSafeEntityUrl","hasEntries","dueCents","billedCents","overdueDays","isOverdue","headingNode","jsx","entitySurfaceVariants","jsxs","Link","event","Badge","Timestamp","TransactionChip","Progress","Accordion","AccordionItem","AccordionTrigger","AccordionContent","entryId","entry","CarePlanEntryCard"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"care-plan-card-BwVUyUBb.js","sources":["../../src/components/care-plan-card/care-plan-card.agent.ts","../../src/components/care-plan-card/care-plan-card.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — CarePlanCard. */\n/* */\n/* Read-only surface: expose the plan's lifecycle state and line-progress */\n/* counts so an agent can introspect a care plan's status. State enum + */\n/* counts only — never the plan name, operator, treatment names, or any */\n/* patient identity. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { CarePlanCardHandle } from './care-plan-card';\n\nexport const carePlanCardAgent: AgentAdapter<CarePlanCardHandle> = {\n id: 'care-plan-card',\n capabilities: ['view_change', 'expand'],\n state: {\n state: {\n type: \"'estimate' | 'accepted' | 'discarded' | 'closed'\",\n descriptionKey: 'ui.agent.carePlanCard.state.state',\n description:\n 'The plan lifecycle state. Enum value only — never the localised label or any PHI.',\n read: (handle) => handle.getState(),\n },\n progress: {\n type: '{ done: number; total: number }',\n descriptionKey: 'ui.agent.carePlanCard.state.progress',\n description:\n 'Line progress as done / total counts. Counts only — never treatment names or operator identity.',\n read: (handle) => handle.getProgress(),\n },\n },\n actions: {},\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'care-plan-card',\n description: 'Marks the CarePlanCard root region.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop. Addresses a specific plan.',\n },\n },\n};\n","import { forwardRef, useId, type HTMLAttributes } from 'react';\nimport { type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Badge } from '../badge/badge';\nimport { Link } from '../link/link';\nimport { Progress } from '../progress/progress';\nimport { Timestamp } from '../timestamp/timestamp';\nimport { TransactionChip } from '../transaction-chip/transaction-chip';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '../accordion/accordion';\nimport {\n CarePlanEntryCard,\n type CarePlanEntryCardProps,\n} from '../care-plan-entry-card';\nimport {\n entitySurfaceVariants,\n CARE_PLAN_STATE_BADGE,\n isSafeEntityUrl,\n type CarePlanState,\n} from '../_shared/entity-card';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { carePlanCardAgent } from './care-plan-card.agent';\n\n/* -------------------------------------------------------------------- */\n/* CarePlanCard */\n/* */\n/* The summary card for a whole care plan / estimate (\"preventivo\"). */\n/* Replaces the hand-rolled platform care-plan header that interleaves */\n/* the plan number + name, a lifecycle badge, the financial figures */\n/* (billed / due / overdue), an acceptance/closure date, a phase- */\n/* progress bar, an optional deep link, and an expandable list of the */\n/* plan's treatment lines. */\n/* */\n/* Composes: entitySurfaceVariants (shared card surface) + Badge */\n/* (lifecycle state) + TransactionChip (money) + Progress (done/total) */\n/* + Timestamp (dates) + Link (deep link) + Accordion (entries rendered */\n/* as <CarePlanEntryCard> rows). */\n/* */\n/* All visible text flows through useTranslation('ui') against the */\n/* `carePlanCard.*` namespace — the lifecycle state is ALWAYS shown as a */\n/* localised label, never the raw server string. Identifiers + structural */\n/* state only reach the agent layer — never patient identity. */\n/* -------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------- */\n/* Public types */\n/* -------------------------------------------------------------------- */\n\n/**\n * A single treatment line within the plan. Each ref is forwarded straight\n * to a `<CarePlanEntryCard>` rendered inside the Accordion. `entryId` keys\n * the row and surfaces as the line's `data-component-id`.\n */\nexport interface CarePlanEntryRef extends Omit<\n CarePlanEntryCardProps,\n 'id' | 'surface'\n> {\n /** Stable line id — identifier only, never PHI. */\n entryId: string;\n}\n\n/** Financial figures for the plan, all amounts in integer cents. */\nexport interface CarePlanAmounts {\n /** Full list price in cents (non-negative). */\n priceCents?: number;\n /** Discounted / agreed price in cents (non-negative). */\n discountedPriceCents?: number;\n /** Amount already invoiced in cents (non-negative). */\n billedAmountCents?: number;\n /** Outstanding amount still due in cents (non-negative). */\n dueCents?: number;\n /** How many days the due amount is overdue (non-negative). */\n overdueDays?: number;\n}\n\n/** Phase / line progress for the plan. */\nexport interface CarePlanProgress {\n /** Number of executed / completed lines. */\n done: number;\n /** Total number of lines. */\n total: number;\n}\n\n/** Lifecycle dates for the plan, all ISO-8601 strings. */\nexport interface CarePlanDates {\n /** When the plan was accepted. */\n acceptedIso?: string;\n /** When the plan was closed. */\n closedIso?: string;\n /** When the plan was discarded / rejected. */\n discardedIso?: string;\n}\n\n/** Curated read-only handle exposed to the agent registry. */\nexport interface CarePlanCardHandle {\n /** The plan's lifecycle state. */\n getState: () => CarePlanState;\n /** Line progress as `{ done, total }` — counts only, no PHI. */\n getProgress: () => { done: number; total: number };\n}\n\nexport interface CarePlanCardProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'aria-label' | 'title' | 'children'\n> {\n /**\n * Stable instance id — surfaced as `data-component-id` for the agent\n * registry and mirrored to the DOM `id`; also wires the heading ↔ region\n * relationship.\n */\n id?: string;\n /** Plan number (e.g. estimate number). Rendered beside the name. */\n number: string | number;\n /** Plan name / title. Rendered as the card heading. */\n name?: string;\n /** Lifecycle state — drives a localised Badge label. */\n state: CarePlanState;\n /** Optional deep link to the full plan. Guarded by `isSafeEntityUrl`. */\n url?: string;\n /** Financial figures (billed / due / overdue …). */\n amounts?: CarePlanAmounts;\n /** Line progress (done / total). */\n progress?: CarePlanProgress;\n /** Lifecycle dates (accepted / closed / discarded). */\n dates?: CarePlanDates;\n /** Reason the plan was discarded — shown only when `state === 'discarded'`. */\n discardReason?: string;\n /**\n * Dentition the plan applies to — forwarded to each entry card's tooth\n * display where relevant. Structural only.\n */\n dentitionMode?: 'permanent' | 'deciduous';\n /** Treatment lines — rendered as `<CarePlanEntryCard>` rows in an Accordion. */\n entries?: CarePlanEntryRef[];\n /** BCP 47 locale override for dates / currency. Defaults to i18n.language. */\n locale?: string;\n /** ISO 4217 currency code for the money chips. Defaults to `EUR`. */\n currency?: string;\n /**\n * `elevated` (default) paints the kit card chrome; `flat` drops it for use\n * inside an existing Card / Sheet.\n */\n surface?: 'flat' | 'elevated';\n /** Fires when the plan deep link / heading is activated. */\n onActivate?: () => void;\n /** Accessible label override for the plan region. */\n 'aria-label'?: string;\n}\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\nconst DEFAULT_CURRENCY = 'EUR';\n\n/**\n * English fallbacks for the lifecycle-state labels. The canonical strings\n * live in the `ui.carePlanCard.state.*` bundle; these inline defaults guard\n * against a stale / not-yet-wired bundle so a raw key never reaches the UI.\n */\nconst STATE_LABEL_FALLBACK: Record<CarePlanState, string> = {\n estimate: 'Estimate',\n accepted: 'Accepted',\n discarded: 'Discarded',\n closed: 'Closed',\n};\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\nexport const CarePlanCard = forwardRef<HTMLDivElement, CarePlanCardProps>(\n (\n {\n id,\n number,\n name,\n state,\n url,\n amounts,\n progress,\n dates,\n discardReason,\n dentitionMode: _dentitionMode,\n entries,\n locale,\n currency = DEFAULT_CURRENCY,\n surface = 'elevated',\n onActivate,\n 'aria-label': ariaLabel,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n const headingId = useId();\n const progressLabelId = useId();\n const activeLocale = locale ?? i18n.language ?? 'en';\n\n useAgentRegistration(\n carePlanCardAgent,\n {\n getState: () => state,\n getProgress: () => ({\n done: progress?.done ?? 0,\n total: progress?.total ?? 0,\n }),\n },\n id,\n );\n\n const stateLabel = t(`carePlanCard.state.${state}`, {\n defaultValue: STATE_LABEL_FALLBACK[state],\n });\n const stateBadgeVariant = CARE_PLAN_STATE_BADGE[state];\n const hasSafeUrl = !!url && isSafeEntityUrl(url);\n const hasEntries = !!entries && entries.length > 0;\n\n // Money: prefer the outstanding \"due\" chip; mark it overdue when it has\n // run past its term. Anything billed shows a settled chip alongside.\n const dueCents = amounts?.dueCents;\n const billedCents = amounts?.billedAmountCents;\n const overdueDays = amounts?.overdueDays;\n const isOverdue =\n typeof overdueDays === 'number' &&\n overdueDays > 0 &&\n typeof dueCents === 'number' &&\n dueCents > 0;\n\n const headingNode = (\n <h3\n id={headingId}\n className=\"type-title-card ds:m-0 ds:text-[color:var(--foreground)]\"\n >\n {name ?? t('carePlanCard.untitled', 'Untitled plan')}\n </h3>\n );\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabel ? undefined : headingId}\n data-component=\"care-plan-card\"\n data-component-id={id}\n data-state={state}\n id={id}\n className={entitySurfaceVariants({ surface, className })}\n {...rest}\n >\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n {/* Heading row: number + name + lifecycle badge */}\n <div className=\"ds:flex ds:items-start ds:gap-[var(--spacing-sm)]\">\n <div className=\"ds:flex ds:min-w-0 ds:flex-1 ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)] ds:tabular-nums\">\n {t('carePlanCard.number', {\n number,\n defaultValue: 'Plan #{{number}}',\n })}\n </span>\n {hasSafeUrl ? (\n <Link\n href={url as string}\n weight=\"strong\"\n onClick={(event) => {\n if (\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey\n ) {\n return;\n }\n onActivate?.();\n }}\n >\n {headingNode}\n </Link>\n ) : (\n headingNode\n )}\n </div>\n\n <Badge\n variant={stateBadgeVariant}\n size=\"sm\"\n withDot\n className=\"ds:shrink-0\"\n >\n {stateLabel}\n </Badge>\n </div>\n\n {/* Discard reason — only when discarded */}\n {state === 'discarded' && discardReason ? (\n <p className=\"type-body-sm ds:m-0 ds:text-[color:var(--muted-foreground)]\">\n <span className=\"ds:font-medium ds:text-[color:var(--foreground)]\">\n {t('carePlanCard.discardReasonLabel', 'Discard reason:')}\n </span>{' '}\n {discardReason}\n </p>\n ) : null}\n\n {/* Lifecycle date line */}\n {dates ? (\n <div className=\"type-body-sm ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-md)] ds:text-[color:var(--muted-foreground)]\">\n {dates.acceptedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.accepted', 'Accepted')}</span>\n <Timestamp\n value={dates.acceptedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n {dates.closedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.closed', 'Closed')}</span>\n <Timestamp\n value={dates.closedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n {dates.discardedIso ? (\n <span className=\"ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <span>{t('carePlanCard.date.discarded', 'Discarded')}</span>\n <Timestamp\n value={dates.discardedIso}\n format=\"absolute\"\n locale={activeLocale}\n />\n </span>\n ) : null}\n </div>\n ) : null}\n\n {/* Money: due / overdue / billed chips */}\n {typeof dueCents === 'number' || typeof billedCents === 'number' ? (\n <div className=\"ds:flex ds:flex-wrap ds:items-center ds:gap-[var(--spacing-sm)]\">\n {typeof dueCents === 'number' && dueCents > 0 ? (\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-xs)]\">\n <TransactionChip\n state=\"debt\"\n amount={dueCents / 100}\n currency={currency}\n locale={activeLocale}\n showLabel\n size=\"md\"\n />\n {isOverdue ? (\n <Badge variant=\"error\" size=\"sm\" withDot>\n {t('carePlanCard.overdue', {\n count: overdueDays,\n defaultValue: 'Overdue {{count}}d',\n })}\n </Badge>\n ) : null}\n </div>\n ) : null}\n {typeof billedCents === 'number' && billedCents > 0 ? (\n <TransactionChip\n state=\"settled\"\n amount={billedCents / 100}\n currency={currency}\n locale={activeLocale}\n showLabel\n size=\"md\"\n />\n ) : null}\n </div>\n ) : null}\n\n {/* Line / phase progress */}\n {progress && progress.total > 0 ? (\n <Progress\n value={progress.done}\n max={progress.total}\n size=\"sm\"\n aria-label={t('carePlanCard.progressLabel', {\n done: progress.done,\n total: progress.total,\n defaultValue: 'Progress: {{done}} of {{total}} completed',\n })}\n >\n <div className=\"ds:flex ds:items-center ds:justify-between ds:gap-[var(--spacing-sm)]\">\n <Progress.Label id={progressLabelId}>\n {t('carePlanCard.progressHeading', 'Progress')}\n </Progress.Label>\n <span className=\"type-body-sm ds:tabular-nums ds:text-[color:var(--muted-foreground)]\">\n {t('carePlanCard.progressCount', {\n done: progress.done,\n total: progress.total,\n defaultValue: '{{done}} / {{total}}',\n })}\n </span>\n </div>\n </Progress>\n ) : null}\n\n {/* Entries — expandable list of treatment lines */}\n {hasEntries ? (\n <Accordion type=\"single\" collapsible variant=\"separated\">\n <AccordionItem value=\"entries\">\n <AccordionTrigger>\n {t('carePlanCard.entries.toggle', {\n count: entries.length,\n defaultValue: 'Treatment lines ({{count}})',\n })}\n </AccordionTrigger>\n <AccordionContent>\n <ul className=\"ds:m-0 ds:flex ds:list-none ds:flex-col ds:gap-[var(--spacing-sm)] ds:p-0\">\n {entries.map(({ entryId, ...entry }) => (\n <li key={entryId}>\n <CarePlanEntryCard\n id={entryId}\n surface=\"flat\"\n locale={activeLocale}\n currency={currency}\n {...entry}\n />\n </li>\n ))}\n </ul>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n ) : null}\n </div>\n </div>\n );\n },\n);\n\nCarePlanCard.displayName = 'CarePlanCard';\n\nexport type CarePlanCardSurface = NonNullable<\n VariantProps<typeof entitySurfaceVariants>['surface']\n>;\n\n/** The dentition the plan applies to. */\nexport type CarePlanCardDentition = NonNullable<\n CarePlanCardProps['dentitionMode']\n>;\n\n/* Re-export the shared lifecycle-state type for downstream typing. */\nexport type { CarePlanState } from '../_shared/entity-card';\n"],"names":["carePlanCardAgent","handle","DEFAULT_CURRENCY","STATE_LABEL_FALLBACK","CarePlanCard","forwardRef","id","number","name","state","url","amounts","progress","dates","discardReason","_dentitionMode","entries","locale","currency","surface","onActivate","ariaLabel","className","rest","ref","t","i18n","useTranslation","headingId","useId","progressLabelId","activeLocale","useAgentRegistration","stateLabel","stateBadgeVariant","CARE_PLAN_STATE_BADGE","hasSafeUrl","isSafeEntityUrl","hasEntries","dueCents","billedCents","overdueDays","isOverdue","headingNode","jsx","entitySurfaceVariants","jsxs","Link","event","Badge","Timestamp","TransactionChip","Progress","Accordion","AccordionItem","AccordionTrigger","AccordionContent","entryId","entry","CarePlanEntryCard"],"mappings":"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"}
|