@estiva-app/ui 0.1.0 → 0.2.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.
Files changed (157) hide show
  1. package/README.md +12 -9
  2. package/base.css +43 -0
  3. package/dist/AppShell.d.ts +39 -0
  4. package/dist/AppShell.d.ts.map +1 -0
  5. package/dist/Avatar.d.ts +7 -2
  6. package/dist/Avatar.d.ts.map +1 -1
  7. package/dist/AvatarGroup.d.ts +23 -0
  8. package/dist/AvatarGroup.d.ts.map +1 -0
  9. package/dist/Banner.d.ts +19 -0
  10. package/dist/Banner.d.ts.map +1 -0
  11. package/dist/Breadcrumb.d.ts +28 -0
  12. package/dist/Breadcrumb.d.ts.map +1 -0
  13. package/dist/Checkbox.d.ts +18 -0
  14. package/dist/Checkbox.d.ts.map +1 -0
  15. package/dist/ChipInput.d.ts +57 -0
  16. package/dist/ChipInput.d.ts.map +1 -0
  17. package/dist/ConfirmDialog.d.ts +22 -0
  18. package/dist/ConfirmDialog.d.ts.map +1 -0
  19. package/dist/DialogShell.d.ts +9 -2
  20. package/dist/DialogShell.d.ts.map +1 -1
  21. package/dist/EditableText.d.ts +36 -0
  22. package/dist/EditableText.d.ts.map +1 -0
  23. package/dist/IdentityMenu.d.ts +58 -0
  24. package/dist/IdentityMenu.d.ts.map +1 -0
  25. package/dist/Menu.d.ts +82 -0
  26. package/dist/Menu.d.ts.map +1 -0
  27. package/dist/NavItem.d.ts +26 -0
  28. package/dist/NavItem.d.ts.map +1 -0
  29. package/dist/Person.d.ts +26 -0
  30. package/dist/Person.d.ts.map +1 -0
  31. package/dist/PersonTrigger.d.ts +23 -0
  32. package/dist/PersonTrigger.d.ts.map +1 -0
  33. package/dist/Property.d.ts +24 -0
  34. package/dist/Property.d.ts.map +1 -0
  35. package/dist/Rail.d.ts +18 -0
  36. package/dist/Rail.d.ts.map +1 -0
  37. package/dist/RailItem.d.ts +23 -0
  38. package/dist/RailItem.d.ts.map +1 -0
  39. package/dist/SearchInput.d.ts +21 -0
  40. package/dist/SearchInput.d.ts.map +1 -0
  41. package/dist/SectionHeader.d.ts +34 -0
  42. package/dist/SectionHeader.d.ts.map +1 -0
  43. package/dist/SectionLabel.d.ts +19 -0
  44. package/dist/SectionLabel.d.ts.map +1 -0
  45. package/dist/Select.d.ts +42 -5
  46. package/dist/Select.d.ts.map +1 -1
  47. package/dist/Sidebar.d.ts +18 -0
  48. package/dist/Sidebar.d.ts.map +1 -0
  49. package/dist/Tabs.d.ts +34 -0
  50. package/dist/Tabs.d.ts.map +1 -0
  51. package/dist/Toast.d.ts +48 -0
  52. package/dist/Toast.d.ts.map +1 -0
  53. package/dist/Tooltip.d.ts +3 -1
  54. package/dist/Tooltip.d.ts.map +1 -1
  55. package/dist/TopBar.d.ts +33 -0
  56. package/dist/TopBar.d.ts.map +1 -0
  57. package/dist/cn.d.ts +14 -4
  58. package/dist/cn.d.ts.map +1 -1
  59. package/dist/index.d.ts +23 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1254 -106
  62. package/dist/index.js.map +4 -4
  63. package/package.json +5 -2
  64. package/src/AppShell.mdx +66 -0
  65. package/src/AppShell.stories.tsx +117 -0
  66. package/src/AppShell.tsx +77 -0
  67. package/src/Avatar.initials.test.ts +21 -0
  68. package/src/Avatar.mdx +49 -0
  69. package/src/Avatar.tsx +12 -9
  70. package/src/AvatarGroup.mdx +39 -0
  71. package/src/AvatarGroup.stories.tsx +56 -0
  72. package/src/AvatarGroup.tsx +42 -0
  73. package/src/Banner.mdx +45 -0
  74. package/src/Banner.stories.tsx +35 -0
  75. package/src/Banner.tsx +35 -0
  76. package/src/Breadcrumb.mdx +52 -0
  77. package/src/Breadcrumb.stories.tsx +42 -0
  78. package/src/Breadcrumb.tsx +106 -0
  79. package/src/Button.mdx +57 -0
  80. package/src/Checkbox.mdx +44 -0
  81. package/src/Checkbox.stories.tsx +49 -0
  82. package/src/Checkbox.tsx +46 -0
  83. package/src/Chip.mdx +43 -0
  84. package/src/ChipInput.mdx +68 -0
  85. package/src/ChipInput.stories.tsx +99 -0
  86. package/src/ChipInput.tsx +247 -0
  87. package/src/ConfirmDialog.mdx +51 -0
  88. package/src/ConfirmDialog.stories.tsx +63 -0
  89. package/src/ConfirmDialog.tsx +55 -0
  90. package/src/DialogShell.mdx +68 -0
  91. package/src/DialogShell.tsx +12 -5
  92. package/src/Divider.mdx +41 -0
  93. package/src/EditableText.mdx +59 -0
  94. package/src/EditableText.stories.tsx +63 -0
  95. package/src/EditableText.tsx +150 -0
  96. package/src/EmptyState.mdx +41 -0
  97. package/src/Field.mdx +39 -0
  98. package/src/IconButton.mdx +47 -0
  99. package/src/IdentityMenu.mdx +62 -0
  100. package/src/IdentityMenu.stories.tsx +52 -0
  101. package/src/IdentityMenu.tsx +159 -0
  102. package/src/Menu.mdx +111 -0
  103. package/src/Menu.stories.tsx +66 -0
  104. package/src/Menu.tsx +172 -0
  105. package/src/MenuItem.mdx +55 -0
  106. package/src/MenuItem.stories.tsx +98 -0
  107. package/src/NavItem.mdx +53 -0
  108. package/src/NavItem.stories.tsx +36 -0
  109. package/src/NavItem.tsx +53 -0
  110. package/src/Person.mdx +44 -0
  111. package/src/Person.stories.tsx +43 -0
  112. package/src/Person.tsx +39 -0
  113. package/src/PersonTrigger.mdx +51 -0
  114. package/src/PersonTrigger.stories.tsx +30 -0
  115. package/src/PersonTrigger.tsx +62 -0
  116. package/src/Property.mdx +51 -0
  117. package/src/Property.stories.tsx +91 -0
  118. package/src/Property.tsx +44 -0
  119. package/src/Rail.tsx +26 -0
  120. package/src/RailItem.mdx +50 -0
  121. package/src/RailItem.stories.tsx +37 -0
  122. package/src/RailItem.tsx +53 -0
  123. package/src/SearchInput.mdx +39 -0
  124. package/src/SearchInput.stories.tsx +24 -0
  125. package/src/SearchInput.tsx +45 -0
  126. package/src/SectionHeader.mdx +52 -0
  127. package/src/SectionHeader.stories.tsx +66 -0
  128. package/src/SectionHeader.tsx +85 -0
  129. package/src/SectionLabel.mdx +42 -0
  130. package/src/SectionLabel.stories.tsx +31 -0
  131. package/src/SectionLabel.tsx +28 -0
  132. package/src/Select.fit.test.ts +97 -0
  133. package/src/Select.mdx +71 -0
  134. package/src/Select.stories.tsx +95 -0
  135. package/src/Select.tsx +123 -15
  136. package/src/Sidebar.mdx +56 -0
  137. package/src/Sidebar.stories.tsx +50 -0
  138. package/src/Sidebar.tsx +32 -0
  139. package/src/Skeleton.mdx +39 -0
  140. package/src/Tabs.mdx +53 -0
  141. package/src/Tabs.stories.tsx +91 -0
  142. package/src/Tabs.tsx +73 -0
  143. package/src/TextInput.mdx +39 -0
  144. package/src/Textarea.mdx +36 -0
  145. package/src/Toast.mdx +54 -0
  146. package/src/Toast.stories.tsx +87 -0
  147. package/src/Toast.tsx +169 -0
  148. package/src/Tooltip.mdx +51 -0
  149. package/src/Tooltip.tsx +4 -2
  150. package/src/TopBar.mdx +62 -0
  151. package/src/TopBar.stories.tsx +96 -0
  152. package/src/TopBar.tsx +57 -0
  153. package/src/cn.test.ts +27 -0
  154. package/src/cn.ts +25 -5
  155. package/src/index.ts +23 -0
  156. package/tailwind-preset.js +4 -3
  157. package/tokens.css +4 -0
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/Avatar.tsx", "../src/cn.ts", "../src/Button.tsx", "../src/Chip.tsx", "../src/Tooltip.tsx", "../src/IconButton.tsx", "../src/Field.tsx", "../src/Select.tsx", "../src/TextInput.tsx", "../src/Textarea.tsx", "../src/DialogShell.tsx", "../src/Divider.tsx", "../src/EmptyState.tsx", "../src/Skeleton.tsx"],
4
- "sourcesContent": ["import { useState } from 'react'\nimport { IconUserFilled } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * A person's face: their picture, or their initials on a colour that is\n * theirs, or \u2014 when there is no name to take initials from \u2014 a silhouette.\n *\n * Peek's Avatar (2026-08-28), made self-contained: Peek's version found the\n * picture itself, by name, from Peek's data (an upload in Convex, else the\n * demo portrait), and switched the initials on only under its Signal theme.\n * Neither belongs in a shared component, so this one takes the picture URL\n * from its caller and draws the initials in every theme. Peek keeps a\n * three-line wrapper that resolves the picture; Ship passes the relay's\n * `kind:0` picture straight in.\n *\n * Initials come from a *name*, never from a key: a component that accepted a\n * pubkey would eventually show one (Ship's ruling, 2026-08-24). An unnamed\n * person is the silhouette \u2014 `?` on a bright gradient read as an error badge\n * rather than a person (Peek, FEE-1).\n *\n * The hue is a fallback palette, not a token: it is per person, chosen\n * from the name so the same person is the same colour everywhere, and a\n * theme has no say in it. Peek's eight, verbatim.\n */\nconst HUES = ['#56c8ff', '#ff8f6b', '#4ade8c', '#b18cff', '#ffc94d', '#ff7eb0', '#7ea8ff', '#5fdfd6']\n\nexport const hueFor = (key: string) => {\n let h = 0\n for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) | 0\n return HUES[Math.abs(h) % HUES.length]\n}\n\n/** Peek's rule: the first letter of the first two words. */\nexport const initialsFor = (name: string) =>\n name\n .split(' ')\n .map((w) => w[0])\n .slice(0, 2)\n .join('')\n .toUpperCase()\n\nexport interface AvatarProps {\n /** The picture URL, resolved by the caller. A picture that fails to load falls back to the initials. */\n src?: string\n /** The person's name \u2014 where the initials and the colour come from. */\n name?: string\n /** Alt text for the picture; the name when absent. Also the initials' source when there is no name. */\n alt?: string\n /** Pixels. Peek's scale: 16 \u00B7 24 \u00B7 32 \u00B7 36 (default). */\n size?: number\n className?: string\n}\n\nexport function Avatar({ src, name, alt = '', size = 36, className }: AvatarProps) {\n const [broken, setBroken] = useState(false)\n const label = name || alt\n const picture = src && !broken ? src : undefined\n return (\n <div className={cn('rounded-sm overflow-hidden shrink-0 bg-bg-inset', className)} style={{ width: size, height: size }}>\n {picture ? (\n <img src={picture} alt={alt || name || ''} className=\"w-full h-full object-cover\" onError={() => setBroken(true)} />\n ) : label ? (\n <div\n className=\"w-full h-full flex items-center justify-center font-semibold\"\n style={{\n color: '#08121c',\n fontSize: Math.round(size * 0.36),\n background: `linear-gradient(160deg, color-mix(in srgb, ${hueFor(label)} 92%, #fff) 0%, color-mix(in srgb, ${hueFor(label)} 70%, #0b0d11) 100%)`,\n }}\n >\n {initialsFor(label)}\n </div>\n ) : (\n <div className=\"w-full h-full bg-accent-muted flex items-center justify-center text-text-muted\">\n <IconUserFilled size={Math.round(size * 0.5)} />\n </div>\n )}\n </div>\n )\n}\n", "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/**\n * Peek's `cn`: clsx for the conditionals, tailwind-merge for the conflicts.\n *\n * Remember the pitfall the README records: a custom text-size class in a\n * merged list is dropped when a `text-{colour}` follows it. Inside this\n * package, sizes are arbitrary values (`text-[12px]`), never the token class.\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n", "import type { ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Button (2026-08-28), verbatim, plus what Ship added and Peek should\n * adopt: a `destructive` variant \u2014 the muted button in the error colour, for\n * \"Delete project\" and its kind; a destructive action is a button like any\n * other, not a dotted link \u2014 and `type=\"button\"` by default, so a button\n * inside a form submits it only when asked to.\n *\n * Three variants and two sizes: 32px default / 24px small, 6px radius, 500\n * weight. Sizes are spelled as arbitrary values for the reason the README\n * records: tailwind-merge drops a custom `text-{size}` that is followed by a\n * `text-{colour}`.\n *\n * The primary reads in `text-inverse` on the accent \u2014 a theme decides what\n * that is (dark on Peek's light accents, light on Ship's dark one). Under\n * Signal it is also semibold, as Peek has it.\n */\nexport type ButtonVariant = 'primary' | 'outlined' | 'muted' | 'destructive'\nexport type ButtonSize = 'default' | 'small'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant\n size?: ButtonSize\n /** 16px, stroke 1.5 on the default size; 14px on small. */\n leadingIcon?: ReactNode\n children: ReactNode\n}\n\nexport function Button({\n variant = 'muted',\n size = 'default',\n leadingIcon,\n className,\n children,\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) {\n const hasLeadingIcon = !!leadingIcon\n return (\n <button\n type={type}\n className={cn(\n 'inline-flex items-center justify-center gap-1 rounded-md transition-colors font-sans font-medium',\n size === 'default' && 'h-8 text-[14px] leading-[14px]',\n size === 'small' && 'h-6 text-[12px] leading-[12px]',\n // Extra right padding beside a leading icon, for optical balance.\n size === 'default' && (hasLeadingIcon ? 'pl-2 pr-3' : 'px-2'),\n size === 'small' && (hasLeadingIcon ? 'pl-1.5 pr-2' : 'px-1.5'),\n !disabled && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse cursor-pointer signal:font-semibold',\n !disabled && variant === 'outlined' && 'border border-border-default hover:bg-bg-hover text-text-primary cursor-pointer',\n !disabled && variant === 'muted' && 'hover:bg-bg-hover text-text-primary cursor-pointer',\n !disabled && variant === 'destructive' && 'hover:bg-error-muted text-error-default cursor-pointer',\n disabled && 'bg-bg-disabled text-text-disabled pointer-events-none',\n disabled && variant === 'outlined' && 'border border-border-default',\n className,\n )}\n disabled={disabled}\n {...props}\n >\n {leadingIcon}\n {children}\n </button>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Chip (2026-08-28), verbatim: a 20px pill in one of six colour\n * types, with room for a 12px icon either side. Under Signal the label is\n * mono and the coloured types carry a hairline, as Peek has it. The icon\n * slots centre their icon (Ship's addition \u2014 an icon beside text otherwise\n * rides on the baseline).\n *\n * The label is the `chip` type token, 11px / 500; it is a plain class here,\n * never merged, so tailwind-merge cannot drop it.\n */\nexport type ChipType = 'neutral' | 'brand' | 'info' | 'warning' | 'success' | 'error'\n\nexport interface ChipProps {\n type?: ChipType\n label?: string\n leadingIcon?: ReactNode\n trailingIcon?: ReactNode\n className?: string\n}\n\nconst typeStyles: Record<ChipType, string> = {\n neutral: 'bg-bg-inset text-text-primary',\n brand: 'bg-accent-muted text-accent-primary signal:border signal:border-[rgba(86,200,255,0.3)]',\n info: 'bg-info-muted text-info-default signal:border signal:border-[rgba(86,200,255,0.3)]',\n warning: 'bg-warning-muted text-warning-default signal:border signal:border-[rgba(255,176,32,0.3)] signal:shadow-[shadow:0_0_5px_rgba(255,176,32,0.4)]',\n success: 'bg-success-muted text-success-default signal:border signal:border-[rgba(63,222,140,0.3)]',\n error: 'bg-error-muted text-error-default signal:border signal:border-[rgba(255,107,107,0.3)]',\n}\n\nexport function Chip({ type = 'neutral', label, leadingIcon, trailingIcon, className }: ChipProps) {\n return (\n <div className={cn('inline-flex items-center justify-center gap-1.5 rounded-full max-h-[20px] min-w-[16px] px-2 py-1', typeStyles[type], className)}>\n {leadingIcon && <span className=\"flex size-3 shrink-0 items-center justify-center\">{leadingIcon}</span>}\n {label && (\n <span className=\"text-chip whitespace-nowrap signal:font-mono signal:text-[10px] signal:font-semibold signal:tracking-[0.02em] signal:tabular-nums\">{label}</span>\n )}\n {trailingIcon && <span className=\"flex size-3 shrink-0 items-center justify-center\">{trailingIcon}</span>}\n </div>\n )\n}\n", "import { useCallback, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { cn } from './cn'\n\n/**\n * Peek's Tooltip and WithTooltip (2026-08-28), verbatim, in one file.\n *\n * `Tooltip` is the surface: a 30px elevated pill with a caption. `WithTooltip`\n * wraps a trigger and portals the surface above or below it on hover, fixed\n * to the viewport and kept 8px inside it. The wrapper is `inline-flex` and\n * shrinks nothing \u2014 wrap a control, never a block (a form inside it\n * collapses to its content width; Ship learnt that).\n */\nexport interface TooltipProps {\n label: string\n className?: string\n}\n\nexport function Tooltip({ label, className }: TooltipProps) {\n return (\n <div role=\"tooltip\" className={cn('bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center px-2 shadow-lg', className)}>\n <span className=\"text-caption text-text-primary whitespace-nowrap\">{label}</span>\n </div>\n )\n}\n\nexport interface WithTooltipProps {\n label: string\n placement?: 'top' | 'bottom'\n children: ReactNode\n}\n\nconst GAP = 6\nconst VIEWPORT_PAD = 8\n\nexport function WithTooltip({ label, placement = 'top', children }: WithTooltipProps) {\n const [show, setShow] = useState(false)\n const ref = useRef<HTMLDivElement>(null)\n const tooltipRef = useRef<HTMLDivElement>(null)\n const [style, setStyle] = useState<CSSProperties>({ position: 'fixed', zIndex: 9999, pointerEvents: 'none', visibility: 'hidden' })\n\n const reposition = useCallback(() => {\n const trigger = ref.current\n const tip = tooltipRef.current\n if (!trigger || !tip) return\n const triggerRect = trigger.getBoundingClientRect()\n const tipRect = tip.getBoundingClientRect()\n const top = placement === 'bottom' ? triggerRect.bottom + GAP : triggerRect.top - tipRect.height - GAP\n let left = triggerRect.left + triggerRect.width / 2 - tipRect.width / 2\n left = Math.max(VIEWPORT_PAD, Math.min(left, window.innerWidth - tipRect.width - VIEWPORT_PAD))\n setStyle({ position: 'fixed', top, left, zIndex: 9999, pointerEvents: 'none', visibility: 'visible' })\n }, [placement])\n\n useLayoutEffect(() => {\n if (show) reposition()\n }, [show, reposition])\n\n return (\n <div ref={ref} className=\"inline-flex shrink-0\" onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show &&\n createPortal(\n <div ref={tooltipRef} style={style}>\n <Tooltip label={label} />\n </div>,\n document.body,\n )}\n </div>\n )\n}\n", "import type { ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from './cn'\nimport { WithTooltip } from './Tooltip'\n\n/**\n * Peek's IconButton (2026-08-28), verbatim: a square 4px-padded button\n * around a 16px icon, 8px radius, three variants, an optional tooltip that\n * portals above or below. Plus `type=\"button\"` by default (Ship's addition),\n * so it never submits a form by accident.\n */\nexport type IconButtonVariant = 'muted' | 'outlined' | 'primary'\n\nexport interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: IconButtonVariant\n tooltip?: string\n tooltipPlacement?: 'top' | 'bottom'\n /** The icon: 16px, stroke 1.5. */\n children: ReactNode\n}\n\nexport function IconButton({\n variant = 'muted',\n className,\n children,\n disabled,\n tooltip,\n tooltipPlacement,\n type = 'button',\n ...props\n}: IconButtonProps) {\n const button = (\n <button\n type={type}\n className={cn(\n 'flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 cursor-pointer',\n !disabled && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse',\n !disabled && variant === 'muted' && 'text-text-secondary hover:bg-bg-hover hover:text-text-primary',\n !disabled && variant === 'outlined' && 'border border-border-default hover:bg-bg-hover text-text-secondary',\n disabled && variant === 'primary' && 'bg-bg-disabled text-text-disabled',\n disabled && variant === 'muted' && 'text-text-disabled',\n disabled && variant === 'outlined' && 'border border-border-default text-text-disabled',\n disabled && 'pointer-events-none cursor-not-allowed',\n className,\n )}\n disabled={disabled}\n {...props}\n >\n {children}\n </button>\n )\n\n if (tooltip) {\n return (\n <WithTooltip label={tooltip} placement={tooltipPlacement}>\n {button}\n </WithTooltip>\n )\n }\n return button\n}\n", "import type { ReactNode } from 'react'\n\n/**\n * Peek's Field (2026-08-28), verbatim: a label over a control, 8px apart,\n * with a red asterisk when required. The label is the `input-label` type\n * token as a plain class, never merged.\n */\nexport interface FieldProps {\n label: string\n required?: boolean\n children: ReactNode\n}\n\nexport function Field({ label, required = false, children }: FieldProps) {\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={`text-input-label text-text-primary${required ? ' flex items-center' : ''}`}>\n {label}\n {required && <span className=\"text-error-default ml-0.5\">*</span>}\n </label>\n {children}\n </div>\n )\n}\n", "import { IconCheck, IconChevronDown } from '@tabler/icons-react'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { cn } from './cn'\n\n/**\n * Peek's Select (2026-08-28), verbatim, plus what Ship added: an option may\n * carry a `leading` node \u2014 an avatar beside a person's name \u2014 shown in the\n * trigger and in the list.\n *\n * A button that opens a portalled listbox under itself: arrows move, Home\n * and End jump, Enter and Space pick, Escape closes and returns focus to the\n * trigger, Tab closes, a click outside closes, a scroll or resize closes\n * (the list is fixed to where the trigger was). Two sizes; `disabled`\n * explains nothing by itself \u2014 wrap it in a tooltip that does.\n */\nexport interface SelectOption {\n value: string\n label: string\n /** Drawn before the label, 16px \u2014 an Avatar, an icon. */\n leading?: ReactNode\n}\n\nexport interface SelectProps {\n value: string\n onChange: (value: string) => void\n options: SelectOption[]\n size?: 'default' | 'small'\n ariaLabel?: string\n placeholder?: string\n disabled?: boolean\n className?: string\n}\n\nexport function Select({ value, onChange, options, size = 'default', ariaLabel, placeholder = 'Select\u2026', disabled, className }: SelectProps) {\n const id = useId()\n const triggerRef = useRef<HTMLButtonElement>(null)\n const menuRef = useRef<HTMLDivElement>(null)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const open = rect !== null\n\n const selectedIndex = useMemo(() => Math.max(0, options.findIndex((o) => o.value === value)), [options, value])\n const [activeIndex, setActiveIndex] = useState(selectedIndex)\n\n const close = useCallback((refocus: boolean) => {\n setRect(null)\n if (refocus) triggerRef.current?.focus()\n }, [])\n\n const openMenu = useCallback(() => {\n setActiveIndex(selectedIndex)\n setRect(triggerRef.current?.getBoundingClientRect() ?? null)\n }, [selectedIndex])\n\n // Outside click and Escape \u2014 the two exits every menu has. `mousedown`\n // rather than `click`, so a press that starts outside dismisses at once.\n useEffect(() => {\n if (!open) return\n const onDown = (e: MouseEvent) => {\n if (menuRef.current?.contains(e.target as Node)) return\n if (triggerRef.current?.contains(e.target as Node)) return\n setRect(null)\n }\n const onMove = () => setRect(null)\n document.addEventListener('mousedown', onDown)\n window.addEventListener('resize', onMove)\n window.addEventListener('scroll', onMove, true)\n return () => {\n document.removeEventListener('mousedown', onDown)\n window.removeEventListener('resize', onMove)\n window.removeEventListener('scroll', onMove, true)\n }\n }, [open])\n\n const pick = (index: number) => {\n const option = options[index]\n if (!option) return\n onChange(option.value)\n close(true)\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (!open) {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {\n e.preventDefault()\n openMenu()\n }\n return\n }\n switch (e.key) {\n case 'Escape':\n e.preventDefault()\n close(true)\n break\n case 'ArrowDown':\n e.preventDefault()\n setActiveIndex((i) => Math.min(options.length - 1, i + 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setActiveIndex((i) => Math.max(0, i - 1))\n break\n case 'Home':\n e.preventDefault()\n setActiveIndex(0)\n break\n case 'End':\n e.preventDefault()\n setActiveIndex(options.length - 1)\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n pick(activeIndex)\n break\n case 'Tab':\n close(false)\n break\n }\n }\n\n const selected = options.find((o) => o.value === value)\n\n return (\n <>\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={ariaLabel}\n onClick={() => (open ? close(false) : openMenu())}\n onKeyDown={onKeyDown}\n className={cn(\n 'flex w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left',\n 'border-border-default text-text-primary outline-none transition-colors',\n 'hover:border-border-strong disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'focus-visible:border-border-focus aria-expanded:border-border-focus',\n 'signal:transition-shadow signal:focus-visible:shadow-focus-ring',\n size === 'default' && 'px-3 py-2 text-[14px] leading-[1.4] font-normal',\n size === 'small' && 'h-6 px-2 text-[12px] leading-[1.4] font-normal',\n className,\n )}\n >\n <span className={cn('flex min-w-0 items-center gap-2', !selected && 'text-text-muted')}>\n {selected?.leading && <span className=\"flex shrink-0 items-center\">{selected.leading}</span>}\n <span className=\"truncate\">{selected?.label ?? placeholder}</span>\n </span>\n <IconChevronDown size={size === 'small' ? 14 : 16} stroke={1.5} className=\"shrink-0 text-text-secondary\" />\n </button>\n\n {open &&\n rect &&\n createPortal(\n <div\n ref={menuRef}\n role=\"listbox\"\n aria-activedescendant={`${id}-${activeIndex}`}\n tabIndex={-1}\n style={{ top: rect.bottom + 4, left: rect.left, minWidth: rect.width }}\n className=\"fixed z-50 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-bg-elevated p-1 shadow-lg\"\n >\n {options.map((option, index) => (\n <div\n key={option.value}\n id={`${id}-${index}`}\n role=\"option\"\n aria-selected={option.value === value}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={(e) => {\n // The document-level dismiss also listens on mousedown.\n e.preventDefault()\n pick(index)\n }}\n className={cn(\n 'flex h-9 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 transition-colors',\n 'text-[14px] font-normal leading-[1.4] text-text-primary',\n index === activeIndex && 'bg-bg-hover',\n option.value === value && 'font-medium',\n )}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {option.leading && <span className=\"flex shrink-0 items-center\">{option.leading}</span>}\n <span className=\"truncate\">{option.label}</span>\n </span>\n {option.value === value && <IconCheck size={16} stroke={1.5} className=\"shrink-0 text-text-secondary\" />}\n </div>\n ))}\n </div>,\n document.body,\n )}\n </>\n )\n}\n", "import { forwardRef, type InputHTMLAttributes } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's TextInput (2026-08-28): the inset field with a 8px radius, 14px\n * text, the focus border in every theme (Katerina, 2026-08-28: Ship's inputs\n * focus like Signal's) \u2014 and Signal's glow on top.\n * Plus a disabled look (Ship's addition): the disabled surface and text,\n * no pointer.\n */\nexport type TextInputProps = InputHTMLAttributes<HTMLInputElement>\n\nexport const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function TextInput({ className, type = 'text', ...props }, ref) {\n return (\n <input\n ref={ref}\n type={type}\n className={cn(\n 'bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2',\n 'text-[14px] leading-[1.4] font-normal text-text-primary placeholder:text-text-muted',\n 'outline-none transition-colors',\n 'disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'signal:transition-shadow signal:focus:shadow-focus-ring',\n className,\n )}\n {...props}\n />\n )\n})\n", "import { forwardRef, type TextareaHTMLAttributes } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Textarea (2026-08-28), verbatim: TextInput's look on a textarea\n * that does not resize. Plus a disabled look (Ship's addition).\n */\nexport type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea({ className, ...props }, ref) {\n return (\n <textarea\n ref={ref}\n className={cn(\n 'bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2',\n 'text-[14px] leading-[1.4] font-normal text-text-primary placeholder:text-text-muted',\n 'resize-none outline-none transition-colors',\n 'disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'signal:transition-shadow signal:focus:shadow-focus-ring',\n className,\n )}\n {...props}\n />\n )\n})\n", "import { useEffect, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconX } from '@tabler/icons-react'\nimport { cn } from './cn'\nimport { IconButton } from './IconButton'\n\n/**\n * Peek's DialogShell (2026-08-28), verbatim: the portal, the backdrop, the\n * 502px card and its chrome \u2014 a 48px header with the title and a close\n * button, a body, a 48px footer for the buttons. A dialog is just what goes\n * in the three slots. Plus what Ship added: Escape closes it, and the card\n * says what it is to assistive tech.\n */\nexport interface DialogShellProps {\n title: string\n onClose: () => void\n footer: ReactNode\n children: ReactNode\n /** Extra classes on the body (e.g. `flex flex-col gap-6`, or a max height with `overflow-y-auto`). */\n bodyClassName?: string\n width?: number\n}\n\nexport function DialogShell({ title, onClose, footer, children, bodyClassName, width = 502 }: DialogShellProps) {\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onClose()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [onClose])\n\n return createPortal(\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 z-40 bg-black/50\" onClick={onClose} />\n\n {/* Dialog */}\n <div className=\"fixed inset-0 z-50 flex items-center justify-center pointer-events-none\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n className=\"bg-bg-elevated border border-border-subtle rounded-lg shadow-lg pointer-events-auto flex flex-col overflow-hidden\"\n style={{ width }}\n >\n {/* Header */}\n <div className=\"h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0\">\n <span className=\"text-h4 text-text-primary\">{title}</span>\n <IconButton tooltip=\"Close\" aria-label=\"Close\" onClick={onClose}>\n <IconX size={16} stroke={1.5} />\n </IconButton>\n </div>\n\n {/* Body */}\n <div className={cn('pl-5 pr-4 py-4 border-b border-border-subtle', bodyClassName)}>{children}</div>\n\n {/* Footer */}\n <div className=\"h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0\">{footer}</div>\n </div>\n </div>\n </>,\n document.body,\n )\n}\n", "import { cn } from './cn'\n\n/**\n * Peek's Divider (2026-08-28): a hairline in `border-subtle`, inset 12px each\n * side. Plus what Ship added: `orientation=\"vertical\"` \u2014 the same hairline\n * standing up, stretching to its row's height, no inset \u2014 and the separator\n * role for assistive tech.\n */\nexport interface DividerProps {\n orientation?: 'horizontal' | 'vertical'\n className?: string\n}\n\nexport function Divider({ orientation = 'horizontal', className }: DividerProps) {\n return (\n <div\n role=\"separator\"\n aria-orientation={orientation}\n className={cn('bg-border-subtle', orientation === 'horizontal' ? 'h-px mx-3' : 'w-px self-stretch shrink-0', className)}\n />\n )\n}\n", "import type { ReactNode } from 'react'\nimport { IconMessage2 } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * Peek's EmptyState (2026-08-28), verbatim: a 16px icon over a centred line\n * of secondary text. The message is the caller's \u2014 a shared component has no\n * words of its own for what is missing.\n */\nexport interface EmptyStateProps {\n /** 16px, stroke 1.5. A speech bubble when absent. */\n icon?: ReactNode\n message: string\n className?: string\n}\n\nexport function EmptyState({ icon, message, className }: EmptyStateProps) {\n return (\n <div className={cn('flex flex-col gap-2 items-center', className)}>\n <span className=\"text-text-secondary\">{icon ?? <IconMessage2 size={16} stroke={1.5} />}</span>\n <p className=\"text-body-2 text-text-secondary text-center\">{message}</p>\n </div>\n )\n}\n", "import type { CSSProperties } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Skeleton (2026-08-28), the generic parts only: the bar, a row\n * shaped like a list row, and a list of them. Peek's placeholders shaped\n * like a conversation card or a huddle card know what those are and stay\n * in Peek, built from the bar.\n *\n * A list reveals after 150ms (`animate-skeleton-in`, in the preset) so a\n * fast load never flashes a skeleton \u2014 Peek's rule.\n */\nexport function SkeletonBar({ className, style }: { className?: string; style?: CSSProperties }) {\n return <div className={cn('bg-bg-inset rounded-sm animate-pulse', className)} style={style} />\n}\n\nconst ROW_BAR_WIDTHS = [150, 100, 170, 120, 90, 140, 110, 160]\n\n/** A 32px row: a 16px square and a bar, like a row with a face and a name. */\nexport function SkeletonRow({ barWidth = 130 }: { barWidth?: number }) {\n return (\n <div className=\"flex items-center gap-2 px-2 h-[32px] rounded-lg\">\n <SkeletonBar className=\"w-4 h-4 shrink-0\" />\n <SkeletonBar className=\"h-3.5\" style={{ width: barWidth }} />\n </div>\n )\n}\n\n/** Rows of varied widths, so the placeholder does not read as a pattern. */\nexport function SkeletonList({ rows = 8, className }: { rows?: number; className?: string }) {\n return (\n <div className={cn('flex flex-col gap-0.5 animate-skeleton-in', className)} aria-hidden>\n {Array.from({ length: rows }, (_, i) => (\n <SkeletonRow key={i} barWidth={ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length]} />\n ))}\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": ["jsx", "jsxs", "useState", "jsx", "jsxs", "useState", "jsx", "jsx", "jsxs", "useCallback", "useRef", "useState", "createPortal", "jsx", "jsxs", "useRef", "useState", "useCallback", "createPortal", "jsx", "TextInput", "forwardRef", "jsx", "forwardRef", "Textarea", "useEffect", "createPortal", "Fragment", "jsx", "jsxs", "useEffect", "createPortal", "jsx", "jsx", "jsxs", "jsx", "jsxs"]
3
+ "sources": ["../src/cn.ts", "../src/TopBar.tsx", "../src/AppShell.tsx", "../src/Avatar.tsx", "../src/AvatarGroup.tsx", "../src/Banner.tsx", "../src/Button.tsx", "../src/Checkbox.tsx", "../src/Chip.tsx", "../src/ChipInput.tsx", "../src/Menu.tsx", "../src/SectionLabel.tsx", "../src/Tooltip.tsx", "../src/IconButton.tsx", "../src/IdentityMenu.tsx", "../src/Divider.tsx", "../src/Person.tsx", "../src/PersonTrigger.tsx", "../src/Field.tsx", "../src/Select.tsx", "../src/TextInput.tsx", "../src/Textarea.tsx", "../src/ConfirmDialog.tsx", "../src/DialogShell.tsx", "../src/EditableText.tsx", "../src/EmptyState.tsx", "../src/Skeleton.tsx", "../src/Breadcrumb.tsx", "../src/NavItem.tsx", "../src/Rail.tsx", "../src/RailItem.tsx", "../src/Sidebar.tsx", "../src/Property.tsx", "../src/SearchInput.tsx", "../src/SectionHeader.tsx", "../src/Tabs.tsx", "../src/Toast.tsx"],
4
+ "sourcesContent": ["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n/**\n * The preset's type ramp, by name, so tailwind-merge can classify the token\n * classes as sizes. Stock tailwind-merge has never heard of `text-body-2`,\n * files it under text-COLOUR, and lets `text-text-primary` in the same\n * merged list knock it out \u2014 the trap Katerina's Tabs review confirmed by\n * measurement (2026-09-01), and the reason shared components used to spell\n * sizes as arbitrary values. Naming the ramp here retires that rule for\n * everything using this cn \u2014 the package and, since the 2026-09-01 dedupe,\n * both apps.\n *\n * Must match `tailwind-preset.js` `fontSize` key for key; `cn.test.ts`\n * asserts it (the preset imports node-only modules, so it cannot be\n * imported here without dragging them into the browser bundle).\n */\nconst FONT_SIZE_TOKENS = [\n 'h1', 'h2', 'h3', 'h4', 'h5',\n 'body-1', 'body-2', 'body-2-strong', 'caption', 'menu',\n 'btn-default', 'btn-small', 'input-label', 'input-value', 'input-helper', 'chip',\n]\n\nconst twMerge = extendTailwindMerge({\n extend: { classGroups: { 'font-size': [{ text: FONT_SIZE_TOKENS }] } },\n})\n\n/** Merge class names, last-one-wins on conflicting Tailwind utilities. */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport { FONT_SIZE_TOKENS as _fontSizeTokens }\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * The 52px top bar, in two manners:\n *\n * - `solid` \u2014 in flow: a hairline under it, the surface behind it, the\n * frame's first row (2026-09-02, verbatim from the structured app; its\n * product title moved to the caller).\n * - `floating` \u2014 an overlay: absolute over the content, no border and no\n * background, and the bar itself ignores the pointer \u2014 only its clusters\n * catch clicks, so the content underneath stays reachable (2026-09-02,\n * verbatim from the floating app).\n *\n * Geometry both apps agreed on before extraction: 52px tall, `pl-5\n * pr-[26px]`, right cluster `gap-[6px]`. The left region is two slots\n * side by side \u2014 `menu` (the menu button, in an app whose navigation\n * collapses) and `logo` (the mark or name beside it; Katerina,\n * 2026-09-02). This component does not know who you are or what is being\n * searched; it only knows where those go.\n */\nexport interface TopBarProps {\n variant?: 'solid' | 'floating'\n /** Far left: the menu button, in an app whose navigation collapses. Stays the caller's \u2014 the bar only places it. */\n menu?: ReactNode\n /** Beside the menu button: the app's logo mark, or its name as text. */\n logo?: ReactNode\n /** The centre: a search field, or a launcher affordance \u2014 or nothing; the slot holds its place. */\n search?: ReactNode\n /** The right cluster \u2014 an `IdentityMenu` and its neighbours. */\n right?: ReactNode\n className?: string\n}\n\nexport function TopBar({ variant = 'solid', menu, logo, search, right, className }: TopBarProps) {\n const floating = variant === 'floating'\n return (\n <header\n className={cn(\n 'flex h-[52px] items-center pl-5 pr-[26px]',\n floating\n ? 'pointer-events-none absolute left-0 right-0 top-0 z-10'\n : 'shrink-0 gap-4 border-b border-border-default bg-bg-surface',\n className,\n )}\n >\n {(menu || logo) && (\n <div className={cn('flex shrink-0 items-center gap-2', floating && 'pointer-events-auto')}>\n {menu}\n {logo && <div className=\"flex items-center text-body-2-strong text-text-primary\">{logo}</div>}\n </div>\n )}\n <div className={cn('flex min-w-0 flex-1 items-center justify-center', floating && 'pointer-events-auto')}>{search}</div>\n <div className={cn('flex shrink-0 items-center gap-[6px]', floating && 'pointer-events-auto')}>{right}</div>\n </header>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\nimport { TopBar } from './TopBar'\n\n/**\n * The frame of an app, in the two manners that pair with the TopBar's\n * (Katerina, 2026-09-02: the two shells go together \u2014 the solid bar with\n * the sidebar, the floating bar with the rail):\n *\n * - `solid` \u2014 the structured frame: a solid TopBar, the navigation\n * column, the content beside it, sidebar and main scrolling\n * independently (2026-09-02, verbatim from the structured app).\n * - `floating` \u2014 the floating frame: the TopBar floats over the top\n * edge, the rail stands on the app background, and the content lives\n * in the rounded card beside it (2026-09-02, the floating app's frame\n * geometry; its collapse animation and launcher stay in the app).\n *\n * The banner belongs to the content area (her ruling, 2026-09-02): it\n * renders at the top of the main column \u2014 inside the card, in the\n * floating manner \u2014 never across the navigation.\n *\n * Layout only. What goes in each region is the caller's; this component\n * has no data and no routes.\n */\nexport interface AppShellProps {\n variant?: 'solid' | 'floating'\n /** The top bar's menu button, in an app whose navigation collapses. */\n menu?: ReactNode\n /** The top bar's left: the app's logo mark, or its name as text. */\n logo?: ReactNode\n /** The top bar's centre \u2014 a search field, when there is one. */\n search?: ReactNode\n /** The top bar's right cluster \u2014 an `IdentityMenu`. */\n identity?: ReactNode\n /** A `Banner`, or nothing \u2014 drawn at the top of the content area. */\n banner?: ReactNode\n /** The navigation: a `Sidebar` in the solid manner, a `Rail` in the floating one. */\n nav: ReactNode\n children: ReactNode\n}\n\nexport function AppShell({ variant = 'solid', menu, logo, search, identity, banner, nav, children }: AppShellProps) {\n const bar = <TopBar variant={variant} menu={menu} logo={logo} search={search} right={identity} />\n\n if (variant === 'floating') {\n return (\n <div className=\"relative h-full min-h-0 overflow-hidden bg-bg-base\">\n {bar}\n <div className=\"flex h-full pb-4 pr-4 pt-[52px]\">\n {nav}\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col overflow-hidden rounded-2xl bg-bg-surface',\n 'signal:border signal:border-border-subtle signal:shadow-[inset_0_1px_0_rgba(255,255,255,0.035)]',\n )}\n >\n {banner}\n <main className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">{children}</main>\n </div>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col bg-bg-base text-text-primary\">\n {bar}\n <div className=\"flex min-h-0 flex-1\">\n {nav}\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col\">\n {banner}\n <main className=\"min-w-0 flex-1 overflow-y-auto\">{children}</main>\n </div>\n </div>\n </div>\n )\n}\n", "import { useState } from 'react'\nimport { IconUserFilled } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * A person's face: their picture, or their initials on a colour that is\n * theirs, or \u2014 when there is no name to take initials from \u2014 a silhouette.\n *\n * Peek's Avatar (2026-08-28), made self-contained: Peek's version found the\n * picture itself, by name, from Peek's data (an upload in Convex, else the\n * demo portrait), and switched the initials on only under its Signal theme.\n * Neither belongs in a shared component, so this one takes the picture URL\n * from its caller and draws the initials in every theme. Peek keeps a\n * three-line wrapper that resolves the picture; Ship passes the relay's\n * `kind:0` picture straight in.\n *\n * Initials come from a *name*, never from a key: a component that accepted a\n * pubkey would eventually show one (Ship's ruling, 2026-08-24). An unnamed\n * person is the silhouette \u2014 `?` on a bright gradient read as an error badge\n * rather than a person (Peek, FEE-1).\n *\n * The hue is a fallback palette, not a token: it is per person, chosen\n * from the name so the same person is the same colour everywhere, and a\n * theme has no say in it. Peek's eight, verbatim.\n */\nconst HUES = ['#56c8ff', '#ff8f6b', '#4ade8c', '#b18cff', '#ffc94d', '#ff7eb0', '#7ea8ff', '#5fdfd6']\n\nexport const hueFor = (key: string) => {\n let h = 0\n for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) | 0\n return HUES[Math.abs(h) % HUES.length]\n}\n\n/**\n * Peek's rule, sharpened: the first letter of the first two words \u2014 counting\n * only words that begin with a letter or digit, so \"Claude (steered by\n * Katerina Kelepouri)\" shows \"C\", never \"C(\" (Katerina, 2026-09-01: a name\n * carrying such symbols gets a single letter rather than one of them).\n */\nexport const initialsFor = (name: string) => {\n const words = name.split(/\\s+/).slice(0, 2).filter((w) => /^[\\p{L}\\p{N}]/u.test(w))\n const initials = words.map((w) => w[0]).join('') || name.trim().charAt(0)\n return initials.toUpperCase()\n}\n\nexport interface AvatarProps {\n /** The picture URL, resolved by the caller. A picture that fails to load falls back to the initials. */\n src?: string\n /** The person's name \u2014 where the initials and the colour come from. */\n name?: string\n /** Alt text for the picture; the name when absent. Also the initials' source when there is no name. */\n alt?: string\n /** Pixels. The scale: 16 \u00B7 24 \u00B7 32 \u00B7 36 (default). */\n size?: number\n className?: string\n}\n\nexport function Avatar({ src, name, alt = '', size = 36, className }: AvatarProps) {\n const [broken, setBroken] = useState(false)\n const label = name || alt\n const picture = src && !broken ? src : undefined\n return (\n <div className={cn('rounded-sm overflow-hidden shrink-0 bg-bg-inset', className)} style={{ width: size, height: size }}>\n {picture ? (\n <img src={picture} alt={alt || name || ''} className=\"w-full h-full object-cover\" onError={() => setBroken(true)} />\n ) : label ? (\n <div\n className=\"w-full h-full flex items-center justify-center font-semibold\"\n style={{\n color: '#08121c',\n fontSize: Math.round(size * 0.36),\n background: `linear-gradient(160deg, color-mix(in srgb, ${hueFor(label)} 92%, #fff) 0%, color-mix(in srgb, ${hueFor(label)} 70%, #0b0d11) 100%)`,\n }}\n >\n {initialsFor(label)}\n </div>\n ) : (\n <div className=\"w-full h-full bg-accent-muted flex items-center justify-center text-text-muted\">\n <IconUserFilled size={Math.round(size * 0.5)} />\n </div>\n )}\n </div>\n )\n}\n", "import { Avatar } from './Avatar'\n\n/**\n * Up to three overlapping 24px faces with a `bg-surface` ring \u2014 Peek's member\n * stack (2026-09-01), with one generalisation on the way in: a member is a\n * name plus an optional picture URL, resolved by the caller, exactly as\n * Avatar takes its `src`. Peek's version took bare names because its Avatar\n * wrapper resolves pictures itself; a consumer without such a wrapper could\n * never have shown one.\n *\n * The ring sits on the Avatar itself: the old extra wrapper was a 24px\n * window minus a 2px border with a fixed 24px Avatar inside, so\n * overflow-hidden clipped 4px of face \u2014 and the right edge of the initials\n * with it, which is how \"CO\" rendered as \"C(\". One box, whole letters.\n */\nexport interface AvatarGroupMember {\n name: string\n /** Resolved by the caller. Absent draws the initials. */\n picture?: string\n}\n\nexport interface AvatarGroupProps {\n members: AvatarGroupMember[]\n}\n\nexport function AvatarGroup({ members }: AvatarGroupProps) {\n const visible = members.slice(0, 3)\n return (\n <div className=\"flex items-center pr-2\">\n {visible.map((member, i) => (\n <Avatar\n key={i}\n size={24}\n name={member.name}\n src={member.picture}\n alt={member.name}\n className=\"-mr-2 relative border-2 border-bg-surface\"\n />\n ))}\n </div>\n )\n}\n", "import { cn } from './cn'\n\n/**\n * The strip under the header where the app says something happened \u2014\n * one line, gone when there is nothing to say (Ship's Banner,\n * 2026-09-02, verbatim; `info` and `warning` added on extraction,\n * Katerina's ruling \u2014 the semantic set is four everywhere else).\n *\n * body-2, her ruling (2026-09-01): the caption this once claimed never\n * rendered, so 14px is what it has always been and what she kept.\n * An `error` announces itself (`role=\"alert\"`); the other tones are\n * polite (`role=\"status\"`).\n */\nexport type BannerTone = 'ok' | 'error' | 'info' | 'warning'\n\nexport interface BannerProps {\n tone: BannerTone\n children: string\n className?: string\n}\n\nconst TONE: Record<BannerTone, string> = {\n ok: 'bg-success-muted text-success-default',\n error: 'bg-error-muted text-error-default',\n info: 'bg-info-muted text-info-default',\n warning: 'bg-warning-muted text-warning-default',\n}\n\nexport function Banner({ tone, children, className }: BannerProps) {\n return (\n <div role={tone === 'error' ? 'alert' : 'status'} className={cn('px-4 py-2 text-body-2', TONE[tone], className)}>\n {children}\n </div>\n )\n}\n", "import type { ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Button (2026-08-28), verbatim, plus what Ship added and Peek should\n * adopt: a `destructive` variant \u2014 the muted button in the error colour, for\n * \"Delete project\" and its kind; a destructive action is a button like any\n * other, not a dotted link \u2014 and `type=\"button\"` by default, so a button\n * inside a form submits it only when asked to.\n *\n * Three variants and two sizes: 32px default / 24px small, 6px radius, 500\n * weight. Sizes are spelled as arbitrary values for the reason the README\n * records: tailwind-merge drops a custom `text-{size}` that is followed by a\n * `text-{colour}`.\n *\n * The primary reads in `text-inverse` on the accent \u2014 a theme decides what\n * that is (dark on Peek's light accents, light on Ship's dark one). Under\n * Signal it is also semibold, as Peek has it.\n */\nexport type ButtonVariant = 'primary' | 'outlined' | 'muted' | 'destructive'\nexport type ButtonSize = 'default' | 'small'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant\n size?: ButtonSize\n /** 16px, stroke 1.5 on the default size; 14px on small. */\n leadingIcon?: ReactNode\n children: ReactNode\n}\n\nexport function Button({\n variant = 'muted',\n size = 'default',\n leadingIcon,\n className,\n children,\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) {\n const hasLeadingIcon = !!leadingIcon\n return (\n <button\n type={type}\n className={cn(\n 'inline-flex items-center justify-center gap-1 rounded-md transition-colors font-sans font-medium',\n size === 'default' && 'h-8 text-[14px] leading-[14px]',\n size === 'small' && 'h-6 text-[12px] leading-[12px]',\n // Extra right padding beside a leading icon, for optical balance.\n size === 'default' && (hasLeadingIcon ? 'pl-2 pr-3' : 'px-2'),\n size === 'small' && (hasLeadingIcon ? 'pl-1.5 pr-2' : 'px-1.5'),\n !disabled && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse cursor-pointer signal:font-semibold',\n !disabled && variant === 'outlined' && 'border border-border-default hover:bg-bg-hover text-text-primary cursor-pointer',\n !disabled && variant === 'muted' && 'hover:bg-bg-hover text-text-primary cursor-pointer',\n !disabled && variant === 'destructive' && 'hover:bg-error-muted text-error-default cursor-pointer',\n disabled && 'bg-bg-disabled text-text-disabled pointer-events-none',\n disabled && variant === 'outlined' && 'border border-border-default',\n className,\n )}\n disabled={disabled}\n {...props}\n >\n {leadingIcon}\n {children}\n </button>\n )\n}\n", "import { IconCheck } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * A 16px square that fills with the accent when checked \u2014 Peek's Checkbox\n * (2026-09-01), verbatim.\n *\n * Controlled only; parents own the state. Rendered as a button with the\n * checkbox role so it works standalone or inside clickable rows: a caller\n * that toggles on the row's own click passes no `onChange`, and the square\n * goes inert rather than stealing the click.\n */\nexport interface CheckboxProps {\n checked: boolean\n onChange?: (checked: boolean) => void\n disabled?: boolean\n 'aria-label'?: string\n className?: string\n}\n\nexport function Checkbox({ checked, onChange, disabled = false, className, ...aria }: CheckboxProps) {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={checked}\n aria-label={aria['aria-label']}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation()\n onChange?.(!checked)\n }}\n className={cn(\n 'inline-flex items-center justify-center size-4 shrink-0 rounded-[4px] border transition-colors',\n checked\n ? 'bg-accent-primary border-accent-primary text-text-inverse'\n : 'bg-transparent border-border-strong hover:border-text-muted',\n disabled && 'opacity-50 pointer-events-none',\n onChange ? 'cursor-pointer' : 'pointer-events-none',\n className,\n )}\n >\n {checked && <IconCheck size={12} stroke={3} />}\n </button>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Chip (2026-08-28), verbatim: a 20px pill in one of six colour\n * types, with room for a 12px icon either side. Under Signal the label is\n * mono and the coloured types carry a hairline, as Peek has it. The icon\n * slots centre their icon (Ship's addition \u2014 an icon beside text otherwise\n * rides on the baseline).\n *\n * The label is the `chip` type token, 11px / 500; it is a plain class here,\n * never merged, so tailwind-merge cannot drop it.\n */\nexport type ChipType = 'neutral' | 'brand' | 'info' | 'warning' | 'success' | 'error'\n\nexport interface ChipProps {\n type?: ChipType\n label?: string\n leadingIcon?: ReactNode\n trailingIcon?: ReactNode\n className?: string\n}\n\nconst typeStyles: Record<ChipType, string> = {\n neutral: 'bg-bg-inset text-text-primary',\n brand: 'bg-accent-muted text-accent-primary signal:border signal:border-[rgba(86,200,255,0.3)]',\n info: 'bg-info-muted text-info-default signal:border signal:border-[rgba(86,200,255,0.3)]',\n warning: 'bg-warning-muted text-warning-default signal:border signal:border-[rgba(255,176,32,0.3)] signal:shadow-[shadow:0_0_5px_rgba(255,176,32,0.4)]',\n success: 'bg-success-muted text-success-default signal:border signal:border-[rgba(63,222,140,0.3)]',\n error: 'bg-error-muted text-error-default signal:border signal:border-[rgba(255,107,107,0.3)]',\n}\n\nexport function Chip({ type = 'neutral', label, leadingIcon, trailingIcon, className }: ChipProps) {\n return (\n <div className={cn('inline-flex items-center justify-center gap-1.5 rounded-full max-h-[20px] min-w-[16px] px-2 py-1', typeStyles[type], className)}>\n {leadingIcon && <span className=\"flex size-3 shrink-0 items-center justify-center\">{leadingIcon}</span>}\n {label && (\n <span className=\"text-chip whitespace-nowrap signal:font-mono signal:text-[10px] signal:font-semibold signal:tracking-[0.02em] signal:tabular-nums\">{label}</span>\n )}\n {trailingIcon && <span className=\"flex size-3 shrink-0 items-center justify-center\">{trailingIcon}</span>}\n </div>\n )\n}\n", "import { useState, useRef, useMemo, useEffect, useLayoutEffect, type KeyboardEvent, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconX } from '@tabler/icons-react'\nimport { cn } from './cn'\nimport { MenuItem } from './Menu'\n\n/**\n * The chip a `ChipInput` is made of: a 24px pill with an optional 16px\n * leading (a face, an icon), a 12px label, and the \u2715 that removes it.\n * Exported on its own (Katerina, 2026-09-01) under a name that promises\n * nothing about people \u2014 a chip like this may one day hold a label, a file,\n * a filter.\n */\nexport interface InputChipProps {\n label: string\n /** Before the label, 16px \u2014 an Avatar, an icon. */\n leading?: ReactNode\n /** Draws the \u2715; absent, the chip is display-only. */\n onRemove?: () => void\n className?: string\n}\n\nexport function InputChip({ label, leading, onRemove, className }: InputChipProps) {\n return (\n <div\n className={cn(\n // Curved, not a pill (Katerina, 2026-09-01): the Avatar keeps its own\n // rounded-sm corners \u2014 never a forced circle \u2014 and the chip's corner\n // follows concentrically: 4px face + 2px inset = rounded-md.\n 'inline-flex items-center gap-1.5 bg-bg-elevated border border-border-subtle rounded-md py-0.5 max-h-[24px]',\n // The padding follows the contents (Katerina, 2026-09-01): a face\n // sits 2px from the edge, a bare label needs 8px of air; the \u2715\n // brings its own box, so 4px behind it \u2014 8px when there isn't one.\n leading ? 'pl-[2px]' : 'pl-2',\n onRemove ? 'pr-1' : 'pr-2',\n className,\n )}\n >\n {leading && <span className=\"flex shrink-0 items-center\">{leading}</span>}\n <span className=\"text-caption font-medium text-text-primary\">{label}</span>\n {onRemove && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onRemove()\n }}\n className=\"size-4 flex items-center justify-center rounded-full hover:bg-bg-hover text-text-secondary\"\n aria-label={`Remove ${label}`}\n >\n <IconX size={10} stroke={1.5} />\n </button>\n )}\n </div>\n )\n}\n\n/**\n * A multi-select input: chips for the chosen, a typeahead for the rest \u2014\n * Peek's PersonChipInput (2026-09-01), generalised on the way in. Peek's\n * version knew it was picking people: it read the directory from Peek's own\n * data layer and drew every face itself. Here the caller hands in `options`,\n * and \u2014 when the entries have faces or icons \u2014 the two leading slots: 16px\n * in a chip, 32px in a suggestion row. Nothing in this file knows what is\n * being picked.\n *\n * Suggestions appear only once the user types \u2014 focusing (or auto-focus on\n * dialog open) must not drop the full directory over the surface below.\n * Backspace on an empty query removes the last chip; Escape clears the query\n * when there is one and bubbles when there is not, so the surface around it\n * (dialog, launcher) can act.\n *\n * Generic over the option type: the objects handed back through `onChange`\n * are the caller's own, extra fields and all \u2014 no re-mapping on the way out.\n */\nexport interface ChipInputOption {\n id: string\n label: string\n /** The suggestion row's second line \u2014 a role, an address. Also searched. */\n description?: string\n}\n\nexport interface ChipInputProps<T extends ChipInputOption = ChipInputOption> {\n value: T[]\n onChange: (next: T[]) => void\n /** The directory the typeahead searches. */\n options: T[]\n placeholder?: string\n autoFocus?: boolean\n /** Option ids excluded from the suggestion list (e.g., the current user). */\n excludeIds?: string[]\n /** Before a chip's label, 16px \u2014 a face, an icon. */\n chipLeading?: (option: T) => ReactNode\n /** Before a suggestion row's label, 32px. */\n rowLeading?: (option: T) => ReactNode\n}\n\nexport function ChipInput<T extends ChipInputOption = ChipInputOption>({\n value,\n onChange,\n options,\n placeholder = 'Search\u2026',\n autoFocus,\n excludeIds = [],\n chipLeading,\n rowLeading,\n}: ChipInputProps<T>) {\n const [query, setQuery] = useState('')\n const [highlight, setHighlight] = useState(0)\n const [isFocused, setIsFocused] = useState(false)\n const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null)\n const wrapperRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n\n const matches = useMemo(() => {\n const selectedIds = new Set(value.map((o) => o.id))\n const excludedIds = new Set(excludeIds)\n const q = query.trim().toLowerCase()\n return options.filter((o) => {\n if (selectedIds.has(o.id)) return false\n if (excludedIds.has(o.id)) return false\n if (!q) return true\n return o.label.toLowerCase().includes(q) || (o.description ?? '').toLowerCase().includes(q)\n })\n }, [query, value, excludeIds, options])\n\n useEffect(() => {\n setHighlight(0)\n }, [query, matches.length])\n\n const showDropdown = isFocused && query.trim().length > 0 && matches.length > 0\n\n useLayoutEffect(() => {\n if (!showDropdown) return\n const update = () => {\n if (wrapperRef.current) setAnchorRect(wrapperRef.current.getBoundingClientRect())\n }\n update()\n window.addEventListener('resize', update)\n window.addEventListener('scroll', update, true)\n return () => {\n window.removeEventListener('resize', update)\n window.removeEventListener('scroll', update, true)\n }\n }, [showDropdown, value.length])\n\n function addOption(option: T) {\n onChange([...value, option])\n setQuery('')\n inputRef.current?.focus()\n }\n\n function removeOption(id: string) {\n onChange(value.filter((o) => o.id !== id))\n }\n\n function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {\n if (e.key === 'Backspace' && query === '' && value.length > 0) {\n // Consumed: removing a chip must not double as the surface's \"back\".\n e.preventDefault()\n removeOption(value[value.length - 1].id)\n return\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setHighlight((h) => Math.min(h + 1, Math.max(0, matches.length - 1)))\n return\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n setHighlight((h) => Math.max(h - 1, 0))\n return\n }\n if (e.key === 'Enter') {\n e.preventDefault()\n const target = matches[highlight]\n if (target) addOption(target)\n return\n }\n if (e.key === 'Escape') {\n // Consume it only when there is something to clear; an idle input lets\n // Escape bubble so the surface around it (dialog, launcher) can act.\n if (query !== '') {\n e.preventDefault()\n setQuery('')\n }\n }\n }\n\n return (\n <div className=\"relative\">\n <div\n ref={wrapperRef}\n className=\"bg-bg-inset border border-border-default hover:border-border-strong focus-within:border-border-focus focus-within:hover:border-border-focus rounded-lg px-3 py-1.5 flex flex-wrap items-center gap-1.5 transition-colors min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring\"\n onClick={() => inputRef.current?.focus()}\n >\n {value.map((o) => (\n <InputChip key={o.id} label={o.label} leading={chipLeading?.(o)} onRemove={() => removeOption(o.id)} />\n ))}\n\n <input\n ref={inputRef}\n autoFocus={autoFocus}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={handleKeyDown}\n onFocus={() => setIsFocused(true)}\n onBlur={() => {\n setTimeout(() => setIsFocused(false), 150)\n }}\n placeholder={value.length === 0 ? placeholder : ''}\n className=\"flex-1 min-w-[120px] bg-transparent text-body-2 text-text-primary placeholder:text-text-muted outline-none border-none\"\n />\n </div>\n\n {showDropdown && anchorRect && createPortal(\n <div\n className=\"fixed z-[60] max-h-[240px] overflow-y-auto bg-bg-elevated border border-border-default rounded-lg shadow-lg\"\n style={{\n top: anchorRect.bottom + 4,\n left: anchorRect.left,\n width: anchorRect.width,\n }}\n >\n {matches.map((o, i) => (\n <MenuItem\n key={o.id}\n size=\"tall\"\n className=\"h-12 rounded-none\"\n leading={rowLeading?.(o)}\n label={o.label}\n description={o.description}\n selected={i === highlight}\n onMouseEnter={() => setHighlight(i)}\n onMouseDown={(e) => {\n e.preventDefault()\n addOption(o)\n }}\n />\n ))}\n </div>,\n document.body\n )}\n </div>\n )\n}\n", "import { useEffect, useRef, type ComponentPropsWithRef, type CSSProperties, type ReactNode } from 'react'\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { createPortal } from 'react-dom'\nimport { cn } from './cn'\nimport { SectionLabel } from './SectionLabel'\n\n/**\n * THE menu shell (2026-09-01) \u2014 extracted once, for every menu in every app.\n *\n * Peek has no single Menu file; its topic menu, conversation menus, files\n * menu and the top bar's account menu each hand-roll the same thing, and\n * they disagree: some close on Escape, most do not, and row heights and\n * minimum widths drift copy by copy. Ship extracted the shape so it would\n * not become the next copy; the package exists so nobody becomes the one\n * after that.\n *\n * What every menu shares, kept exactly: an elevated container with a\n * hairline border, 8px radius, 8px padding and the large shadow; items that\n * are 8px-radius rows, `px-2 py-1.5`, hover fill, 14px text, destructive\n * ones in the error colour; section headings as a SectionLabel in a 32px\n * row; and the two exits every menu has \u2014 Escape and a click outside \u2014\n * owned by the menu, never copied into a caller.\n *\n * Two anchorings: `position` portals it to the body at fixed viewport\n * coordinates (for menus opened inside scrolling containers that clip);\n * without it the menu hangs below its trigger, right-aligned, and the\n * caller's wrapper is `relative`.\n */\nexport interface MenuProps {\n onClose: () => void\n /** Viewport coordinates; the menu is portalled, hung from `top`, and aligned to whichever edge is given. */\n position?: { top: number; right: number } | { top: number; left: number }\n children: ReactNode\n className?: string\n}\n\nexport function Menu({ onClose, position, children, className }: MenuProps) {\n const ref = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const onDown = (event: MouseEvent) => {\n if (!ref.current?.contains(event.target as Node)) onClose()\n }\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onClose()\n }\n document.addEventListener('mousedown', onDown)\n document.addEventListener('keydown', onKey)\n return () => {\n document.removeEventListener('mousedown', onDown)\n document.removeEventListener('keydown', onKey)\n }\n }, [onClose])\n\n const style: CSSProperties | undefined = position ? { top: position.top, ...('left' in position ? { left: position.left } : { right: position.right }) } : undefined\n const node = (\n <div\n ref={ref}\n role=\"menu\"\n data-interactive\n className={cn(\n 'z-50 flex min-w-[180px] flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg',\n position ? 'fixed' : 'absolute right-0 top-full mt-1',\n className,\n )}\n style={style}\n onClick={(event) => event.stopPropagation()}\n >\n {children}\n </div>\n )\n return position ? createPortal(node, document.body) : node\n}\n\nexport interface MenuItemProps extends Omit<ComponentPropsWithRef<'button'>, 'children'> {\n label?: string\n /** Replaces the label/description block \u2014 for rows whose middle is richer\n * than text (the launcher's form rows). Leading/trailing still apply. */\n children?: ReactNode\n /** `tall` is the picker row \u2014 content height with a 40px floor, px-3,\n * gap-3, room for a 32px face or icon tile and the description line.\n * `default` is the command row. */\n size?: 'default' | 'tall'\n /** A second line under the label \u2014 a role, an address \u2014 12px, secondary, truncating. */\n description?: string\n /** Before the label: a 16px icon (stroke 1.5, secondary), or an Avatar, for rows led by a face. */\n leading?: ReactNode\n /** At the right edge: a hint, a value \u2014 anything. Wins over `shortcut` and `submenu`. */\n trailing?: ReactNode\n /** A keyboard hint, drawn as the kbd chip. */\n shortcut?: string\n /** The row opens another menu: draws the chevron at the right edge. */\n submenu?: boolean\n destructive?: boolean\n /** The row the menu currently points at (a submenu's chosen value). */\n selected?: boolean\n}\n\nexport function MenuItem({ label, children, size = 'default', description, leading, trailing, shortcut, submenu, destructive, selected, className, ...props }: MenuItemProps) {\n const edge =\n trailing ??\n (shortcut ? (\n <kbd className=\"inline-flex shrink-0 items-center justify-center rounded-sm border border-border-strong bg-bg-inset px-1 py-px text-caption text-text-secondary\">\n {shortcut}\n </kbd>\n ) : submenu ? (\n <IconChevronRight size={16} stroke={1.5} className=\"shrink-0 text-text-muted\" />\n ) : null)\n return (\n <button\n type=\"button\"\n role=\"menuitem\"\n className={cn(\n 'flex w-full cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover transition-colors',\n // tall: as tall as its content, never shorter than 40px (Katerina,\n // 2026-09-01) \u2014 a single-line picker row sits at 40, a row with a\n // 32px face and a role line comes out at its natural 48. One rule,\n // not a hand-picked height per file.\n size === 'tall' ? 'min-h-10 gap-3 px-3 py-1.5' : 'gap-2 px-2 py-1.5',\n selected && 'bg-bg-hover',\n className,\n )}\n {...props}\n >\n {leading && <span className=\"flex shrink-0 items-center\">{leading}</span>}\n {/* Sizes are arbitrary values (the body-2 and caption tokens): these\n lists merge with a colour, and tw-merge drops a token size beside a\n colour. Ship's copy said `text-sm`, which was never the ramp. */}\n {children ?? (\n <span className={cn('flex min-w-0 flex-1 flex-col', size === 'tall' && 'gap-[2px]')}>\n <span className={cn('truncate text-[14px] leading-[140%]', destructive ? 'text-error-default' : 'text-text-primary')}>\n {label}\n </span>\n {description && <span className=\"truncate text-[12px] leading-[120%] text-text-secondary\">{description}</span>}\n </span>\n )}\n {edge && <span className=\"flex shrink-0 items-center\">{edge}</span>}\n </button>\n )\n}\n\n/**\n * The keyboard hint a picker row shows while highlighted \u2014 \"\u21A9 Enter\",\n * \"\u21A9 #topic\" \u2014 hand-rolled in five files before this (2026-09-01).\n */\nexport function EnterHint({ label = 'Enter' }: { label?: string }) {\n return (\n <span className=\"flex shrink-0 items-center gap-2 text-text-muted\">\n <span className=\"text-[12px] leading-[120%]\">\u21A9</span>\n <span className=\"text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]\">{label}</span>\n </span>\n )\n}\n\n/** A section heading inside a menu: the 32px row with a SectionLabel, read\n * secondary \u2014 a heading inside a menu labels the rows, it is not one of\n * them (Katerina, 2026-09-01). */\nexport function MenuSection({ label, children, className }: { label: string; children: ReactNode; /** On the heading row \u2014 a surface whose rows are px-3 aligns its heading with px-3. */ className?: string }) {\n return (\n <div className=\"flex flex-col\">\n <div className={cn('flex h-8 items-center px-2', className)}>\n <SectionLabel className=\"text-text-secondary\">{label}</SectionLabel>\n </div>\n {children}\n </div>\n )\n}\n\n/** A non-interactive row inside a menu, at the item's own geometry. */\nexport function MenuRow({ children, className }: { children: ReactNode; className?: string }) {\n return <div className={cn('flex items-center gap-2 px-2 py-1.5', className)}>{children}</div>\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * THE section-title label \u2014 every section heading (sidebar sections, menu\n * headings, panel titles) renders through this span, so a style change is\n * one edit. Peek's original, verbatim: 12px / 12px / 500, primary text\n * (Peek's ruling 2026-07-23: labels stay primary), the mono uppercase\n * micro-label under Signal. Metrics as arbitrary values for the tw-merge\n * reason the README records.\n *\n * Deliberately NOT the same thing as Property's stacked field label (the\n * 9px `menu`-token one): they were merged for a day and unmerged by\n * Katerina's ruling (2026-09-01) \u2014 a section title and a field label are\n * different voices, at different sizes, on purpose.\n */\nexport function SectionLabel({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <span\n className={cn(\n 'text-[12px] leading-[12px] font-medium text-text-primary signal:font-mono signal:text-[10px] signal:uppercase signal:tracking-[0.14em]',\n className,\n )}\n >\n {children}\n </span>\n )\n}\n", "import { useCallback, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { cn } from './cn'\n\n/**\n * Peek's Tooltip and WithTooltip (2026-08-28), verbatim, in one file.\n *\n * `Tooltip` is the surface: a 30px elevated pill with a caption. `WithTooltip`\n * wraps a trigger and portals the surface above or below it on hover, fixed\n * to the viewport and kept 8px inside it. The wrapper is `inline-flex` and\n * shrinks nothing \u2014 wrap a control, never a block (a form inside it\n * collapses to its content width; Ship learnt that).\n */\nexport interface TooltipProps {\n label: string\n className?: string\n}\n\nexport function Tooltip({ label, className }: TooltipProps) {\n return (\n <div role=\"tooltip\" className={cn('bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center px-2 shadow-lg', className)}>\n <span className=\"text-caption text-text-primary whitespace-nowrap\">{label}</span>\n </div>\n )\n}\n\nexport interface WithTooltipProps {\n label: string\n placement?: 'top' | 'bottom'\n /** Extra classes on the wrapper \u2014 e.g. `min-w-0 shrink` so a truncating label keeps truncating inside it. */\n wrapperClassName?: string\n children: ReactNode\n}\n\nconst GAP = 6\nconst VIEWPORT_PAD = 8\n\nexport function WithTooltip({ label, placement = 'top', wrapperClassName, children }: WithTooltipProps) {\n const [show, setShow] = useState(false)\n const ref = useRef<HTMLDivElement>(null)\n const tooltipRef = useRef<HTMLDivElement>(null)\n const [style, setStyle] = useState<CSSProperties>({ position: 'fixed', zIndex: 9999, pointerEvents: 'none', visibility: 'hidden' })\n\n const reposition = useCallback(() => {\n const trigger = ref.current\n const tip = tooltipRef.current\n if (!trigger || !tip) return\n const triggerRect = trigger.getBoundingClientRect()\n const tipRect = tip.getBoundingClientRect()\n const top = placement === 'bottom' ? triggerRect.bottom + GAP : triggerRect.top - tipRect.height - GAP\n let left = triggerRect.left + triggerRect.width / 2 - tipRect.width / 2\n left = Math.max(VIEWPORT_PAD, Math.min(left, window.innerWidth - tipRect.width - VIEWPORT_PAD))\n setStyle({ position: 'fixed', top, left, zIndex: 9999, pointerEvents: 'none', visibility: 'visible' })\n }, [placement])\n\n useLayoutEffect(() => {\n if (show) reposition()\n }, [show, reposition])\n\n return (\n <div ref={ref} className={cn('inline-flex shrink-0', wrapperClassName)} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show &&\n createPortal(\n <div ref={tooltipRef} style={style}>\n <Tooltip label={label} />\n </div>,\n document.body,\n )}\n </div>\n )\n}\n", "import type { ButtonHTMLAttributes, ReactNode } from 'react'\nimport { cn } from './cn'\nimport { WithTooltip } from './Tooltip'\n\n/**\n * Peek's IconButton (2026-08-28), verbatim: a square 4px-padded button\n * around a 16px icon, 8px radius, three variants, an optional tooltip that\n * portals above or below. Plus `type=\"button\"` by default (Ship's addition),\n * so it never submits a form by accident.\n */\nexport type IconButtonVariant = 'muted' | 'outlined' | 'primary'\n\nexport interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: IconButtonVariant\n tooltip?: string\n tooltipPlacement?: 'top' | 'bottom'\n /** The icon: 16px, stroke 1.5. */\n children: ReactNode\n}\n\nexport function IconButton({\n variant = 'muted',\n className,\n children,\n disabled,\n tooltip,\n tooltipPlacement,\n type = 'button',\n ...props\n}: IconButtonProps) {\n const button = (\n <button\n type={type}\n className={cn(\n 'flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 cursor-pointer',\n !disabled && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse',\n !disabled && variant === 'muted' && 'text-text-secondary hover:bg-bg-hover hover:text-text-primary',\n !disabled && variant === 'outlined' && 'border border-border-default hover:bg-bg-hover text-text-secondary',\n disabled && variant === 'primary' && 'bg-bg-disabled text-text-disabled',\n disabled && variant === 'muted' && 'text-text-disabled',\n disabled && variant === 'outlined' && 'border border-border-default text-text-disabled',\n disabled && 'pointer-events-none cursor-not-allowed',\n className,\n )}\n disabled={disabled}\n {...props}\n >\n {children}\n </button>\n )\n\n if (tooltip) {\n return (\n <WithTooltip label={tooltip} placement={tooltipPlacement}>\n {button}\n </WithTooltip>\n )\n }\n return button\n}\n", "import { useState, type ReactNode } from 'react'\nimport { cn } from './cn'\nimport { Divider } from './Divider'\nimport { Menu, MenuItem, MenuRow, MenuSection } from './Menu'\nimport { Person } from './Person'\nimport { PersonTrigger } from './PersonTrigger'\n\n/**\n * Who you are, and where you are \u2014 the top bar's identity trigger and the\n * menu it opens. Ship's IdentityMenu (2026-09-01), moved in whole: every app\n * needs one, and this one is already made of nothing but shared parts.\n *\n * The trigger is a face and a name, never a key (Ship's ruling A4) \u2014 or, with\n * `compact`, the face alone, as Peek's top bar draws it. Anonymous is a\n * silhouette and the word \"Anonymous\": the truth of the situation rather than\n * eight hex characters posing as a name.\n *\n * The menu keeps the two honest sentences, and the workspace line names the\n * relay because on this substrate the relay *is* the workspace. Every section\n * hides when the app cannot fill it: no `relayUrl`, no workspace section; no\n * `onCopyKey`, no copy item \u2014 only offer actions that can succeed. The one\n * place a key appears is as the *result* of \"Copy public key\" \u2014 this\n * component never receives the key, so it cannot show one; the caller\n * copies. Items are text only, no icons (her ruling).\n */\nexport interface Identity {\n /** From `kind:0`. Absent means anonymous. */\n name?: string\n picture?: string\n /** Known to Estiva ID, never published to the relay. */\n email?: string\n}\n\nexport interface IdentityMenuProps {\n me: Identity\n /** Signed in through Estiva ID, as opposed to a browser-held key. */\n signedIn: boolean\n /** Names the workspace; absent hides the workspace section. */\n relayUrl?: string\n /** Estiva ID's base URL when this build offers sign-in; absent = anonymous-only build. */\n idBase?: string\n /** \"Copy public key\" appears only when the app can supply one. */\n onCopyKey?: () => void\n onSignOut?: () => void\n /** Face-only trigger, 36px \u2014 no chevron, no name beside it. */\n compact?: boolean\n className?: string\n /**\n * App-specific rows, drawn as their own group between the workspace\n * section and the actions. A function receives `close`, so a row can\n * shut the menu before opening what it opens.\n */\n children?: ReactNode | ((close: () => void) => ReactNode)\n}\n\nexport interface IdentityPanelProps extends Omit<IdentityMenuProps, 'compact' | 'className'> {\n onClose: () => void\n /** On the Menu surface \u2014 the stories pass `static` to stand it in flow. */\n className?: string\n}\n\n/** The menu alone \u2014 what `IdentityMenu` opens. Exported so the stories show\n * the designed artifact rather than a closed trigger, and for any surface\n * that wants the panel without the trigger. */\nexport function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, className, children }: IdentityPanelProps) {\n const act = (action: () => void) => () => {\n onClose()\n action()\n }\n const appRows = typeof children === 'function' ? children(onClose) : children\n return (\n <Menu onClose={onClose} className={cn('w-72', className)}>\n <MenuSection label={signedIn ? 'Signed in as' : 'Acting as'}>\n <MenuRow>\n <Person name={me.name} picture={me.picture} fallback=\"Anonymous\" size={28} className=\"text-body-2-strong\" />\n </MenuRow>\n <Note>\n {signedIn\n ? 'Your Estiva ID. The same person you are in every Estiva app.'\n : 'A key held by this browser only. Nobody else knows who you are.'}\n </Note>\n {me.email && <Note>{me.email} \u00B7 known to Estiva, never published to the relay</Note>}\n </MenuSection>\n\n {relayUrl && (\n <>\n <Divider className=\"mx-0 my-2\" />\n <MenuSection label=\"Workspace\">\n <MenuRow>\n <span className=\"break-all font-mono text-caption text-text-primary\">{relayUrl}</span>\n </MenuRow>\n <Note>Everyone on this relay shares this workspace, in every app.</Note>\n </MenuSection>\n </>\n )}\n\n {appRows && (\n <>\n <Divider className=\"mx-0 my-2\" />\n {appRows}\n </>\n )}\n\n {(signedIn && idBase) || onCopyKey || (idBase && onSignOut) ? <Divider className=\"mx-0 my-2\" /> : null}\n\n {signedIn && idBase && (\n <MenuItem\n label=\"Edit your profile in Estiva ID\"\n onClick={act(() => window.open(idBase, '_blank', 'noopener,noreferrer'))}\n />\n )}\n {onCopyKey && <MenuItem label=\"Copy public key\" onClick={act(onCopyKey)} />}\n {idBase && onSignOut && <MenuItem label=\"Sign out\" onClick={act(onSignOut)} />}\n </Menu>\n )\n}\n\nexport function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, compact = false, className, children }: IdentityMenuProps) {\n const [open, setOpen] = useState(false)\n\n return (\n <div className={cn('relative', className)}>\n <PersonTrigger\n name={me.name}\n picture={me.picture}\n fallback=\"Anonymous\"\n compact={compact}\n size={compact ? 36 : undefined}\n open={open}\n // Swallowed so the menu's outside-mousedown dismiss does not fire\n // first and turn the toggle into a close-then-reopen flicker.\n onMouseDown={(event) => event.stopPropagation()}\n onClick={() => setOpen((value) => !value)}\n // The row shape is named by its own text \u2014 the person. Only the bare\n // face needs a label; naming the row would override the person's name\n // as the accessible name (Ship's tests find the trigger by it).\n aria-label={compact ? 'Account menu' : undefined}\n />\n\n {open && (\n <IdentityPanel\n me={me}\n signedIn={signedIn}\n relayUrl={relayUrl}\n idBase={idBase}\n onCopyKey={onCopyKey}\n onSignOut={onSignOut}\n onClose={() => setOpen(false)}\n >\n {children}\n </IdentityPanel>\n )}\n </div>\n )\n}\n\nfunction Note({ children }: { children: ReactNode }) {\n return <span className=\"px-2 pb-1.5 text-caption text-text-secondary\">{children}</span>\n}\n", "import { cn } from './cn'\n\n/**\n * Peek's Divider (2026-08-28): a hairline in `border-subtle`, inset 12px each\n * side. Plus what Ship added: `orientation=\"vertical\"` \u2014 the same hairline\n * standing up, stretching to its row's height, no inset \u2014 and the separator\n * role for assistive tech.\n */\nexport interface DividerProps {\n orientation?: 'horizontal' | 'vertical'\n className?: string\n}\n\nexport function Divider({ orientation = 'horizontal', className }: DividerProps) {\n return (\n <div\n role=\"separator\"\n aria-orientation={orientation}\n className={cn('bg-border-subtle', orientation === 'horizontal' ? 'h-px mx-3' : 'w-px self-stretch shrink-0', className)}\n />\n )\n}\n", "import { cn } from './cn'\nimport { Avatar } from './Avatar'\n\n/**\n * A face beside a name. Ship's Person (2026-09-01), which was Peek's minus\n * the projection type it took its profile from: this one is handed the two\n * strings and nothing else, so it cannot be handed a key. Peek keeps a\n * wrapper that resolves the profile, the way its Avatar wrapper resolves the\n * picture.\n *\n * A face beside a name needs no caption (Peek's rulings), and someone nobody\n * has published a name for gets the `fallback` \u2014 an em dash by default, the\n * same mark a property with no value uses \u2014 rather than the key back on\n * screen to say it. Pass \"Anonymous\" where the unnamed person is *you*, and\n * you know who that is.\n *\n * The text size is the caller's, so the face and the words are always set\n * together \u2014 see the Sizes story.\n */\nexport interface PersonProps {\n name?: string\n picture?: string\n /** Shown in place of a missing name. */\n fallback?: string\n size?: number\n className?: string\n}\n\nexport function Person({ name, picture, fallback = '\u2014', size = 20, className }: PersonProps) {\n return (\n // gap-2: the face-to-name distance is one rule (8px) wherever a person\n // appears \u2014 Katerina, 2026-09-01, set on Person so PersonTrigger and every\n // row agree by construction.\n <span className={cn('flex min-w-0 items-center gap-2', className)}>\n <Avatar name={name} src={picture} size={size} />\n <span className={cn('truncate', !name && 'text-text-muted')}>{name ?? fallback}</span>\n </span>\n )\n}\n", "import type { ComponentPropsWithRef } from 'react'\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { cn } from './cn'\nimport { Avatar } from './Avatar'\nimport { Person, type PersonProps } from './Person'\n\n/**\n * The person, as the button that opens the account menu.\n *\n * Two shapes, one component (Katerina, 2026-09-01):\n *\n * - the row \u2014 face \u00B7 name \u00B7 chevron on a 32px button with a hover fill, as\n * Ship's top bar draws it. The chevron is 14px, the small-control size.\n * - `compact` \u2014 the face alone, as Peek's top bar draws it: no chevron, no\n * padding, and no hover fill, because the face fills the whole control and\n * a fill would have nowhere to show.\n *\n * What it opens \u2014 the menu and everything in it \u2014 stays in the app.\n */\nexport interface PersonTriggerProps\n extends Omit<PersonProps, 'className'>,\n Omit<ComponentPropsWithRef<'button'>, 'children'> {\n /** Whether what it opens is open \u2014 the row shape holds its hover fill while so. */\n open?: boolean\n /** Face only \u2014 no chevron, no hover fill. Defaults the face to 36px; the row shape to 22px. */\n compact?: boolean\n}\n\nexport function PersonTrigger({ name, picture, fallback, size, open = false, compact = false, className, ...props }: PersonTriggerProps) {\n if (compact) {\n return (\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={cn('cursor-pointer rounded-full focus:outline-none', className)}\n {...props}\n >\n <Avatar name={name} src={picture} size={size ?? 36} />\n </button>\n )\n }\n return (\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={cn(\n // The size is an arbitrary value (the body-2 token): this list goes through\n // cn(), and tw-merge silently drops a custom text-{size} once a\n // text-{colour} follows it. Measured: the name rendered 16px.\n 'flex h-8 cursor-pointer items-center gap-1.5 rounded-md pl-1.5 pr-1.5 text-[14px] leading-[140%] text-text-primary transition-colors hover:bg-bg-hover',\n open && 'bg-bg-hover',\n className,\n )}\n {...props}\n >\n <Person name={name} picture={picture} fallback={fallback} size={size ?? 22} />\n <IconChevronDown size={14} stroke={1.5} className=\"shrink-0 text-text-muted\" />\n </button>\n )\n}\n", "import type { ReactNode } from 'react'\n\n/**\n * Peek's Field (2026-08-28), verbatim: a label over a control, 8px apart,\n * with a red asterisk when required. The label is the `input-label` type\n * token as a plain class, never merged.\n */\nexport interface FieldProps {\n label: string\n required?: boolean\n children: ReactNode\n}\n\nexport function Field({ label, required = false, children }: FieldProps) {\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={`text-input-label text-text-primary${required ? ' flex items-center' : ''}`}>\n {label}\n {required && <span className=\"text-error-default ml-0.5\">*</span>}\n </label>\n {children}\n </div>\n )\n}\n", "import { IconCheck, IconChevronDown } from '@tabler/icons-react'\nimport { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { cn } from './cn'\n\n/**\n * Peek's Select (2026-08-28), verbatim, plus what Ship added: an option may\n * carry a `leading` node \u2014 an avatar beside a person's name \u2014 shown in the\n * trigger and in the list.\n *\n * A button that opens a portalled listbox under itself: arrows move (and keep\n * the active option scrolled into view), Home and End jump, Enter and Space\n * pick, Escape closes and returns focus to the trigger, Tab closes, a click\n * outside closes, a PAGE scroll or resize closes (the list is fixed to where\n * the trigger was) \u2014 a scroll *inside* the list is the list's own business\n * and must not dismiss it. The menu keeps itself on screen: clamped to the\n * viewport's sides, height capped to the space it actually has, opening\n * upward when the room below is worse than the room above (Katerina,\n * 2026-09-01 \u2014 the files-panel picker was cut right and bottom, and its own\n * scroll closed it). Two sizes; `disabled` explains nothing by itself \u2014 wrap\n * it in a tooltip that does.\n */\nexport interface SelectOption {\n value: string\n label: string\n /** Drawn before the label, 16px \u2014 an Avatar, an icon. */\n leading?: ReactNode\n}\n\nexport interface SelectProps {\n value: string\n onChange: (value: string) => void\n options: SelectOption[]\n size?: 'default' | 'small'\n ariaLabel?: string\n placeholder?: string\n disabled?: boolean\n className?: string\n}\n\n/**\n * Where a menu of this size goes, given its anchor and the viewport \u2014 pure,\n * so the geometry is testable without a browser.\n *\n * Left is clamped inside the viewport with an 8px margin. Height is capped\n * at 288px (the old `max-h-72`) but never taller than the space it opens\n * into; when the room below the anchor is smaller than both the content and\n * the room above, the menu opens UPWARD (anchored to the trigger's top via\n * `bottom`). The 120px floor keeps a menu usable even in a cramped corner \u2014\n * scrollable beats invisible.\n */\nexport function fitMenu({\n anchor,\n menu,\n viewport,\n}: {\n anchor: { left: number; top: number; bottom: number }\n menu: { width: number; contentHeight: number }\n viewport: { width: number; height: number }\n}): { left: number; top?: number; bottom?: number; maxHeight: number } {\n const MARGIN = 8\n const GAP = 4\n const CAP = 288\n const left = Math.max(MARGIN, Math.min(anchor.left, viewport.width - menu.width - MARGIN))\n const below = viewport.height - anchor.bottom - GAP - MARGIN\n const above = anchor.top - GAP - MARGIN\n const openUp = below < Math.min(menu.contentHeight, CAP) && above > below\n const maxHeight = Math.max(Math.min(CAP, openUp ? above : below), 120)\n return openUp\n ? { left, bottom: viewport.height - anchor.top + GAP, maxHeight }\n : { left, top: anchor.bottom + GAP, maxHeight }\n}\n\nexport function Select({ value, onChange, options, size = 'default', ariaLabel, placeholder = 'Select\u2026', disabled, className }: SelectProps) {\n const id = useId()\n const triggerRef = useRef<HTMLButtonElement>(null)\n const menuRef = useRef<HTMLDivElement>(null)\n const [rect, setRect] = useState<DOMRect | null>(null)\n /** Where the menu actually goes \u2014 measured against the viewport after the\n * provisional render, so it is never cut off by an edge. */\n const [placement, setPlacement] = useState<{\n left: number\n top?: number\n bottom?: number\n maxHeight: number\n } | null>(null)\n const open = rect !== null\n\n const selectedIndex = useMemo(() => Math.max(0, options.findIndex((o) => o.value === value)), [options, value])\n const [activeIndex, setActiveIndex] = useState(selectedIndex)\n\n const close = useCallback((refocus: boolean) => {\n setRect(null)\n if (refocus) triggerRef.current?.focus()\n }, [])\n\n const openMenu = useCallback(() => {\n setActiveIndex(selectedIndex)\n setRect(triggerRef.current?.getBoundingClientRect() ?? null)\n }, [selectedIndex])\n\n /*\n * Fit the menu to the viewport, before paint.\n *\n * The provisional render sits at the trigger's corner and is invisible;\n * this measures it and decides the real box: left clamped inside the\n * viewport, height capped to the space available, and the whole thing\n * opening UPWARD when the room below is smaller than both the content and\n * the room above. A menu that is always fully on screen is also the only\n * kind whose scrollbar can actually be used.\n */\n useLayoutEffect(() => {\n if (!rect || !menuRef.current) {\n setPlacement(null)\n return\n }\n setPlacement(\n fitMenu({\n anchor: { left: rect.left, top: rect.top, bottom: rect.bottom },\n menu: { width: menuRef.current.offsetWidth, contentHeight: menuRef.current.scrollHeight },\n viewport: { width: window.innerWidth, height: window.innerHeight },\n }),\n )\n }, [rect, options.length])\n\n // Keyboard follows the highlight: without this, arrowing past the fold\n // moved `activeIndex` into rows the capped menu never showed.\n useEffect(() => {\n if (!open) return\n // Optional call: jsdom implements neither scrolling nor this method, and\n // a consumer's component tests should not crash for a scroll nicety.\n document.getElementById(`${id}-${activeIndex}`)?.scrollIntoView?.({ block: 'nearest' })\n }, [open, activeIndex, id])\n\n // Outside click and Escape \u2014 the two exits every menu has. `mousedown`\n // rather than `click`, so a press that starts outside dismisses at once.\n useEffect(() => {\n if (!open) return\n const onDown = (e: MouseEvent) => {\n if (menuRef.current?.contains(e.target as Node)) return\n if (triggerRef.current?.contains(e.target as Node)) return\n setRect(null)\n }\n const onResize = () => setRect(null)\n /*\n * A PAGE scroll moves the anchor out from under the fixed menu, so it\n * closes. A scroll INSIDE the menu is the menu working as designed \u2014\n * capture phase sees those too, and closing on them made the list\n * impossible to scroll at all (the bug this comment survives to prevent).\n */\n const onScroll = (e: Event) => {\n if (e.target instanceof Node && menuRef.current?.contains(e.target)) return\n setRect(null)\n }\n document.addEventListener('mousedown', onDown)\n window.addEventListener('resize', onResize)\n window.addEventListener('scroll', onScroll, true)\n return () => {\n document.removeEventListener('mousedown', onDown)\n window.removeEventListener('resize', onResize)\n window.removeEventListener('scroll', onScroll, true)\n }\n }, [open])\n\n const pick = (index: number) => {\n const option = options[index]\n if (!option) return\n onChange(option.value)\n close(true)\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (!open) {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {\n e.preventDefault()\n openMenu()\n }\n return\n }\n switch (e.key) {\n case 'Escape':\n e.preventDefault()\n close(true)\n break\n case 'ArrowDown':\n e.preventDefault()\n setActiveIndex((i) => Math.min(options.length - 1, i + 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setActiveIndex((i) => Math.max(0, i - 1))\n break\n case 'Home':\n e.preventDefault()\n setActiveIndex(0)\n break\n case 'End':\n e.preventDefault()\n setActiveIndex(options.length - 1)\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n pick(activeIndex)\n break\n case 'Tab':\n close(false)\n break\n }\n }\n\n const selected = options.find((o) => o.value === value)\n\n return (\n <>\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={ariaLabel}\n onClick={() => (open ? close(false) : openMenu())}\n onKeyDown={onKeyDown}\n className={cn(\n /*\n * `min-w-0 max-w-full`: a trigger must never outgrow its container\n * (Katerina, 2026-09-01 \u2014 a long label stretched the files panel's\n * Lead row past its card). In a flex row the default min-width:auto\n * forbids shrinking below the label's width, which is what kept\n * `truncate` from ever engaging; in a block container max-w-full is\n * the cap. Full-width callers are unaffected.\n */\n 'flex w-full min-w-0 max-w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left',\n 'border-border-default text-text-primary outline-none transition-colors',\n // The focused border survives a hover: hover alone strengthens the\n // hairline, but hover while focused must not grey the focus colour \u2014\n // the stacked variant outranks plain hover by specificity.\n 'hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'focus-visible:border-border-focus aria-expanded:border-border-focus',\n 'signal:transition-shadow signal:focus-visible:shadow-focus-ring',\n size === 'default' && 'px-3 py-2 text-[14px] leading-[1.4] font-normal',\n size === 'small' && 'h-6 px-2 text-[12px] leading-[1.4] font-normal',\n className,\n )}\n >\n <span className={cn('flex min-w-0 items-center gap-2', !selected && 'text-text-muted')}>\n {selected?.leading && <span className=\"flex shrink-0 items-center\">{selected.leading}</span>}\n <span className=\"truncate\">{selected?.label ?? placeholder}</span>\n </span>\n <IconChevronDown size={size === 'small' ? 14 : 16} stroke={1.5} className=\"shrink-0 text-text-secondary\" />\n </button>\n\n {open &&\n rect &&\n createPortal(\n <div\n ref={menuRef}\n role=\"listbox\"\n aria-activedescendant={`${id}-${activeIndex}`}\n tabIndex={-1}\n style={\n placement\n ? { ...placement, minWidth: rect.width }\n : // Provisional frame: measured by the layout effect above,\n // replaced before paint. Hidden so a cut-off position is\n // never visible, not even for a frame.\n { top: rect.bottom + 4, left: rect.left, minWidth: rect.width, visibility: 'hidden' }\n }\n className=\"fixed z-50 overflow-y-auto rounded-lg border border-border-default bg-bg-elevated p-1 shadow-lg\"\n >\n {options.map((option, index) => (\n <div\n key={option.value}\n id={`${id}-${index}`}\n role=\"option\"\n aria-selected={option.value === value}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={(e) => {\n // The document-level dismiss also listens on mousedown.\n e.preventDefault()\n pick(index)\n }}\n className={cn(\n 'flex h-9 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 transition-colors',\n 'text-[14px] font-normal leading-[1.4] text-text-primary',\n index === activeIndex && 'bg-bg-hover',\n option.value === value && 'font-medium',\n )}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {option.leading && <span className=\"flex shrink-0 items-center\">{option.leading}</span>}\n <span className=\"truncate\">{option.label}</span>\n </span>\n {option.value === value && <IconCheck size={16} stroke={1.5} className=\"shrink-0 text-text-secondary\" />}\n </div>\n ))}\n </div>,\n document.body,\n )}\n </>\n )\n}\n", "import { forwardRef, type InputHTMLAttributes } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's TextInput (2026-08-28): the inset field with a 8px radius, 14px\n * text, the focus border in every theme (Katerina, 2026-08-28: Ship's inputs\n * focus like Signal's) \u2014 and Signal's glow on top.\n * Plus a disabled look (Ship's addition): the disabled surface and text,\n * no pointer.\n */\nexport type TextInputProps = InputHTMLAttributes<HTMLInputElement>\n\nexport const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function TextInput({ className, type = 'text', ...props }, ref) {\n return (\n <input\n ref={ref}\n type={type}\n className={cn(\n 'bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2',\n 'text-[14px] leading-[1.4] font-normal text-text-primary placeholder:text-text-muted',\n 'outline-none transition-colors',\n 'disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'signal:transition-shadow signal:focus:shadow-focus-ring',\n className,\n )}\n {...props}\n />\n )\n})\n", "import { forwardRef, type TextareaHTMLAttributes } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Textarea (2026-08-28), verbatim: TextInput's look on a textarea\n * that does not resize. Plus a disabled look (Ship's addition).\n */\nexport type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea({ className, ...props }, ref) {\n return (\n <textarea\n ref={ref}\n className={cn(\n 'bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2',\n 'text-[14px] leading-[1.4] font-normal text-text-primary placeholder:text-text-muted',\n 'resize-none outline-none transition-colors',\n 'disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled',\n 'signal:transition-shadow signal:focus:shadow-focus-ring',\n className,\n )}\n {...props}\n />\n )\n})\n", "import { useState, type ReactNode } from 'react'\nimport { Button } from './Button'\nimport { DialogShell } from './DialogShell'\n\n/**\n * A question with two answers, before something a person would want back \u2014\n * Ship's ConfirmDialog (2026-09-01), verbatim. Its own comment always called\n * it a package candidate: nothing here knows what is being confirmed.\n *\n * The confirm button is `destructive` when the action is (a deletion, an\n * archive); `primary` otherwise. While the action runs the buttons wait;\n * an action that resolves `false` keeps the dialog open, so the caller's\n * banner can say why.\n */\nexport interface ConfirmDialogProps {\n title: string\n /** The body \u2014 plain sentences, body-2. */\n children: ReactNode\n confirmLabel: string\n destructive?: boolean\n onConfirm: () => Promise<boolean | void> | boolean | void\n onClose: () => void\n}\n\nexport function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }: ConfirmDialogProps) {\n const [busy, setBusy] = useState(false)\n const confirm = async () => {\n setBusy(true)\n try {\n const ok = await onConfirm()\n if (ok !== false) onClose()\n } finally {\n setBusy(false)\n }\n }\n return (\n <DialogShell\n title={title}\n onClose={onClose}\n bodyClassName=\"flex flex-col gap-3 text-body-2 text-text-primary\"\n footer={\n <>\n <Button variant=\"muted\" onClick={onClose} disabled={busy}>\n Cancel\n </Button>\n <Button variant={destructive ? 'destructive' : 'primary'} onClick={() => void confirm()} disabled={busy}>\n {confirmLabel}\n </Button>\n </>\n }\n >\n {children}\n </DialogShell>\n )\n}\n", "import { useEffect, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconX } from '@tabler/icons-react'\nimport { cn } from './cn'\nimport { IconButton } from './IconButton'\n\n/**\n * Peek's DialogShell (2026-08-28), verbatim: the portal, the backdrop, the\n * 502px card and its chrome \u2014 a 48px header with the title and a close\n * button, a body, a 48px footer for the buttons. A dialog is just what goes\n * in the three slots. Plus what Ship added: Escape closes it, and the card\n * says what it is to assistive tech.\n */\nexport interface DialogShellProps {\n /** Labels the dialog for assistive tech, and renders as the header text\n * unless `headerContent` replaces it. */\n title: string\n onClose: () => void\n /** Replaces the title text in the header \u2014 a back button beside the title,\n * a count chip after it. The close button stays. */\n headerContent?: ReactNode\n /** Absent: no footer row, and the body keeps the card's own bottom edge\n * (a roster that simply ends). */\n footer?: ReactNode\n children: ReactNode\n /** Extra classes on the body (e.g. `flex flex-col gap-6`, or a max height with `overflow-y-auto`). */\n bodyClassName?: string\n width?: number\n}\n\nexport function DialogShell({ title, onClose, headerContent, footer, children, bodyClassName, width = 502 }: DialogShellProps) {\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onClose()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [onClose])\n\n return createPortal(\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 z-40 bg-black/50\" onClick={onClose} />\n\n {/* Dialog */}\n <div className=\"fixed inset-0 z-50 flex items-center justify-center pointer-events-none\">\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n className=\"bg-bg-elevated border border-border-subtle rounded-lg shadow-lg pointer-events-auto flex flex-col overflow-hidden\"\n style={{ width }}\n >\n {/* Header */}\n <div className=\"h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0\">\n {headerContent ?? <span className=\"text-h4 text-text-primary\">{title}</span>}\n <IconButton tooltip=\"Close\" aria-label=\"Close\" onClick={onClose}>\n <IconX size={16} stroke={1.5} />\n </IconButton>\n </div>\n\n {/* Body */}\n <div className={cn('pl-5 pr-4 py-4', footer != null && 'border-b border-border-subtle', bodyClassName)}>{children}</div>\n\n {/* Footer */}\n {footer != null && <div className=\"h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0\">{footer}</div>}\n </div>\n </div>\n </>,\n document.body,\n )\n}\n", "import { useEffect, useRef, useState, type KeyboardEvent, type RefObject } from 'react'\nimport { cn } from './cn'\n\n/**\n * Text you click to edit \u2014 Ship's EditableText (2026-09-01), verbatim. Its\n * own comment always called it a package candidate: nothing here knows what\n * is being edited.\n *\n * Reads as text until clicked; then it is a field. Enter commits (Shift+Enter\n * is a new line when multiline), Escape cancels, blur commits. A commit that\n * fails keeps the field open with the text in it, so an edit is never\n * silently lost \u2014 the caller has already said why. An unchanged value is not\n * committed at all. Empty shows the placeholder, muted.\n */\nexport interface EditableTextProps {\n value: string\n placeholder: string\n /** Resolve `true` when saved; `false` (or throw) keeps the field open with the text. */\n onCommit: (value: string) => Promise<boolean> | boolean\n multiline?: boolean\n /** Applied to both the text and the field, so they are the same size. */\n className?: string\n /** The accessible name of the field \u2014 \"Title\", \"Description\". */\n label: string\n /**\n * What to *show* when not editing, when that differs from what is edited \u2014\n * a value carrying a raw reference that the caller renders as a live object\n * below the text, say.\n *\n * It overrides the display branch only. `value` is still what the editor\n * opens with and what a commit compares against, so nothing is silently lost\n * \u2014 which is the failure a naive `value={stripped}` would cause.\n */\n display?: string\n /** Show the value only \u2014 no edit affordance. For a reader who cannot write. */\n readOnly?: boolean\n}\n\nexport function EditableText({ value, display, placeholder, onCommit, multiline = false, className, label, readOnly = false }: EditableTextProps) {\n const [editing, setEditing] = useState(false)\n const [draft, setDraft] = useState(value)\n const [busy, setBusy] = useState(false)\n const fieldRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null)\n\n useEffect(() => {\n if (editing) {\n fieldRef.current?.focus()\n fieldRef.current?.select()\n }\n }, [editing])\n\n const open = () => {\n setDraft(value)\n setEditing(true)\n }\n\n const cancel = () => {\n setEditing(false)\n setDraft(value)\n }\n\n const commit = async () => {\n if (busy) return\n const next = draft.trim()\n if (next === value) {\n setEditing(false)\n return\n }\n setBusy(true)\n try {\n const ok = await onCommit(next)\n if (ok) setEditing(false)\n } catch {\n /* the caller has shown why; keep the field open */\n } finally {\n setBusy(false)\n }\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n cancel()\n } else if (event.key === 'Enter' && !(multiline && event.shiftKey)) {\n event.preventDefault()\n void commit()\n }\n }\n\n const fieldClass = cn(\n 'w-full rounded-md border border-border-strong bg-bg-inset px-2 py-1 text-text-primary outline-none',\n multiline && 'resize-none',\n className,\n )\n\n if (readOnly) {\n return (\n <div\n aria-label={label}\n className={cn('w-full px-2 py-1', multiline && 'whitespace-pre-wrap', (display ?? value) ? 'text-text-primary' : 'text-text-muted', className)}\n >\n {(display ?? value) || placeholder}\n </div>\n )\n }\n\n if (editing) {\n return multiline ? (\n <textarea\n ref={fieldRef as RefObject<HTMLTextAreaElement>}\n aria-label={label}\n value={draft}\n rows={4}\n disabled={busy}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n onBlur={() => void commit()}\n className={fieldClass}\n />\n ) : (\n <input\n ref={fieldRef as RefObject<HTMLInputElement>}\n aria-label={label}\n value={draft}\n disabled={busy}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n onBlur={() => void commit()}\n className={fieldClass}\n />\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={open}\n title=\"Click to edit\"\n aria-label={`Edit ${label.toLowerCase()}`}\n className={cn(\n 'w-full rounded-md border border-transparent px-2 py-1 text-left transition-colors hover:border-border-default',\n multiline && 'whitespace-pre-wrap',\n (display ?? value) ? 'text-text-primary' : 'text-text-muted',\n className,\n )}\n >\n {(display ?? value) || placeholder}\n </button>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { IconMessage2 } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * Peek's EmptyState (2026-08-28), verbatim: a 16px icon over a centred line\n * of secondary text. The message is the caller's \u2014 a shared component has no\n * words of its own for what is missing.\n */\nexport interface EmptyStateProps {\n /** 16px, stroke 1.5. A speech bubble when absent. */\n icon?: ReactNode\n message: string\n className?: string\n}\n\nexport function EmptyState({ icon, message, className }: EmptyStateProps) {\n return (\n <div className={cn('flex flex-col gap-2 items-center', className)}>\n <span className=\"text-text-secondary\">{icon ?? <IconMessage2 size={16} stroke={1.5} />}</span>\n <p className=\"text-body-2 text-text-secondary text-center\">{message}</p>\n </div>\n )\n}\n", "import type { CSSProperties } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's Skeleton (2026-08-28), the generic parts only: the bar, a row\n * shaped like a list row, and a list of them. Peek's placeholders shaped\n * like a conversation card or a huddle card know what those are and stay\n * in Peek, built from the bar.\n *\n * A list reveals after 150ms (`animate-skeleton-in`, in the preset) so a\n * fast load never flashes a skeleton \u2014 Peek's rule.\n */\nexport function SkeletonBar({ className, style }: { className?: string; style?: CSSProperties }) {\n return <div className={cn('bg-bg-inset rounded-sm animate-pulse', className)} style={style} />\n}\n\nconst ROW_BAR_WIDTHS = [150, 100, 170, 120, 90, 140, 110, 160]\n\n/** A 32px row: a 16px square and a bar, like a row with a face and a name. */\nexport function SkeletonRow({ barWidth = 130 }: { barWidth?: number }) {\n return (\n <div className=\"flex items-center gap-2 px-2 h-[32px] rounded-lg\">\n <SkeletonBar className=\"w-4 h-4 shrink-0\" />\n <SkeletonBar className=\"h-3.5\" style={{ width: barWidth }} />\n </div>\n )\n}\n\n/** Rows of varied widths, so the placeholder does not read as a pattern. */\nexport function SkeletonList({ rows = 8, className }: { rows?: number; className?: string }) {\n return (\n <div className={cn('flex flex-col gap-0.5 animate-skeleton-in', className)} aria-hidden>\n {Array.from({ length: rows }, (_, i) => (\n <SkeletonRow key={i} barWidth={ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length]} />\n ))}\n </div>\n )\n}\n", "import { Fragment, useCallback, useLayoutEffect, useRef, useState } from 'react'\nimport { cn } from './cn'\nimport { WithTooltip } from './Tooltip'\n\n/**\n * A trail: \"Documents / Quarterly plan / DOC-12\". Ship's Breadcrumb\n * (2026-09-01), which knows nothing about what the places are.\n *\n * An item with an `href` is a link, even when it is last \u2014 a page may end its\n * trail on somewhere to go. The last item is where you are when it has none,\n * and may be `mono` (a ref, an id) or plain. The separator is a slash, muted,\n * never read aloud.\n *\n * A crumb that truncates shows its whole label in a tooltip on hover\n * (Katerina, 2026-09-01); one that fits shows nothing extra. Truncation is\n * re-measured when the trail resizes, so the tooltip appears and disappears\n * with the room the trail actually has.\n */\nexport interface Crumb {\n label: string\n href?: string\n /** Set the item in the mono face \u2014 a ref, an id. */\n mono?: boolean\n /** Quieter \u2014 a label that is not a place. */\n muted?: boolean\n}\n\nexport interface BreadcrumbProps {\n items: Crumb[]\n className?: string\n}\n\nexport function Breadcrumb({ items, className }: BreadcrumbProps) {\n const navRef = useRef<HTMLElement>(null)\n const labelRefs = useRef<(HTMLElement | null)[]>([])\n const [truncated, setTruncated] = useState<ReadonlySet<number>>(new Set())\n\n const measure = useCallback(() => {\n const next = new Set<number>()\n labelRefs.current.forEach((el, index) => {\n if (el && el.scrollWidth > el.clientWidth) next.add(index)\n })\n setTruncated((prev) => (prev.size === next.size && [...next].every((i) => prev.has(i)) ? prev : next))\n }, [])\n\n useLayoutEffect(() => {\n measure()\n const nav = navRef.current\n // jsdom has neither layout nor ResizeObserver; without this guard a\n // consumer app cannot render a page with a trail in its tests.\n if (!nav || typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(measure)\n observer.observe(nav)\n return () => observer.disconnect()\n }, [measure, items])\n\n return (\n <nav ref={navRef} aria-label=\"Breadcrumb\" className={cn('flex min-w-0 items-center gap-1.5 text-[14px] leading-[140%]', className)}>\n {items.map((item, index) => {\n const last = index === items.length - 1\n // The mono size is an arbitrary value (the caption token) because this\n // string goes through cn() and a token size before a colour class is\n // dropped (the tailwind-merge pitfall).\n const text = cn(\n 'truncate',\n // A mono crumb is a ref \u2014 the identity. It never gives up width to a\n // long name beside it (the LongName story always claimed \"the ref\n // stays\"; flexbox was squeezing it anyway until this line).\n item.mono && 'shrink-0 font-mono text-[12px] leading-[120%]',\n item.muted || (last && item.mono) ? 'text-text-muted' : last ? 'text-text-primary' : 'text-text-secondary',\n )\n const setLabelRef = (el: HTMLElement | null) => {\n labelRefs.current[index] = el\n }\n const crumb = item.href ? (\n <a ref={setLabelRef} href={item.href} className={cn(text, 'hover:text-text-primary')}>\n {item.label}\n </a>\n ) : (\n <span ref={setLabelRef} className={text} aria-current={last ? 'page' : undefined}>\n {item.label}\n </span>\n )\n return (\n <Fragment key={`${item.label}-${index}`}>\n {index > 0 && (\n <span aria-hidden=\"true\" className=\"shrink-0 text-text-muted\">\n /\n </span>\n )}\n {truncated.has(index) ? (\n // `min-w-0 shrink` undoes the wrapper's own shrink-0 \u2014 the crumb\n // must keep truncating inside it, or wrapping it would widen the\n // trail and the tooltip would never be needed again.\n <WithTooltip label={item.label} wrapperClassName=\"min-w-0 shrink\">\n {crumb}\n </WithTooltip>\n ) : (\n crumb\n )}\n </Fragment>\n )\n })}\n </nav>\n )\n}\n", "import type { ComponentPropsWithoutRef, ReactNode } from 'react'\nimport { cn } from './cn'\nimport { WithTooltip } from './Tooltip'\n\n/**\n * One row of a sidebar: a label, an optional count, an active state\n * (Ship's NavItem, 2026-09-02, verbatim).\n *\n * A count is a number of ONE thing, and its tooltip says which (\"18\n * open\", \"5 active\") \u2014 the caller passes the number and the words for it,\n * and a zero is not drawn at all. That is this row's rule; a *tab's*\n * count draws its zero (see Tabs) \u2014 both rulings stand, deliberately.\n *\n * Active is the same fill as a selected tab \u2014 `bg-active`, neutral.\n */\nexport interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'> {\n label: string\n href: string\n /** `0` or absent renders no counter. */\n count?: number\n /** What the count counts, for the tooltip \u2014 \"18 open\", \"5 active\". */\n countLabel?: string\n active?: boolean\n /** 16px, stroke 1.5. */\n icon?: ReactNode\n className?: string\n}\n\nexport function NavItem({ label, href, count, countLabel, active = false, icon, className, ...props }: NavItemProps) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={cn(\n // shrink-0: the row keeps its 32px inside an overflowing flex column\n // \u2014 without it the list compresses instead of scrolling (the missing-\n // shrink-0 family; Katerina, 2026-09-02).\n 'flex h-8 min-w-0 shrink-0 items-center gap-2 rounded-md px-2 text-body-2 transition-colors',\n active ? 'bg-bg-active text-text-primary' : 'text-text-secondary hover:bg-bg-hover hover:text-text-primary',\n className,\n )}\n {...props}\n >\n {icon && <span className=\"flex shrink-0 items-center\">{icon}</span>}\n <span className=\"flex-1 truncate\">{label}</span>\n {count ? (\n <WithTooltip label={countLabel ?? `${count} open`}>\n <span className=\"shrink-0 font-mono text-caption tabular-nums text-text-muted\">{count}</span>\n </WithTooltip>\n ) : null}\n </a>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * The icon rail: a 64px strip of RailItems (the shell of Peek's NavRail,\n * 2026-09-02 \u2014 its entries and their routes stayed in the app).\n *\n * It has no border and no surface of its own \u2014 it stands on the app\n * background, beside whatever card or column the app draws. Collapsing\n * is the caller's: the rail does not know about the burger, it only gets\n * given less room (or none) to stand in. Desktop only.\n */\nexport interface RailProps {\n /** Names the navigation region for assistive tech. */\n 'aria-label'?: string\n children: ReactNode\n className?: string\n}\n\nexport function Rail({ 'aria-label': ariaLabel = 'Navigation', children, className }: RailProps) {\n return (\n <nav aria-label={ariaLabel} className={cn('flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3', className)}>\n {children}\n </nav>\n )\n}\n", "import type { ComponentPropsWithoutRef, ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * One tile of an icon rail: the icon in its hover tile, a 9px label\n * under it (Peek's NavItem, 2026-09-02, verbatim \u2014 minus the router).\n *\n * The original computed its own active state from the route and rendered\n * a router link; neither belongs in a shared component. This one takes\n * `active` and renders an anchor \u2014 a router app keeps a thin wrapper\n * that does its own matching and intercepts the click.\n *\n * Active is `bg-selected` on the tile \u2014 some themes also give the icon\n * their interactive colour.\n */\nexport interface RailItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'> {\n href: string\n /** 16px, stroke 1.5. */\n icon: ReactNode\n label: string\n active?: boolean\n className?: string\n}\n\nexport function RailItem({ href, icon, label, active = false, className, ...props }: RailItemProps) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className={cn('group flex w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5', className)}\n {...props}\n >\n <div\n className={cn(\n 'flex items-center justify-center rounded-lg p-2 transition-colors',\n active ? 'bg-bg-selected' : 'group-hover:bg-bg-hover',\n )}\n >\n <span\n className={cn(\n 'flex items-center transition-colors',\n active ? 'text-text-primary signal:text-[color:var(--text-interactive)]' : 'text-text-secondary',\n )}\n >\n {icon}\n </span>\n </div>\n <span className={cn('text-center text-[9px] font-medium leading-[115%]', active ? 'text-text-primary' : 'text-text-secondary')}>\n {label}\n </span>\n </a>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * The navigation column: 240px, a hairline on its right, the surface\n * behind it, scrolling independently of the content beside it (the shell\n * of Ship's Sidebar, 2026-09-02 \u2014 its contents stayed in the app).\n *\n * What goes inside is the caller's: NavItem rows, a SectionLabel heading\n * in a 32px row over a group. Desktop only \u2014 there is no narrow-screen\n * drawer.\n */\nexport interface SidebarProps {\n /** Names the navigation region for assistive tech. */\n 'aria-label'?: string\n children: ReactNode\n className?: string\n}\n\nexport function Sidebar({ 'aria-label': ariaLabel = 'Workspace', children, className }: SidebarProps) {\n return (\n <nav\n aria-label={ariaLabel}\n className={cn(\n 'flex w-60 shrink-0 flex-col gap-px overflow-y-auto border-r border-border-default bg-bg-surface px-2.5 py-3',\n className,\n )}\n >\n {children}\n </nav>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * A labelled property row \u2014 \"Status\", \"Lead\", and whatever else a page\n * declares (2026-09-01). Both apps carried one, and they had already\n * drifted: Peek's label was `text-xs` \u2014 a Tailwind default with the wrong\n * line-height, not the caption token \u2014 and Ship's had grown a layout Peek's\n * lacked. One component, both layouts, the token:\n *\n * - `row` \u2014 Peek's: a 68px label column that lines the values up into a\n * column of their own; `text-secondary` rather than muted because a\n * property label is content, not a placeholder.\n * - `stacked` \u2014 Ship's rail: the label above a full-width control. The label\n * is the `menu` token (9px / 115% / 500), uppercase and letter-spaced \u2014\n * its own voice, deliberately not SectionLabel's (unmerged, Katerina\n * 2026-09-01). A literal string, not merged, so the token size survives.\n */\nexport interface PropertyProps {\n label: string\n layout?: 'row' | 'stacked'\n children: ReactNode\n className?: string\n}\n\nexport function Property({ label, layout = 'row', children, className }: PropertyProps) {\n if (layout === 'stacked') {\n return (\n // gap-3 \u2014 the one label-above-content distance (Katerina, 2026-09-01):\n // Peek's topic-details sections already sit at 12px, Ship's rail was\n // 6px, and unifying means the bigger, calmer one.\n <div className={cn('flex flex-col gap-3', className)}>\n <span className=\"text-menu uppercase tracking-[0.08em] text-text-secondary\">{label}</span>\n {children}\n </div>\n )\n }\n return (\n <div className={cn('flex items-center gap-2', className)}>\n <span className=\"w-[68px] shrink-0 text-caption text-text-secondary\">{label}</span>\n {children}\n </div>\n )\n}\n", "import { type InputHTMLAttributes } from 'react'\nimport { cn } from './cn'\n\n/**\n * Peek's SearchInput (2026-09-01), verbatim: an inset field with a hairline\n * border that strengthens on focus, and an optional keyboard hint at the\n * right edge \u2014 under Signal, the hint becomes the mono `kbd` chip with the\n * thicker bottom edge.\n *\n * The one product string \u2014 Peek's default placeholder \"Search Peek...\" \u2014\n * stays in Peek; the default here says only \"Search\u2026\".\n *\n * In Peek's top bar this is a launcher affordance rather than a live field:\n * the input is `pointer-events-none` and clicking the surround opens the\n * command launcher. The component is the same either way.\n */\nexport interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {\n /** A keyboard hint drawn at the right edge, e.g. \"\u2318 K\". */\n shortcut?: string\n className?: string\n}\n\nexport function SearchInput({ shortcut, className, placeholder = 'Search\u2026', ...props }: SearchInputProps) {\n return (\n <div\n className={cn(\n 'flex gap-2 items-center px-3 py-2 rounded-lg',\n 'bg-bg-inset border border-border-default',\n 'focus-within:border-border-strong transition-colors',\n className,\n )}\n >\n <input\n className=\"flex-1 min-w-0 bg-transparent text-input-value text-text-primary placeholder:text-text-muted outline-none\"\n placeholder={placeholder}\n {...props}\n />\n {shortcut && (\n <div className=\"flex items-center justify-center px-1 py-px rounded-sm bg-bg-inset border border-border-strong shrink-0 signal:bg-[rgba(255,255,255,.05)] signal:border-b-2\">\n <span className=\"text-caption text-text-secondary whitespace-nowrap signal:font-mono signal:text-[10px]\">{shortcut}</span>\n </div>\n )}\n </div>\n )\n}\n", "import { useState, type ReactNode } from 'react'\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { cn } from './cn'\nimport { IconButton } from './IconButton'\nimport { SectionLabel } from './SectionLabel'\n\n/**\n * The 32px row a section starts with (Peek's SectionHeader, 2026-09-01):\n * a SectionLabel, an optional collapse chevron that makes the whole row the\n * toggle, and actions that appear only while the row is hovered, as\n * IconButtons with tooltips.\n *\n * One API change from Peek's original, safe because no call site used the\n * old shape yet: the two hard-wired action slots (add, sort) became\n * `actions` \u2014 the caller brings the icon, the tooltip and the handler; the\n * header owns the hover reveal and stops the click from also toggling the\n * section. Peek's add/sort pair is the WithActions story, in its original\n * order.\n */\nexport interface SectionAction {\n /** 16px, stroke 1.5. */\n icon: ReactNode\n tooltip: string\n onClick: () => void\n}\n\nexport interface SectionHeaderProps {\n title: string\n /** Collapsible: draws the chevron and makes the whole row the toggle. */\n chevron?: boolean\n isExpanded?: boolean\n onToggle?: () => void\n /** Right-aligned, in the order given. */\n actions?: SectionAction[]\n /** `hover` reveals the actions only while the row is hovered; `always` keeps them. */\n showActions?: 'hover' | 'always'\n className?: string\n}\n\nexport function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, actions, showActions = 'hover', className }: SectionHeaderProps) {\n const [isHovered, setIsHovered] = useState(false)\n\n return (\n <div\n className={cn(\n 'flex h-[32px] items-center justify-between rounded-lg px-2 transition-colors',\n isHovered && 'bg-bg-hover',\n chevron && 'cursor-pointer',\n className,\n )}\n onClick={chevron ? onToggle : undefined}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <div className=\"flex shrink-0 items-center gap-1\">\n {chevron && (\n <IconChevronRight\n size={12}\n stroke={1.5}\n className={cn('text-text-secondary transition-transform duration-150', isExpanded && 'rotate-90')}\n />\n )}\n <SectionLabel>{title}</SectionLabel>\n </div>\n\n {(showActions === 'always' || isHovered) && actions && actions.length > 0 && (\n <div className=\"flex items-center gap-1\">\n {actions.map((action) => (\n <IconButton\n key={action.tooltip}\n tooltip={action.tooltip}\n aria-label={action.tooltip}\n onClick={(event) => {\n event.stopPropagation()\n action.onClick()\n }}\n >\n {action.icon}\n </IconButton>\n ))}\n </div>\n )}\n </div>\n )\n}\n", "import type { ReactNode } from 'react'\nimport { cn } from './cn'\n\n/**\n * A row of tabs. Ship's Tabs (2026-09-01), which was Peek's TopicTabs with\n * the topic-specific ids taken out.\n *\n * A selected tab is a neutral fill (bg-active), not the accent tint \u2014\n * Katerina's ruling (2026-08-27), extended to every app (2026-09-01). Two\n * sizes: `default` is 14px, a little more room; `small` is Peek's own\n * geometry (its inline `fontSize: 12` was the caption token spelled by hand).\n *\n * A tab's count is the sidebar's number, not a chip (Katerina, 2026-09-01,\n * superseding the chip ruling of 2026-08-26): mono, caption size, muted,\n * tabular \u2014 the one way a number sits beside a label everywhere. A count of\n * `0` is still drawn (\"Assigned to me 0\" is an answer); an absent count draws\n * nothing.\n */\nexport interface TabDef<T extends string> {\n id: T\n label: string\n /** Shown as a muted mono number after the label. Absent draws nothing; 0 is drawn. */\n count?: number\n /** 16px, stroke 1.5. */\n icon?: ReactNode\n}\n\nexport interface TabsProps<T extends string> {\n tabs: TabDef<T>[]\n active: T\n onChange: (id: T) => void\n /** `default` 14px; `small` 12px, the denser geometry. */\n size?: 'default' | 'small'\n className?: string\n}\n\nexport function Tabs<T extends string>({ tabs, active, onChange, size = 'default', className }: TabsProps<T>) {\n return (\n <div role=\"tablist\" className={cn('flex items-center gap-2', className)}>\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={active === tab.id}\n onClick={() => onChange(tab.id)}\n className={cn(\n 'flex cursor-pointer items-center transition-colors',\n // Arbitrary sizes (the body-2 and caption tokens): the colour branch below\n // follows them through cn(), and tw-merge drops a custom text-{size}\n // once a text-{colour} lands after it. Measured: tabs rendered 16px.\n // gap: default is Ship's 6px; small keeps Peek's original 4px, or\n // \"small is Peek's geometry\" stops being true.\n size === 'default' ? 'gap-1.5 rounded-md px-2 py-1 text-[14px] leading-[140%]' : 'gap-1 rounded px-1.5 py-0.5 text-[12px] leading-[120%]',\n active === tab.id ? 'bg-bg-active text-text-primary' : 'text-text-secondary hover:bg-bg-hover',\n )}\n >\n {tab.icon}\n {/* Label and count share a baseline: a smaller text centred as a box\n (items-center) floats above the label's baseline \u2014 the digits\n read as riding high. Baseline alignment is what makes two sizes\n sit on one line. */}\n <span className=\"flex items-baseline\">\n {tab.label}\n {tab.count !== undefined ? (\n <span className=\"ml-2.5 font-mono text-caption tabular-nums text-text-secondary\">{tab.count}</span>\n ) : null}\n </span>\n </button>\n ))}\n </div>\n )\n}\n", "import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconCircleCheck } from '@tabler/icons-react'\nimport { cn } from './cn'\n\n/**\n * Peek's Toast (Figma: Alert) and its provider (2026-09-01), verbatim.\n *\n * The pill: three types \u2014 `success`, `brand`, `neutral` \u2014 an optional leading\n * circle-check, an optional action on the right. Under Signal every toast is\n * the same dark overlay pill; the type lives in the icon's colour and glow,\n * not the surface.\n *\n * The provider owns positioning (bottom-left), the portal, and auto-dismiss\n * (5 s; pass `durationMs: 0` to keep one up). One toast at a time \u2014 a new\n * one replaces the standing one, it does not queue.\n */\nexport type ToastType = 'success' | 'brand' | 'neutral'\n\nexport interface ToastProps {\n label: string\n /** Visual variant per Figma (Alert component): success, brand, or neutral. */\n type?: ToastType\n /** Show the leading circle-check icon. Defaults to true. */\n leadingIcon?: boolean\n /** When set together with onAction, renders a clickable action on the right side. */\n actionLabel?: string\n onAction?: () => void\n className?: string\n}\n\n// Signal: every toast is the same dark overlay pill (v3) \u2014 the type lives in\n// the icon color + glow, not the surface.\nconst SURFACE_BY_TYPE: Record<ToastType, string> = {\n success: 'bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]',\n brand: 'bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]',\n neutral: 'bg-bg-inset border border-border-subtle signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]',\n}\n\nconst ICON_BY_TYPE: Record<ToastType, string> = {\n success: 'signal:text-success-default signal:drop-shadow-[0_0_5px_rgba(63,222,140,0.7)]',\n brand: 'signal:text-text-interactive signal:drop-shadow-[0_0_5px_rgba(86,200,255,0.6)]',\n neutral: 'signal:text-text-secondary',\n}\n\nconst ACTION_BORDER_BY_TYPE: Record<ToastType, string> = {\n success: 'signal:border signal:border-border-default signal:hover:border-border-strong',\n brand: 'signal:border signal:border-border-default signal:hover:border-border-strong',\n neutral: 'border border-border-default',\n}\n\nexport function Toast({ label, type = 'neutral', leadingIcon = true, actionLabel, onAction, className }: ToastProps) {\n const hasAction = !!(actionLabel && onAction)\n return (\n <div\n className={cn(\n 'inline-flex items-center min-h-[32px] pl-2 py-1 rounded-lg shadow-lg',\n // Without an action the label needs real right padding; the action\n // button brings its own edge, so the tight pr-1 only applies there.\n hasAction ? 'pr-1 gap-[46px]' : 'pr-3',\n SURFACE_BY_TYPE[type],\n className,\n )}\n >\n <div className=\"flex items-center gap-2 shrink-0\">\n {leadingIcon && (\n <IconCircleCheck size={16} stroke={1.5} className={cn('text-text-primary shrink-0', ICON_BY_TYPE[type])} />\n )}\n <span className=\"font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap\">{label}</span>\n </div>\n {hasAction && (\n <button\n type=\"button\"\n onClick={onAction}\n className={cn(\n 'h-6 flex items-center justify-center gap-1 px-1 py-1 rounded-md shrink-0 transition-colors',\n ACTION_BORDER_BY_TYPE[type],\n type === 'neutral' ? 'hover:border-border-strong' : 'hover:opacity-80',\n )}\n >\n <span className=\"font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap\">{actionLabel}</span>\n </button>\n )}\n </div>\n )\n}\n\nexport interface ToastOptions {\n label: string\n /** Visual variant per Figma (Alert component). Defaults to 'neutral'. */\n type?: ToastType\n /** When set, renders a clickable action on the right side. */\n actionLabel?: string\n onAction?: () => void\n /** Show the leading circle-check icon. Defaults to true. */\n leadingIcon?: boolean\n /** Auto-dismiss after this many ms. Defaults to 5000. Pass 0 to disable. */\n durationMs?: number\n}\n\ninterface ActiveToast extends ToastOptions {\n id: number\n}\n\ninterface ToastValue {\n showToast: (opts: ToastOptions) => void\n dismissToast: () => void\n}\n\nconst ToastContext = createContext<ToastValue | null>(null)\n\nlet toastSeq = 0\n\nexport function ToastProvider({ children }: { children: ReactNode }) {\n const [toast, setToast] = useState<ActiveToast | null>(null)\n\n const showToast = useCallback((opts: ToastOptions) => {\n setToast({ ...opts, id: ++toastSeq })\n }, [])\n\n const dismissToast = useCallback(() => {\n setToast(null)\n }, [])\n\n useEffect(() => {\n if (!toast) return\n const duration = toast.durationMs ?? 5000\n if (duration <= 0) return\n const timer = setTimeout(() => {\n setToast((current) => (current?.id === toast.id ? null : current))\n }, duration)\n return () => clearTimeout(timer)\n }, [toast])\n\n const value = useMemo<ToastValue>(() => ({ showToast, dismissToast }), [showToast, dismissToast])\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n {toast &&\n createPortal(\n <div className=\"fixed bottom-4 left-4 z-[100] pointer-events-none\">\n <Toast\n className=\"pointer-events-auto\"\n label={toast.label}\n type={toast.type ?? 'neutral'}\n leadingIcon={toast.leadingIcon ?? true}\n actionLabel={toast.actionLabel}\n onAction={\n toast.onAction\n ? () => {\n toast.onAction?.()\n dismissToast()\n }\n : undefined\n }\n />\n </div>,\n document.body,\n )}\n </ToastContext.Provider>\n )\n}\n\nexport function useToast(): ToastValue {\n const ctx = useContext(ToastContext)\n if (!ctx) throw new Error('useToast must be used within ToastProvider')\n return ctx\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "jsx", "jsx", "jsx", "jsxs", "jsx", "jsx", "jsxs", "useState", "useRef", "useEffect", "createPortal", "jsx", "jsx", "jsxs", "jsx", "jsxs", "useState", "useRef", "useEffect", "createPortal", "useLayoutEffect", "useRef", "useState", "createPortal", "jsx", "jsxs", "useState", "useRef", "useLayoutEffect", "createPortal", "jsx", "useState", "jsx", "jsx", "jsxs", "jsx", "jsxs", "jsx", "jsxs", "useState", "jsx", "jsxs", "IconCheck", "IconChevronDown", "useCallback", "useEffect", "useLayoutEffect", "useMemo", "useRef", "useState", "createPortal", "Fragment", "jsx", "jsxs", "GAP", "useRef", "useState", "useMemo", "useCallback", "useLayoutEffect", "useEffect", "IconChevronDown", "createPortal", "IconCheck", "jsx", "TextInput", "forwardRef", "jsx", "forwardRef", "Textarea", "useState", "useEffect", "createPortal", "IconX", "Fragment", "jsx", "jsxs", "useEffect", "createPortal", "IconX", "Fragment", "jsx", "jsxs", "useState", "useEffect", "useRef", "useState", "jsx", "useState", "useRef", "useEffect", "jsx", "jsxs", "jsx", "jsxs", "Fragment", "useCallback", "useLayoutEffect", "useRef", "useState", "jsx", "jsxs", "useRef", "useState", "useCallback", "useLayoutEffect", "Fragment", "jsx", "jsxs", "jsx", "jsx", "jsxs", "jsx", "jsx", "jsxs", "jsx", "jsxs", "useState", "IconChevronRight", "jsx", "jsxs", "useState", "IconChevronRight", "jsx", "jsxs", "useCallback", "useEffect", "useMemo", "useState", "createPortal", "jsx", "jsxs", "useState", "useCallback", "useEffect", "useMemo", "createPortal"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,6 +9,7 @@
9
9
  "src",
10
10
  "tailwind-preset.js",
11
11
  "tokens.css",
12
+ "base.css",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
@@ -19,7 +20,8 @@
19
20
  },
20
21
  "./tailwind-preset": "./tailwind-preset.js",
21
22
  "./tokens.css": "./tokens.css",
22
- "./package.json": "./package.json"
23
+ "./package.json": "./package.json",
24
+ "./base.css": "./base.css"
23
25
  },
24
26
  "scripts": {
25
27
  "build": "node build.mjs && tsc -p tsconfig.build.json",
@@ -49,6 +51,7 @@
49
51
  "postcss": "^8.5.8",
50
52
  "react": "^19.2.4",
51
53
  "react-dom": "^19.2.4",
54
+ "remark-gfm": "^4.0.1",
52
55
  "storybook": "^10.5.10",
53
56
  "tailwindcss": "^3.4.19",
54
57
  "typescript": "~5.9.3",
@@ -0,0 +1,66 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks'
2
+ import * as AppShellStories from './AppShell.stories'
3
+
4
+ <Meta of={AppShellStories} />
5
+
6
+ # AppShell
7
+
8
+ The frame of an app, in the two manners that pair: **`solid`** — the
9
+ structured frame, a solid TopBar over a Sidebar and the content beside
10
+ it — and **`floating`** — the TopBar floating over the top edge, a Rail
11
+ standing on the background, and the content living in the rounded card.
12
+ Layout only: no data, no routes.
13
+
14
+ <Canvas of={AppShellStories.Solid} />
15
+
16
+ <Canvas of={AppShellStories.Floating} />
17
+
18
+ ## When
19
+
20
+ - **`solid` + Sidebar** — a place-by-place app: labelled entries, counts,
21
+ groups. Sidebar and main scroll independently, so a long list never
22
+ scrolls the navigation away.
23
+ - **`floating` + Rail** — an app with a handful of top-level places and
24
+ its content in one card; the `menu` button in the bar collapses the
25
+ rail (the collapsing stays the app's).
26
+ - The **banner belongs to the content area**: it draws at the top of the
27
+ main column — inside the card, in the floating manner — never across
28
+ the navigation.
29
+
30
+ <Canvas of={AppShellStories.FloatingWithBanner} />
31
+
32
+ ## When not
33
+
34
+ - Panels *inside* the content — lists beside conversations, side rails —
35
+ are layout components, not the shell's business.
36
+ - Mixing the pairs (a rail in the solid frame, a sidebar in the floating
37
+ one) is possible but is a new design, not a port — take it to the
38
+ designer first.
39
+
40
+ ## How
41
+
42
+ ```tsx
43
+ import { AppShell, Banner, IdentityMenu, Rail, RailItem } from '@estiva-app/ui'
44
+
45
+ <AppShell
46
+ variant="floating"
47
+ menu={menuButton}
48
+ logo="Estiva"
49
+ identity={<IdentityMenu me={me} signedIn={signedIn} />}
50
+ banner={notice && <Banner tone={notice.tone}>{notice.text}</Banner>}
51
+ nav={<Rail>…</Rail>}
52
+ >
53
+ {page}
54
+ </AppShell>
55
+ ```
56
+
57
+ - Give it a height: it fills `h-full` and manages `min-h-0` internally so
58
+ navigation and content scroll apart.
59
+ - `menu`, `logo` and `search` pass through to the TopBar's slots.
60
+ - In the floating manner the card owns `overflow-hidden` — your content
61
+ manages its own scrolling inside it.
62
+ - Desktop only for now — no narrow-screen behaviour is provided.
63
+
64
+ ## Props
65
+
66
+ <Controls of={AppShellStories.Solid} />