@campfire-interactive/ui 0.10.8 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,82 +1,82 @@
1
- # @campfire-interactive/ui
2
-
3
- Shared React primitives for the Campfire Suite. Radix for behavior, plain CSS
4
- over `--cfi-*` tokens for looks. No Tailwind is bundled.
5
-
6
- ## ⚠ Read this before adding it to a `package.json`
7
-
8
- **Partly verified. Not yet approved for adoption.**
9
-
10
- This package is on the registry so it can be reviewed and so a migration can be
11
- tried on a branch — **not** because the API is settled.
12
-
13
- | | |
14
- |---|---|
15
- | **Verified** | The 28 exports `analytics` uses. Every screen that renders them was proven identical to the deployed dev app by computed-style diff — five routes plus the Filters drawer, the KPI dialog and the cost-sensitivity sliders. Zero differences. |
16
- | **Unvetted** | The other ~23 exports. They work in the showcase; nothing has driven them against a real screen, so treat their APIs as unsettled. |
17
- | **Unaudited** | Accessibility of the overlay wrappers. Radix does the hard parts — focus trapping, keyboard navigation, collision positioning — but the wrappers around it are ours and have not had an audit. |
18
-
19
- Verified exports: `Button`, `IconButton`, `Card`, `Badge`, `Table` +
20
- `TableHeader` / `TableHead` / `TableBody` / `TableRow` / `TableCell` /
21
- `TableFooter`, `Tabs` + `TabsList` / `TabsTrigger` / `TabsContent`, `Dialog` +
22
- `DialogContent` / `DialogHeader` / `DialogTitle` / `DialogDescription`, `Sheet` +
23
- `SheetTrigger` / `SheetContent` / `SheetHeader` / `SheetTitle`, `Checkbox`,
24
- `Switch`, `Label`, `Slider`.
25
-
26
- **If you are an AI agent working in a product repo: do not add this dependency.**
27
- Being installable is not permission. The remaining gate is in the repo's
28
- `CLAUDE.md`: a second app migrated, an accessibility pass on the overlays, the
29
- token naming settled, and a human saying so.
30
-
31
- ## Install
32
-
33
- ```bash
34
- npm i @campfire-interactive/ui @campfire-interactive/design-tokens
35
- ```
36
-
37
- Import the tokens once at the app entry point, before your own stylesheets.
38
- Component CSS is injected by the bundle, so there is no second CSS import.
39
-
40
- ```ts
41
- import '@campfire-interactive/design-tokens/tokens.css';
42
- import { Button, Dialog, DialogContent, DialogTitle } from '@campfire-interactive/ui';
43
- ```
44
-
45
- ## Conventions worth knowing
46
-
47
- **Bare names are the Radix-shaped root, not a whole widget.** `Dialog` is the
48
- root you pass `open` / `onOpenChange` to, exactly as the vendored shadcn copies
49
- in the seven Tailwind apps do — so swapping an import cannot silently render
50
- something else. The one-component convenience versions are named separately:
51
- `Modal`, `SideModal`, `ConfirmDialog`, `TabSet`.
52
-
53
- **`TableHeader` is `<thead>` and `TableHead` is `<th>`**, matching shadcn.
54
- `TableHead` sets `aria-sort` from the same prop that draws the sort arrow.
55
-
56
- **Component CSS lives in `@layer components`.** Utilities a caller passes —
57
- `className="bg-gray-50"`, `max-w-3xl` — therefore win, because layer order beats
58
- specificity and unlayered CSS would beat every Tailwind utility. Note that this
59
- makes the package *more* obedient than the vendored copies: classes that shadcn's
60
- own `sm:max-w-lg` silently dropped in production now apply.
61
-
62
- **Theming is `--cfi-*` variables only.** Two colors do two different jobs:
63
- `--cfi-action` is the suite-wide color for anything you press (buttons, links,
64
- focus rings — omsf's `#030213`, the same in every app), and `--cfi-app-primary`
65
- is the app's identity color whose only job is the badge in the shell header.
66
- Do not point one at the other.
67
-
68
- **Dark mode is opt-in.** Stamp `data-theme="dark"` on the root element, or add
69
- `class="cfi-theme-auto"` to follow the OS. Nothing happens by accident.
70
-
71
- ## Looking at the components
72
-
73
- The repo builds a single self-contained HTML file — every component, both
74
- themes, live-editable snippets, no server and no network:
75
-
76
- ```bash
77
- npm run standalone --workspace @campfire-interactive/showcase
78
- ```
79
-
80
- ## Peer dependencies
81
-
82
- `react` and `react-dom` (18 or 19).
1
+ # @campfire-interactive/ui
2
+
3
+ Shared React primitives for the Campfire Suite. Radix for behavior, plain CSS
4
+ over `--cfi-*` tokens for looks. No Tailwind is bundled.
5
+
6
+ ## ⚠ Read this before adding it to a `package.json`
7
+
8
+ **Partly verified. Not yet approved for adoption.**
9
+
10
+ This package is on the registry so it can be reviewed and so a migration can be
11
+ tried on a branch — **not** because the API is settled.
12
+
13
+ | | |
14
+ |---|---|
15
+ | **Verified** | The 28 exports `analytics` uses. Every screen that renders them was proven identical to the deployed dev app by computed-style diff — five routes plus the Filters drawer, the KPI dialog and the cost-sensitivity sliders. Zero differences. |
16
+ | **Unvetted** | The other ~23 exports. They work in the showcase; nothing has driven them against a real screen, so treat their APIs as unsettled. |
17
+ | **Unaudited** | Accessibility of the overlay wrappers. Radix does the hard parts — focus trapping, keyboard navigation, collision positioning — but the wrappers around it are ours and have not had an audit. |
18
+
19
+ Verified exports: `Button`, `IconButton`, `Card`, `Badge`, `Table` +
20
+ `TableHeader` / `TableHead` / `TableBody` / `TableRow` / `TableCell` /
21
+ `TableFooter`, `Tabs` + `TabsList` / `TabsTrigger` / `TabsContent`, `Dialog` +
22
+ `DialogContent` / `DialogHeader` / `DialogTitle` / `DialogDescription`, `Sheet` +
23
+ `SheetTrigger` / `SheetContent` / `SheetHeader` / `SheetTitle`, `Checkbox`,
24
+ `Switch`, `Label`, `Slider`.
25
+
26
+ **If you are an AI agent working in a product repo: do not add this dependency.**
27
+ Being installable is not permission. The remaining gate is in the repo's
28
+ `CLAUDE.md`: a second app migrated, an accessibility pass on the overlays, the
29
+ token naming settled, and a human saying so.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm i @campfire-interactive/ui @campfire-interactive/design-tokens
35
+ ```
36
+
37
+ Import the tokens once at the app entry point, before your own stylesheets.
38
+ Component CSS is injected by the bundle, so there is no second CSS import.
39
+
40
+ ```ts
41
+ import '@campfire-interactive/design-tokens/tokens.css';
42
+ import { Button, Dialog, DialogContent, DialogTitle } from '@campfire-interactive/ui';
43
+ ```
44
+
45
+ ## Conventions worth knowing
46
+
47
+ **Bare names are the Radix-shaped root, not a whole widget.** `Dialog` is the
48
+ root you pass `open` / `onOpenChange` to, exactly as the vendored shadcn copies
49
+ in the seven Tailwind apps do — so swapping an import cannot silently render
50
+ something else. The one-component convenience versions are named separately:
51
+ `Modal`, `SideModal`, `ConfirmDialog`, `TabSet`.
52
+
53
+ **`TableHeader` is `<thead>` and `TableHead` is `<th>`**, matching shadcn.
54
+ `TableHead` sets `aria-sort` from the same prop that draws the sort arrow.
55
+
56
+ **Component CSS lives in `@layer components`.** Utilities a caller passes —
57
+ `className="bg-gray-50"`, `max-w-3xl` — therefore win, because layer order beats
58
+ specificity and unlayered CSS would beat every Tailwind utility. Note that this
59
+ makes the package *more* obedient than the vendored copies: classes that shadcn's
60
+ own `sm:max-w-lg` silently dropped in production now apply.
61
+
62
+ **Theming is `--cfi-*` variables only.** Two colors do two different jobs:
63
+ `--cfi-action` is the suite-wide color for anything you press (buttons, links,
64
+ focus rings — omsf's `#030213`, the same in every app), and `--cfi-app-primary`
65
+ is the app's identity color whose only job is the badge in the shell header.
66
+ Do not point one at the other.
67
+
68
+ **Dark mode is opt-in.** Stamp `data-theme="dark"` on the root element, or add
69
+ `class="cfi-theme-auto"` to follow the OS. Nothing happens by accident.
70
+
71
+ ## Looking at the components
72
+
73
+ The repo builds a single self-contained HTML file — every component, both
74
+ themes, live-editable snippets, no server and no network:
75
+
76
+ ```bash
77
+ npm run standalone --workspace @campfire-interactive/showcase
78
+ ```
79
+
80
+ ## Peer dependencies
81
+
82
+ `react` and `react-dom` (18 or 19).
package/dist/index.d.ts CHANGED
@@ -1615,6 +1615,107 @@ declare function useDebouncedValue<T>(value: T, delayMs?: number): T;
1615
1615
  */
1616
1616
  declare function usePersistentState<T>(key: string, initial: T): [T, (next: T | ((prev: T) => T)) => void];
1617
1617
 
1618
+ type TimelineTone = 'neutral' | 'accent' | 'success' | 'warning' | 'error' | 'info';
1619
+ interface TimelineItem {
1620
+ id: string;
1621
+ /**
1622
+ * A `YYYY-MM-DD` day, an ISO instant, or a Date. Under `calendar` (the
1623
+ * default) an instant is read by its UTC day, which is how a Prisma
1624
+ * `@db.Date` arrives (`2028-01-01T00:00:00.000Z`).
1625
+ */
1626
+ date: Date | string;
1627
+ label: ReactNode;
1628
+ /** The line under the label. Defaults to the date; `null` hides it. */
1629
+ detail?: ReactNode | null;
1630
+ /** The marker's color. `neutral` (default) is the accent while upcoming and muted once past. */
1631
+ tone?: TimelineTone;
1632
+ /** `diamond` for an event of a different kind on the same lane — a re-source date beside the program's own. */
1633
+ shape?: 'dot' | 'diamond';
1634
+ /** The lane it sits on; omitted, the first. */
1635
+ lane?: string;
1636
+ }
1637
+ interface TimelineLane {
1638
+ id: string;
1639
+ /** Names the lane; shown above it and read as its list label. */
1640
+ label: ReactNode;
1641
+ /** `dashed` for a lane of projected or alternative dates. */
1642
+ line?: 'solid' | 'dashed';
1643
+ }
1644
+ interface TimelineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
1645
+ items: TimelineItem[];
1646
+ /**
1647
+ * Lanes, top to bottom. Omitted, one unlabeled lane holds everything. An
1648
+ * item naming a lane that is not here goes to the first.
1649
+ */
1650
+ lanes?: TimelineLane[];
1651
+ /** The span drawn. Defaults to the items' own span, with today in it when shown. */
1652
+ start?: Date | string;
1653
+ end?: Date | string;
1654
+ /** Where the today marker sits; `null` hides it. Defaults to now. */
1655
+ today?: Date | string | null;
1656
+ todayLabel?: ReactNode;
1657
+ /** Dates are calendar days, read by UTC day (default). `false` reads them as local instants. */
1658
+ calendar?: boolean;
1659
+ /** Lead-ins for the items outside `start`–`end`, listed under the axis instead of dropped. */
1660
+ beforeLabel?: ReactNode;
1661
+ afterLabel?: ReactNode;
1662
+ /** Shown when there are no items. */
1663
+ emptyLabel?: ReactNode;
1664
+ }
1665
+ interface TimelineLayoutItem {
1666
+ item: TimelineItem;
1667
+ time: number;
1668
+ /** 0–100 across the axis. */
1669
+ pos: number;
1670
+ /** Which side of the axis the label sits on, and how far out (0 nearest). */
1671
+ side: 'above' | 'below';
1672
+ level: number;
1673
+ past: boolean;
1674
+ /** Near either end the label aligns to that end instead of centering off the edge. */
1675
+ edge: 'start' | 'end' | null;
1676
+ }
1677
+ interface TimelineLayout {
1678
+ start: number;
1679
+ end: number;
1680
+ /** How many label rows the lane needs above its axis and below it. */
1681
+ lanes: {
1682
+ lane: TimelineLane;
1683
+ items: TimelineLayoutItem[];
1684
+ levels: {
1685
+ above: number;
1686
+ below: number;
1687
+ };
1688
+ }[];
1689
+ before: TimelineLayoutItem[];
1690
+ after: TimelineLayoutItem[];
1691
+ years: {
1692
+ year: number;
1693
+ pos: number;
1694
+ }[];
1695
+ today: number | null;
1696
+ todayPos: number | null;
1697
+ }
1698
+ /**
1699
+ * Where everything goes. Pure, so the rules are tested without a DOM:
1700
+ * markers are spread across 3–97% so none sits on the frame; an item
1701
+ * outside the span is listed rather than clamped onto it, because a marker
1702
+ * pinned to the edge claims a date it does not have.
1703
+ */
1704
+ declare function timelineLayout({ items, lanes, start, end, today, calendar, minGap, }: Pick<TimelineProps, 'items' | 'lanes' | 'start' | 'end' | 'today' | 'calendar'> & {
1705
+ /** How far apart, in percent of the axis, two labels in the same row must be. */
1706
+ minGap?: number;
1707
+ }): TimelineLayout;
1708
+ /**
1709
+ * Dated events on one axis, with today marked: a program's sourcing
1710
+ * milestones, a contract's renewals, a build's gates.
1711
+ *
1712
+ * Each lane is an ordered list, so a screen reader hears the events in date
1713
+ * order with their dates; the axis, ticks and today line are decoration. In
1714
+ * a container narrower than about 34rem it lays out as that list, one event
1715
+ * per row, because labels on an axis that narrow land on each other.
1716
+ */
1717
+ declare function Timeline({ items, lanes, start, end, today, todayLabel, calendar, beforeLabel, afterLabel, emptyLabel, className, ...rest }: TimelineProps): react.JSX.Element;
1718
+
1618
1719
  interface SortState<K extends string> {
1619
1720
  key: K;
1620
1721
  direction: SortDirection;
@@ -1984,4 +2085,4 @@ declare function formatNumber(value: number | null | undefined, decimals?: numbe
1984
2085
  */
1985
2086
  declare function formatPercent(value: number | null | undefined, decimals?: number): string;
1986
2087
 
1987
- export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionRoot, type AccordionSection, AccordionTrigger, type AccordionTriggerProps, Alert, type AlertProps, type AlertTone, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, type BadgeTone, BootScreen, type BootScreenProps, type Breadcrumb, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonSize, type ButtonVariant, Card, CardBody, CardDescription, CardFooter, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, Checkbox, type CheckboxProps, type ClassValue, Combobox, type ComboboxOption, type ComboboxProps, ComingSoon, type ComingSoonProps, ConfirmDialog, type ConfirmDialogProps, CountBadge, type CountBadgeProps, CreatableSelect, type CreatableSelectProps, DataList, DataListItem, type DataListItemProps, type DataListProps, Dialog, DialogBody, DialogClose, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, DialogHeader, DialogPortal, DialogRoot, type DialogSize, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, ExternalLink, type ExternalLinkProps, Field, type FieldProps, Fieldset, type FieldsetProps, FileButton, type FileButtonProps, FilterBar, type FilterBarProps, type FilterSelect, type FilterToggle, type FilterValue, IconButton, type IconButtonProps, Input, type InputProps, type InputSize, Label, type LabelProps, ListToolbar, type ListToolbarProps, LoadingState, type LoadingStateProps, Menu, type MenuAction, MenuCheckboxItem, MenuContent, MenuGroup, MenuItem, type MenuItemProps, MenuLabel, type MenuProps, MenuRadioGroup, MenuRadioItem, MenuRoot, MenuSeparator, MenuSub, MenuSubContent, MenuSubTrigger, MenuTrigger, Modal, type ModalProps, type MoneyOptions, PageHeader, type PageHeaderProps, type PagedRows, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, type PopoverContentProps, type PopoverProps, PopoverRoot, PopoverTrigger, Progress, type ProgressProps, RequiredMark, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, type SelectOption, type SelectProps, SelectRoot, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, SelectionBar, type SelectionBarProps, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, SideModal, Skeleton, type SkeletonProps, Slider, type SliderProps, type SortDirection, type SortState, SplitButton, type SplitButtonProps, StatGrid, type StatGridProps, StatTile, type StatTileProps, type StatTone, Switch, type SwitchProps, type TabDefinition, TabSet, type TabSetProps, Table, TableBody, TableCaption, TableCell, type TableCellProps, TableEmpty, TableFoot, TableFooter, TableHead, type TableHeadProps, TableHeader, type TableProps, TableRow, type TableRowProps, TableScroller, Tabs, TabsContent, TabsList, type TabsListProps, TabsRoot, TabsTrigger, type TabsTriggerProps, Textarea, type TextareaProps, type ToastOptions, ToastProvider, type ToastProviderProps, type ToastTone, ToggleGroup, type ToggleGroupProps, type ToggleOption, Tooltip, TooltipContent, type TooltipContentProps, type TooltipProps, TooltipProvider, TooltipRoot, TooltipTrigger, type UseSortStateResult, cn, formatCalendarDate, formatDate, formatDateTime, formatMoney, formatNumber, formatPercent, useDebouncedValue, useFieldControl, usePagedRows, usePersistentState, useSortState, useToast };
2088
+ export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionRoot, type AccordionSection, AccordionTrigger, type AccordionTriggerProps, Alert, type AlertProps, type AlertTone, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, type BadgeTone, BootScreen, type BootScreenProps, type Breadcrumb, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonSize, type ButtonVariant, Card, CardBody, CardDescription, CardFooter, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, Checkbox, type CheckboxProps, type ClassValue, Combobox, type ComboboxOption, type ComboboxProps, ComingSoon, type ComingSoonProps, ConfirmDialog, type ConfirmDialogProps, CountBadge, type CountBadgeProps, CreatableSelect, type CreatableSelectProps, DataList, DataListItem, type DataListItemProps, type DataListProps, Dialog, DialogBody, DialogClose, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, DialogHeader, DialogPortal, DialogRoot, type DialogSize, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, ExternalLink, type ExternalLinkProps, Field, type FieldProps, Fieldset, type FieldsetProps, FileButton, type FileButtonProps, FilterBar, type FilterBarProps, type FilterSelect, type FilterToggle, type FilterValue, IconButton, type IconButtonProps, Input, type InputProps, type InputSize, Label, type LabelProps, ListToolbar, type ListToolbarProps, LoadingState, type LoadingStateProps, Menu, type MenuAction, MenuCheckboxItem, MenuContent, MenuGroup, MenuItem, type MenuItemProps, MenuLabel, type MenuProps, MenuRadioGroup, MenuRadioItem, MenuRoot, MenuSeparator, MenuSub, MenuSubContent, MenuSubTrigger, MenuTrigger, Modal, type ModalProps, type MoneyOptions, PageHeader, type PageHeaderProps, type PagedRows, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, type PopoverContentProps, type PopoverProps, PopoverRoot, PopoverTrigger, Progress, type ProgressProps, RequiredMark, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, type SelectOption, type SelectProps, SelectRoot, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, SelectionBar, type SelectionBarProps, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, SideModal, Skeleton, type SkeletonProps, Slider, type SliderProps, type SortDirection, type SortState, SplitButton, type SplitButtonProps, StatGrid, type StatGridProps, StatTile, type StatTileProps, type StatTone, Switch, type SwitchProps, type TabDefinition, TabSet, type TabSetProps, Table, TableBody, TableCaption, TableCell, type TableCellProps, TableEmpty, TableFoot, TableFooter, TableHead, type TableHeadProps, TableHeader, type TableProps, TableRow, type TableRowProps, TableScroller, Tabs, TabsContent, TabsList, type TabsListProps, TabsRoot, TabsTrigger, type TabsTriggerProps, Textarea, type TextareaProps, Timeline, type TimelineItem, type TimelineLane, type TimelineLayout, type TimelineLayoutItem, type TimelineProps, type TimelineTone, type ToastOptions, ToastProvider, type ToastProviderProps, type ToastTone, ToggleGroup, type ToggleGroupProps, type ToggleOption, Tooltip, TooltipContent, type TooltipContentProps, type TooltipProps, TooltipProvider, TooltipRoot, TooltipTrigger, type UseSortStateResult, cn, formatCalendarDate, formatDate, formatDateTime, formatMoney, formatNumber, formatPercent, timelineLayout, useDebouncedValue, useFieldControl, usePagedRows, usePersistentState, useSortState, useToast };
package/dist/index.js CHANGED
@@ -2243,6 +2243,252 @@ function usePersistentState(key, initial) {
2243
2243
  return [value, set];
2244
2244
  }
2245
2245
 
2246
+ // src/format.ts
2247
+ function formatDate(value) {
2248
+ const day = calendarDay(value);
2249
+ if (day !== null) return day;
2250
+ const d = toDate(value);
2251
+ if (!d) return "\u2014";
2252
+ return ymd(d.getFullYear(), d.getMonth() + 1, d.getDate());
2253
+ }
2254
+ function formatCalendarDate(value) {
2255
+ const day = calendarDay(value);
2256
+ if (day !== null) return day;
2257
+ const d = toDate(value);
2258
+ if (!d) return "\u2014";
2259
+ return ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
2260
+ }
2261
+ function ymd(year, month, day) {
2262
+ const pad = (n) => String(n).padStart(2, "0");
2263
+ return `${year}-${pad(month)}-${pad(day)}`;
2264
+ }
2265
+ function calendarDay(value) {
2266
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
2267
+ const d = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
2268
+ return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value ? value : "\u2014";
2269
+ }
2270
+ function formatDateTime(value) {
2271
+ const d = toDate(value);
2272
+ if (!d) return "\u2014";
2273
+ const pad = (n) => String(n).padStart(2, "0");
2274
+ return `${formatDate(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
2275
+ }
2276
+ function formatMoney(value, { currency = "USD", decimals, perUnit = false, compact = false } = {}) {
2277
+ if (value == null || Number.isNaN(value)) return "\u2014";
2278
+ if (compact) return compactMoney(value, currency, decimals ?? 1);
2279
+ const places = decimals ?? (perUnit ? 4 : 2);
2280
+ return new Intl.NumberFormat("en-US", {
2281
+ style: "currency",
2282
+ currency,
2283
+ minimumFractionDigits: places,
2284
+ maximumFractionDigits: places
2285
+ }).format(value);
2286
+ }
2287
+ function compactMoney(value, currency, decimals) {
2288
+ let symbol = currency;
2289
+ try {
2290
+ symbol = new Intl.NumberFormat("en-US", { style: "currency", currency }).formatToParts(0).find((p) => p.type === "currency")?.value ?? currency;
2291
+ } catch {
2292
+ }
2293
+ const abs = Math.abs(value);
2294
+ const sign = value < 0 ? "-" : "";
2295
+ for (const [size, suffix] of [[1e9, "B"], [1e6, "M"], [1e3, "K"]]) {
2296
+ if (abs >= size) return `${sign}${symbol}${(abs / size).toFixed(decimals)}${suffix}`;
2297
+ }
2298
+ return `${sign}${symbol}${Math.round(abs)}`;
2299
+ }
2300
+ function formatNumber(value, decimals = 0) {
2301
+ if (value == null || Number.isNaN(value)) return "\u2014";
2302
+ return new Intl.NumberFormat("en-US", {
2303
+ minimumFractionDigits: decimals,
2304
+ maximumFractionDigits: decimals
2305
+ }).format(value);
2306
+ }
2307
+ function formatPercent(value, decimals = 1) {
2308
+ if (value == null || Number.isNaN(value)) return "\u2014";
2309
+ return `${formatNumber(value, decimals)}%`;
2310
+ }
2311
+ function toDate(value) {
2312
+ if (value == null || value === "") return null;
2313
+ const d = value instanceof Date ? value : new Date(value);
2314
+ return Number.isNaN(d.getTime()) ? null : d;
2315
+ }
2316
+
2317
+ // src/Timeline.css
2318
+ styleInject('@layer components {\n :where(.cfi-ui-timeline) {\n container-type: inline-size;\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-sm);\n min-width: 0;\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-timeline__empty {\n margin: 0;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-timeline__lane-label {\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-semibold);\n letter-spacing: var(--cfi-ui-tracking-caps);\n text-transform: uppercase;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-timeline__track {\n --_row: 2.25rem;\n --_above: calc(var(--cfi-ui-timeline-above, 1) * var(--_row) + 0.75rem);\n --_below: calc(var(--cfi-ui-timeline-below, 1) * var(--_row) + 0.75rem);\n position: relative;\n height: calc(var(--_above) + var(--_below) + 2.5rem);\n margin: 0 var(--cfi-ui-space-xs);\n }\n .cfi-ui-timeline__axis {\n position: absolute;\n inset-inline: 0;\n top: var(--_above);\n height: 0.375rem;\n margin-top: -0.1875rem;\n border-radius: var(--cfi-ui-radius-full);\n background: var(--cfi-ui-surface-muted);\n }\n .cfi-ui-timeline__lane[data-line=dashed] .cfi-ui-timeline__axis {\n height: 0;\n margin-top: 0;\n border-radius: 0;\n background: none;\n border-top: 2px dashed var(--cfi-ui-border-strong);\n }\n .cfi-ui-timeline__year {\n position: absolute;\n top: var(--_above);\n height: calc(var(--_below) + 0.25rem);\n width: 0;\n border-left: 1px solid var(--cfi-ui-border);\n }\n .cfi-ui-timeline__year > span {\n position: absolute;\n top: 100%;\n transform: translateX(-50%);\n padding: 0 var(--cfi-ui-space-3xs);\n background: var(--cfi-ui-surface);\n color: var(--cfi-ui-content-muted);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ui-timeline__today {\n position: absolute;\n top: 0.25rem;\n bottom: 0;\n width: 2px;\n margin-left: -1px;\n background: var(--cfi-ui-content);\n z-index: 1;\n }\n .cfi-ui-timeline__today-label {\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n padding: 0 var(--cfi-ui-space-2xs);\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-content);\n color: var(--cfi-ui-content-inverse);\n font-weight: var(--cfi-ui-weight-semibold);\n white-space: nowrap;\n }\n .cfi-ui-timeline__items {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n .cfi-ui-timeline__item {\n position: absolute;\n top: var(--_above);\n width: 0;\n height: 0;\n z-index: 2;\n }\n .cfi-ui-timeline__marker {\n position: absolute;\n left: 0;\n top: 0;\n width: 0.875rem;\n height: 0.875rem;\n transform: translate(-50%, -50%);\n border: 2px solid var(--cfi-ui-surface);\n border-radius: var(--cfi-ui-radius-full);\n background: var(--cfi-ui-accent);\n }\n .cfi-ui-timeline__item[data-shape=diamond] .cfi-ui-timeline__marker {\n width: 0.75rem;\n height: 0.75rem;\n border-radius: 2px;\n transform: translate(-50%, -50%) rotate(45deg);\n }\n .cfi-ui-timeline__item[data-tone=neutral][data-past] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-timeline__item[data-tone=success] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-success);\n }\n .cfi-ui-timeline__item[data-tone=warning] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-warning);\n }\n .cfi-ui-timeline__item[data-tone=error] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-error);\n }\n .cfi-ui-timeline__item[data-tone=info] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-info);\n }\n .cfi-ui-timeline__item[data-tone=accent] .cfi-ui-timeline__marker {\n background: var(--cfi-ui-accent);\n }\n .cfi-ui-timeline__text {\n position: absolute;\n left: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n transform: translateX(-50%);\n padding: 0 var(--cfi-ui-space-3xs);\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-surface);\n white-space: nowrap;\n line-height: var(--cfi-ui-leading-snug);\n text-align: center;\n }\n .cfi-ui-timeline__item[data-side=above] .cfi-ui-timeline__text {\n bottom: 0.625rem;\n }\n .cfi-ui-timeline__item[data-side=below] .cfi-ui-timeline__text {\n top: 0.625rem;\n }\n .cfi-ui-timeline__item[data-side=above][data-level="1"] .cfi-ui-timeline__text {\n bottom: calc(0.625rem + var(--_row));\n }\n .cfi-ui-timeline__item[data-side=below][data-level="1"] .cfi-ui-timeline__text {\n top: calc(0.625rem + var(--_row));\n }\n .cfi-ui-timeline__item[data-side=above][data-level="2"] .cfi-ui-timeline__text {\n bottom: calc(0.625rem + 2 * var(--_row));\n }\n .cfi-ui-timeline__item[data-side=below][data-level="2"] .cfi-ui-timeline__text {\n top: calc(0.625rem + 2 * var(--_row));\n }\n .cfi-ui-timeline__item[data-edge=start] .cfi-ui-timeline__text {\n align-items: flex-start;\n text-align: start;\n transform: translateX(-0.5rem);\n }\n .cfi-ui-timeline__item[data-edge=end] .cfi-ui-timeline__text {\n align-items: flex-end;\n text-align: end;\n transform: translateX(calc(-100% + 0.5rem));\n }\n .cfi-ui-timeline__label {\n font-weight: var(--cfi-ui-weight-semibold);\n }\n .cfi-ui-timeline__detail {\n color: var(--cfi-ui-content-muted);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ui-timeline__outside {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-timeline__outside > p {\n margin: 0;\n }\n .cfi-ui-timeline__outside-lead {\n font-weight: var(--cfi-ui-weight-medium);\n }\n @container (max-width: 34rem) {\n .cfi-ui-timeline__track {\n height: auto;\n margin: 0;\n }\n .cfi-ui-timeline__axis,\n .cfi-ui-timeline__year,\n .cfi-ui-timeline__today {\n display: none;\n }\n .cfi-ui-timeline__items {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-2xs);\n }\n .cfi-ui-timeline__item {\n position: static;\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n width: auto;\n height: auto;\n }\n .cfi-ui-timeline__marker {\n position: relative;\n flex: none;\n transform: none;\n }\n .cfi-ui-timeline__item[data-shape=diamond] .cfi-ui-timeline__marker {\n transform: rotate(45deg);\n }\n .cfi-ui-timeline__item[data-side] .cfi-ui-timeline__text {\n position: static;\n padding: 0;\n flex-direction: row;\n gap: var(--cfi-ui-space-xs);\n align-items: baseline;\n transform: none;\n white-space: normal;\n text-align: start;\n }\n }\n}\n');
2319
+
2320
+ // src/Timeline.tsx
2321
+ import { Fragment as Fragment8, jsx as jsx28, jsxs as jsxs28 } from "react/jsx-runtime";
2322
+ var DAY = 864e5;
2323
+ function timelineTime(value, calendar = true) {
2324
+ if (value == null || value === "") return null;
2325
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
2326
+ const [y, m, d] = value.split("-").map(Number);
2327
+ return calendar ? Date.UTC(y, m - 1, d) : new Date(y, m - 1, d).getTime();
2328
+ }
2329
+ const date = value instanceof Date ? value : new Date(value);
2330
+ const t = date.getTime();
2331
+ if (Number.isNaN(t)) return null;
2332
+ return calendar ? Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) : t;
2333
+ }
2334
+ function todayTime(value, calendar) {
2335
+ if (value === null) return null;
2336
+ if (value !== void 0) return timelineTime(value, calendar);
2337
+ const now = /* @__PURE__ */ new Date();
2338
+ return calendar ? Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) : now.getTime();
2339
+ }
2340
+ var DEFAULT_LANE = { id: "__default", label: null };
2341
+ var SLOTS = [
2342
+ ["above", 0],
2343
+ ["below", 0],
2344
+ ["above", 1],
2345
+ ["below", 1],
2346
+ ["above", 2],
2347
+ ["below", 2]
2348
+ ];
2349
+ function placeLabels(items, minGap) {
2350
+ const last = SLOTS.map(() => -Infinity);
2351
+ const levels = { above: 1, below: 1 };
2352
+ for (const x of items) {
2353
+ let slot = SLOTS.findIndex((_, i) => x.pos - last[i] >= minGap);
2354
+ if (slot === -1) slot = last.indexOf(Math.min(...last));
2355
+ last[slot] = x.pos;
2356
+ [x.side, x.level] = SLOTS[slot];
2357
+ levels[x.side] = Math.max(levels[x.side], x.level + 1);
2358
+ }
2359
+ return levels;
2360
+ }
2361
+ function timelineLayout({
2362
+ items,
2363
+ lanes,
2364
+ start,
2365
+ end,
2366
+ today,
2367
+ calendar = true,
2368
+ minGap = 12
2369
+ }) {
2370
+ const laneList = lanes && lanes.length > 0 ? lanes : [DEFAULT_LANE];
2371
+ const dated = items.map((item) => ({ item, time: timelineTime(item.date, calendar) })).filter((x) => x.time !== null).sort((a, b) => a.time - b.time || a.item.id.localeCompare(b.item.id));
2372
+ const now = todayTime(today, calendar);
2373
+ const times = dated.map((d) => d.time);
2374
+ if (now !== null) times.push(now);
2375
+ let lo = timelineTime(start, calendar) ?? (times.length ? Math.min(...times) : now ?? Date.now());
2376
+ let hi = timelineTime(end, calendar) ?? (times.length ? Math.max(...times) : lo);
2377
+ if (hi <= lo) {
2378
+ lo -= 30 * DAY;
2379
+ hi += 30 * DAY;
2380
+ }
2381
+ const pos = (t) => 3 + (t - lo) / (hi - lo) * 94;
2382
+ const laneIndex = new Map(laneList.map((l, i) => [l.id, i]));
2383
+ const byLane = laneList.map((lane) => ({ lane, items: [], levels: { above: 1, below: 1 } }));
2384
+ const before = [];
2385
+ const after = [];
2386
+ for (const { item, time } of dated) {
2387
+ const p = pos(time);
2388
+ const placed = {
2389
+ item,
2390
+ time,
2391
+ pos: p,
2392
+ side: "above",
2393
+ level: 0,
2394
+ past: now !== null && time < now,
2395
+ edge: p < 12 ? "start" : p > 88 ? "end" : null
2396
+ };
2397
+ if (time < lo) before.push(placed);
2398
+ else if (time > hi) after.push(placed);
2399
+ else byLane[laneIndex.get(item.lane ?? "") ?? 0].items.push(placed);
2400
+ }
2401
+ for (const lane of byLane) lane.levels = placeLabels(lane.items, minGap);
2402
+ const firstYear = new Date(lo).getUTCFullYear() + 1;
2403
+ const lastYear = new Date(hi).getUTCFullYear();
2404
+ const step = lastYear - firstYear >= 12 ? 2 : 1;
2405
+ const years = [];
2406
+ for (let y = firstYear; y <= lastYear; y += step) {
2407
+ const t = calendar ? Date.UTC(y, 0, 1) : new Date(y, 0, 1).getTime();
2408
+ if (t > lo && t <= hi) years.push({ year: y, pos: pos(t) });
2409
+ }
2410
+ const todayInSpan = now !== null && now >= lo && now <= hi;
2411
+ return { start: lo, end: hi, lanes: byLane, before, after, years, today: now, todayPos: todayInSpan ? pos(now) : null };
2412
+ }
2413
+ function Timeline({
2414
+ items,
2415
+ lanes,
2416
+ start,
2417
+ end,
2418
+ today,
2419
+ todayLabel = "Today",
2420
+ calendar = true,
2421
+ beforeLabel = "Before this view",
2422
+ afterLabel = "After this view",
2423
+ emptyLabel = "No dates",
2424
+ className,
2425
+ ...rest
2426
+ }) {
2427
+ const layout = timelineLayout({ items, lanes, start, end, today, calendar });
2428
+ const fmt = (value) => calendar ? formatCalendarDate(value) : formatDate(value);
2429
+ const detailOf = (x) => x.item.detail === void 0 ? fmt(x.item.date) : x.item.detail;
2430
+ const empty = layout.lanes.every((l) => l.items.length === 0) && layout.before.length === 0 && layout.after.length === 0;
2431
+ return /* @__PURE__ */ jsx28("div", { className: cn("cfi-ui", "cfi-ui-timeline", className), ...rest, children: empty ? /* @__PURE__ */ jsx28("p", { className: "cfi-ui-timeline__empty", children: emptyLabel }) : /* @__PURE__ */ jsxs28(Fragment8, { children: [
2432
+ layout.lanes.map(({ lane, items: laneItems, levels }, laneIdx) => /* @__PURE__ */ jsxs28("div", { className: "cfi-ui-timeline__lane", "data-line": lane.line ?? "solid", "data-first": laneIdx === 0 || void 0, children: [
2433
+ lane.label ? /* @__PURE__ */ jsx28("div", { className: "cfi-ui-timeline__lane-label", children: lane.label }) : null,
2434
+ /* @__PURE__ */ jsxs28(
2435
+ "div",
2436
+ {
2437
+ className: "cfi-ui-timeline__track",
2438
+ style: { "--cfi-ui-timeline-above": levels.above, "--cfi-ui-timeline-below": levels.below },
2439
+ children: [
2440
+ /* @__PURE__ */ jsx28("div", { className: "cfi-ui-timeline__axis", "aria-hidden": "true" }),
2441
+ laneIdx === 0 ? layout.years.map((y) => /* @__PURE__ */ jsx28("div", { className: "cfi-ui-timeline__year", style: { left: `${y.pos}%` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx28("span", { children: y.year }) }, y.year)) : null,
2442
+ layout.todayPos !== null ? /* @__PURE__ */ jsx28("div", { className: "cfi-ui-timeline__today", style: { left: `${layout.todayPos}%` }, "aria-hidden": "true", children: laneIdx === layout.lanes.length - 1 ? /* @__PURE__ */ jsx28("span", { className: "cfi-ui-timeline__today-label", children: todayLabel }) : null }) : null,
2443
+ /* @__PURE__ */ jsx28("ol", { className: "cfi-ui-timeline__items", "aria-label": typeof lane.label === "string" ? lane.label : void 0, children: laneItems.map((x) => {
2444
+ const detail = detailOf(x);
2445
+ return /* @__PURE__ */ jsxs28(
2446
+ "li",
2447
+ {
2448
+ className: "cfi-ui-timeline__item",
2449
+ style: { left: `${x.pos}%` },
2450
+ "data-side": x.side,
2451
+ "data-level": x.level,
2452
+ "data-edge": x.edge ?? void 0,
2453
+ "data-past": x.past || void 0,
2454
+ "data-tone": x.item.tone ?? "neutral",
2455
+ "data-shape": x.item.shape ?? "dot",
2456
+ children: [
2457
+ /* @__PURE__ */ jsx28("span", { className: "cfi-ui-timeline__marker", "aria-hidden": "true" }),
2458
+ /* @__PURE__ */ jsxs28("span", { className: "cfi-ui-timeline__text", children: [
2459
+ /* @__PURE__ */ jsx28("span", { className: "cfi-ui-timeline__label", children: x.item.label }),
2460
+ detail != null && detail !== "" ? /* @__PURE__ */ jsx28("span", { className: "cfi-ui-timeline__detail", children: detail }) : null
2461
+ ] })
2462
+ ]
2463
+ },
2464
+ x.item.id
2465
+ );
2466
+ }) })
2467
+ ]
2468
+ }
2469
+ )
2470
+ ] }, lane.id)),
2471
+ layout.before.length > 0 || layout.after.length > 0 ? /* @__PURE__ */ jsx28("div", { className: "cfi-ui-timeline__outside", children: [
2472
+ [beforeLabel, layout.before],
2473
+ [afterLabel, layout.after]
2474
+ ].map(
2475
+ ([lead, list], i) => list.length > 0 ? /* @__PURE__ */ jsxs28("p", { children: [
2476
+ /* @__PURE__ */ jsxs28("span", { className: "cfi-ui-timeline__outside-lead", children: [
2477
+ lead,
2478
+ ":"
2479
+ ] }),
2480
+ " ",
2481
+ list.map((x, j) => /* @__PURE__ */ jsxs28("span", { children: [
2482
+ j > 0 ? " \xB7 " : null,
2483
+ x.item.label,
2484
+ " ",
2485
+ detailOf(x)
2486
+ ] }, x.item.id))
2487
+ ] }, i) : null
2488
+ ) }) : null
2489
+ ] }) });
2490
+ }
2491
+
2246
2492
  // src/useSortState.ts
2247
2493
  import { useCallback as useCallback3, useMemo as useMemo5, useState as useState6 } from "react";
2248
2494
  function useSortState(initialKey, initialDirection = "asc") {
@@ -2290,7 +2536,7 @@ import { Search as Search2, X as X6 } from "lucide-react";
2290
2536
  styleInject("@layer components {\n .cfi-ui-search__clear {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n background: none;\n border: none;\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n border-radius: var(--cfi-ui-radius-sm);\n }\n .cfi-ui-search__clear > svg {\n width: 0.875rem;\n height: 0.875rem;\n }\n .cfi-ui-search__clear:hover {\n color: var(--cfi-ui-content);\n }\n .cfi-ui-search__clear:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: 1px;\n }\n}\n");
2291
2537
 
2292
2538
  // src/SearchInput.tsx
2293
- import { jsx as jsx28 } from "react/jsx-runtime";
2539
+ import { jsx as jsx29 } from "react/jsx-runtime";
2294
2540
  var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay = 250, placeholder = "Search\u2026", clearLabel = "Clear search", ...rest }, ref) {
2295
2541
  const [local, setLocal] = useState7(value);
2296
2542
  const timer = useRef4(void 0);
@@ -2309,7 +2555,7 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
2309
2555
  setLocal("");
2310
2556
  latest.current("");
2311
2557
  };
2312
- return /* @__PURE__ */ jsx28(
2558
+ return /* @__PURE__ */ jsx29(
2313
2559
  Input,
2314
2560
  {
2315
2561
  ...rest,
@@ -2318,8 +2564,8 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
2318
2564
  value: local,
2319
2565
  placeholder,
2320
2566
  onChange: (e) => push(e.target.value),
2321
- prefix: /* @__PURE__ */ jsx28(Search2, { "aria-hidden": "true" }),
2322
- suffix: local ? /* @__PURE__ */ jsx28("button", { type: "button", className: "cfi-ui-search__clear", onClick: clear, "aria-label": clearLabel, children: /* @__PURE__ */ jsx28(X6, { "aria-hidden": "true" }) }) : void 0
2567
+ prefix: /* @__PURE__ */ jsx29(Search2, { "aria-hidden": "true" }),
2568
+ suffix: local ? /* @__PURE__ */ jsx29("button", { type: "button", className: "cfi-ui-search__clear", onClick: clear, "aria-label": clearLabel, children: /* @__PURE__ */ jsx29(X6, { "aria-hidden": "true" }) }) : void 0
2323
2569
  }
2324
2570
  );
2325
2571
  });
@@ -2331,7 +2577,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
2331
2577
  styleInject("@layer components {\n :where(.cfi-ui-pagination) {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--cfi-ui-space-md);\n padding-block: var(--cfi-ui-space-sm);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-pagination__controls {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n margin-left: auto;\n }\n .cfi-ui-pagination__page {\n font-variant-numeric: tabular-nums;\n color: var(--cfi-ui-content);\n }\n .cfi-ui-pagination__range {\n font-variant-numeric: tabular-nums;\n }\n .cfi-ui-pagination__btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--cfi-ui-control-height-sm);\n height: var(--cfi-ui-control-height-sm);\n padding: 0;\n background: var(--cfi-ui-surface);\n color: var(--cfi-ui-content);\n border: 1px solid var(--cfi-ui-border-strong);\n border-radius: var(--cfi-ui-radius-md);\n cursor: pointer;\n }\n .cfi-ui-pagination__btn > svg {\n width: 1rem;\n height: 1rem;\n }\n .cfi-ui-pagination__btn:hover:not(:disabled) {\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-pagination__btn:disabled {\n color: var(--cfi-ui-content-disabled);\n cursor: default;\n }\n .cfi-ui-pagination__btn:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: var(--cfi-ui-focus-offset);\n }\n}\n");
2332
2578
 
2333
2579
  // src/Pagination.tsx
2334
- import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
2580
+ import { jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
2335
2581
  function Pagination({
2336
2582
  page,
2337
2583
  pageSize,
@@ -2347,12 +2593,12 @@ function Pagination({
2347
2593
  const from = (current - 1) * pageSize + 1;
2348
2594
  const to = Math.min(current * pageSize, totalItems);
2349
2595
  const range = labels?.range?.(from, to, totalItems) ?? `${from}\u2013${to} of ${totalItems.toLocaleString()}`;
2350
- return /* @__PURE__ */ jsxs28("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
2596
+ return /* @__PURE__ */ jsxs29("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
2351
2597
  showRange && // aria-live so a screen-reader user hears where they landed; paging
2352
2598
  // otherwise moves focus nowhere and announces nothing.
2353
- /* @__PURE__ */ jsx29("span", { className: "cfi-ui-pagination__range", "aria-live": "polite", children: range }),
2354
- /* @__PURE__ */ jsxs28("div", { className: "cfi-ui-pagination__controls", children: [
2355
- /* @__PURE__ */ jsx29(
2599
+ /* @__PURE__ */ jsx30("span", { className: "cfi-ui-pagination__range", "aria-live": "polite", children: range }),
2600
+ /* @__PURE__ */ jsxs29("div", { className: "cfi-ui-pagination__controls", children: [
2601
+ /* @__PURE__ */ jsx30(
2356
2602
  "button",
2357
2603
  {
2358
2604
  type: "button",
@@ -2360,15 +2606,15 @@ function Pagination({
2360
2606
  onClick: () => onPageChange(current - 1),
2361
2607
  disabled: current <= 1,
2362
2608
  "aria-label": labels?.previous ?? "Previous page",
2363
- children: /* @__PURE__ */ jsx29(ChevronLeft, { "aria-hidden": "true" })
2609
+ children: /* @__PURE__ */ jsx30(ChevronLeft, { "aria-hidden": "true" })
2364
2610
  }
2365
2611
  ),
2366
- /* @__PURE__ */ jsxs28("span", { className: "cfi-ui-pagination__page", children: [
2612
+ /* @__PURE__ */ jsxs29("span", { className: "cfi-ui-pagination__page", children: [
2367
2613
  current,
2368
2614
  " / ",
2369
2615
  pageCount
2370
2616
  ] }),
2371
- /* @__PURE__ */ jsx29(
2617
+ /* @__PURE__ */ jsx30(
2372
2618
  "button",
2373
2619
  {
2374
2620
  type: "button",
@@ -2376,7 +2622,7 @@ function Pagination({
2376
2622
  onClick: () => onPageChange(current + 1),
2377
2623
  disabled: current >= pageCount,
2378
2624
  "aria-label": labels?.next ?? "Next page",
2379
- children: /* @__PURE__ */ jsx29(ChevronRight, { "aria-hidden": "true" })
2625
+ children: /* @__PURE__ */ jsx30(ChevronRight, { "aria-hidden": "true" })
2380
2626
  }
2381
2627
  )
2382
2628
  ] })
@@ -2387,12 +2633,12 @@ function Pagination({
2387
2633
  styleInject("@layer components {\n :where(.cfi-ui-list-toolbar) {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--cfi-ui-space-md);\n min-width: 0;\n }\n .cfi-ui-list-toolbar__search {\n flex: 1 1 14rem;\n min-width: 0;\n max-width: 22rem;\n }\n .cfi-ui-list-toolbar__filters {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n min-width: 0;\n flex: 0 1 auto;\n }\n .cfi-ui-list-toolbar__actions {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n margin-inline-start: auto;\n }\n @media (max-width: 40rem) {\n .cfi-ui-list-toolbar__search,\n .cfi-ui-list-toolbar__filters,\n .cfi-ui-list-toolbar__actions {\n flex: 1 1 100%;\n max-width: none;\n margin-inline-start: 0;\n }\n }\n}\n");
2388
2634
 
2389
2635
  // src/ListToolbar.tsx
2390
- import { jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
2636
+ import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
2391
2637
  function ListToolbar({ search, filters, actions, className }) {
2392
- return /* @__PURE__ */ jsxs29("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
2393
- search && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__search", children: search }),
2394
- filters && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
2395
- actions && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
2638
+ return /* @__PURE__ */ jsxs30("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
2639
+ search && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-list-toolbar__search", children: search }),
2640
+ filters && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
2641
+ actions && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
2396
2642
  ] });
2397
2643
  }
2398
2644
 
@@ -2403,7 +2649,7 @@ import { ArrowLeft, ChevronRight as ChevronRight2 } from "lucide-react";
2403
2649
  styleInject("@layer components {\n :where(.cfi-ui-page-header) {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-xs);\n font-family: var(--cfi-ui-font);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-page-header[data-bordered] {\n padding-bottom: var(--cfi-ui-space-md);\n border-bottom: 1px solid var(--cfi-ui-border);\n }\n .cfi-ui-page-header__crumbs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-ui-space-3xs);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-page-header__crumb {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-ui-space-3xs);\n }\n .cfi-ui-page-header__crumb-sep {\n width: 0.75rem;\n height: 0.75rem;\n flex: none;\n color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-page-header__crumb a {\n color: inherit;\n text-decoration: none;\n border-radius: var(--cfi-ui-radius-sm);\n transition: color var(--cfi-ui-transition-fast);\n }\n .cfi-ui-page-header__crumb a:hover {\n color: var(--cfi-ui-content);\n }\n .cfi-ui-page-header__crumb a:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: var(--cfi-ui-focus-offset);\n }\n .cfi-ui-page-header__crumb[data-current] {\n color: var(--cfi-ui-content);\n }\n .cfi-ui-page-header__back {\n display: inline-flex;\n align-items: center;\n align-self: flex-start;\n gap: var(--cfi-ui-space-2xs);\n margin-left: -0.5rem;\n padding: 0.25rem 0.5rem;\n border: 0;\n border-radius: var(--cfi-ui-radius-md);\n background: none;\n font: inherit;\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n transition: color var(--cfi-ui-transition-fast), background var(--cfi-ui-transition-fast);\n }\n .cfi-ui-page-header__back:hover {\n background: var(--cfi-ui-surface-hover);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-page-header__back:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: var(--cfi-ui-focus-offset);\n }\n .cfi-ui-page-header__back > svg {\n width: 1rem;\n height: 1rem;\n flex: none;\n }\n .cfi-ui-page-header__row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--cfi-ui-space-md);\n }\n .cfi-ui-page-header__titles {\n min-width: 0;\n }\n .cfi-ui-page-header__title,\n h1.cfi-ui-page-header__title,\n h2.cfi-ui-page-header__title,\n h3.cfi-ui-page-header__title {\n margin: 0;\n font-size: var(--cfi-ui-text-xl);\n font-weight: var(--cfi-ui-weight-semibold);\n line-height: var(--cfi-ui-leading-tight);\n letter-spacing: -0.01em;\n color: var(--cfi-ui-content);\n }\n .cfi-ui-page-header__description {\n margin: 0.25rem 0 0;\n font-size: var(--cfi-ui-text-sm);\n line-height: var(--cfi-ui-leading-normal);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-page-header__actions {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n flex: none;\n }\n .cfi-ui-page-header__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n margin-top: var(--cfi-ui-space-3xs);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content-muted);\n }\n @media (max-width: 640px) {\n .cfi-ui-page-header__row {\n flex-direction: column;\n align-items: stretch;\n gap: var(--cfi-ui-space-sm);\n }\n .cfi-ui-page-header__actions {\n flex-wrap: wrap;\n }\n }\n}\n");
2404
2650
 
2405
2651
  // src/PageHeader.tsx
2406
- import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
2652
+ import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
2407
2653
  function PageHeader({
2408
2654
  title,
2409
2655
  description,
@@ -2417,32 +2663,32 @@ function PageHeader({
2417
2663
  className
2418
2664
  }) {
2419
2665
  const Heading = `h${level}`;
2420
- return /* @__PURE__ */ jsxs30(
2666
+ return /* @__PURE__ */ jsxs31(
2421
2667
  "div",
2422
2668
  {
2423
2669
  className: cn("cfi-ui", "cfi-ui-page-header", className),
2424
2670
  "data-bordered": bordered ? "" : void 0,
2425
2671
  children: [
2426
- breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx31("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
2672
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx32("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
2427
2673
  const last = i === breadcrumbs.length - 1;
2428
- const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */ jsx31("a", { href: crumb.href, children: crumb.label }) : crumb.label;
2429
- return /* @__PURE__ */ jsxs30("span", { className: "cfi-ui-page-header__crumb", "data-current": last ? "" : void 0, children: [
2430
- i > 0 && /* @__PURE__ */ jsx31(ChevronRight2, { className: "cfi-ui-page-header__crumb-sep", "aria-hidden": "true" }),
2431
- /* @__PURE__ */ jsx31("span", { "aria-current": last ? "page" : void 0, children: label })
2674
+ const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */ jsx32("a", { href: crumb.href, children: crumb.label }) : crumb.label;
2675
+ return /* @__PURE__ */ jsxs31("span", { className: "cfi-ui-page-header__crumb", "data-current": last ? "" : void 0, children: [
2676
+ i > 0 && /* @__PURE__ */ jsx32(ChevronRight2, { className: "cfi-ui-page-header__crumb-sep", "aria-hidden": "true" }),
2677
+ /* @__PURE__ */ jsx32("span", { "aria-current": last ? "page" : void 0, children: label })
2432
2678
  ] }, i);
2433
2679
  }) }),
2434
- onBack && /* @__PURE__ */ jsxs30("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
2435
- /* @__PURE__ */ jsx31(ArrowLeft, { "aria-hidden": "true" }),
2680
+ onBack && /* @__PURE__ */ jsxs31("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
2681
+ /* @__PURE__ */ jsx32(ArrowLeft, { "aria-hidden": "true" }),
2436
2682
  backLabel
2437
2683
  ] }),
2438
- /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-page-header__row", children: [
2439
- /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-page-header__titles", children: [
2440
- /* @__PURE__ */ jsx31(Heading, { className: "cfi-ui-page-header__title", children: title }),
2441
- description && /* @__PURE__ */ jsx31("p", { className: "cfi-ui-page-header__description", children: description })
2684
+ /* @__PURE__ */ jsxs31("div", { className: "cfi-ui-page-header__row", children: [
2685
+ /* @__PURE__ */ jsxs31("div", { className: "cfi-ui-page-header__titles", children: [
2686
+ /* @__PURE__ */ jsx32(Heading, { className: "cfi-ui-page-header__title", children: title }),
2687
+ description && /* @__PURE__ */ jsx32("p", { className: "cfi-ui-page-header__description", children: description })
2442
2688
  ] }),
2443
- actions && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-page-header__actions", children: actions })
2689
+ actions && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-page-header__actions", children: actions })
2444
2690
  ] }),
2445
- children && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-page-header__meta", children })
2691
+ children && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-page-header__meta", children })
2446
2692
  ]
2447
2693
  }
2448
2694
  );
@@ -2455,7 +2701,7 @@ import { X as X7 } from "lucide-react";
2455
2701
  styleInject("@layer components {\n :where(.cfi-ui-selection-bar) {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n padding: var(--cfi-ui-space-2xs) var(--cfi-ui-space-sm);\n border: 1px solid var(--cfi-ui-accent);\n border-radius: var(--cfi-ui-radius-md);\n background: var(--cfi-ui-accent-soft);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content);\n min-height: 2.5rem;\n }\n .cfi-ui-selection-bar__count {\n font-weight: var(--cfi-ui-weight-medium);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n .cfi-ui-selection-bar__actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-ui-space-2xs);\n }\n .cfi-ui-selection-bar__clear {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-ui-space-3xs);\n margin-left: auto;\n padding: 0.25rem 0.5rem;\n border: 0;\n border-radius: var(--cfi-ui-radius-sm);\n background: none;\n font: inherit;\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n transition: color var(--cfi-ui-transition-fast), background var(--cfi-ui-transition-fast);\n }\n .cfi-ui-selection-bar__clear:hover:not(:disabled) {\n background: var(--cfi-ui-surface-hover);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-selection-bar__clear:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: var(--cfi-ui-focus-offset);\n }\n .cfi-ui-selection-bar__clear:disabled {\n color: var(--cfi-ui-content-disabled);\n cursor: not-allowed;\n }\n .cfi-ui-selection-bar__clear > svg {\n width: 0.875rem;\n height: 0.875rem;\n flex: none;\n }\n .cfi-ui-selection-bar[data-busy] {\n opacity: 0.6;\n pointer-events: none;\n }\n .cfi-ui-selection-bar[data-variant=floating] {\n position: fixed;\n left: 50%;\n bottom: var(--cfi-ui-space-lg);\n transform: translateX(-50%);\n z-index: var(--cfi-ui-z-dropdown);\n width: max-content;\n max-width: calc(100vw - 2 * var(--cfi-ui-space-lg));\n padding: var(--cfi-ui-space-xs) var(--cfi-ui-space-md);\n border-radius: var(--cfi-ui-radius-lg);\n background: var(--cfi-ui-surface-raised);\n box-shadow: var(--cfi-ui-shadow-lg);\n }\n @media (max-width: 640px) {\n .cfi-ui-selection-bar {\n flex-wrap: wrap;\n }\n .cfi-ui-selection-bar[data-variant=floating] {\n left: var(--cfi-ui-space-sm);\n right: var(--cfi-ui-space-sm);\n width: auto;\n max-width: none;\n transform: none;\n }\n }\n}\n");
2456
2702
 
2457
2703
  // src/SelectionBar.tsx
2458
- import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
2704
+ import { jsx as jsx33, jsxs as jsxs32 } from "react/jsx-runtime";
2459
2705
  function SelectionBar({
2460
2706
  count,
2461
2707
  children,
@@ -2467,7 +2713,7 @@ function SelectionBar({
2467
2713
  className
2468
2714
  }) {
2469
2715
  if (count === 0) return null;
2470
- return /* @__PURE__ */ jsxs31(
2716
+ return /* @__PURE__ */ jsxs32(
2471
2717
  "div",
2472
2718
  {
2473
2719
  role: "status",
@@ -2476,9 +2722,9 @@ function SelectionBar({
2476
2722
  "data-busy": busy ? "" : void 0,
2477
2723
  className: cn("cfi-ui", "cfi-ui-selection-bar", className),
2478
2724
  children: [
2479
- /* @__PURE__ */ jsx32("span", { className: "cfi-ui-selection-bar__count", children: label(count) }),
2480
- children && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-selection-bar__actions", children }),
2481
- onClear && /* @__PURE__ */ jsxs31(
2725
+ /* @__PURE__ */ jsx33("span", { className: "cfi-ui-selection-bar__count", children: label(count) }),
2726
+ children && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-selection-bar__actions", children }),
2727
+ onClear && /* @__PURE__ */ jsxs32(
2482
2728
  "button",
2483
2729
  {
2484
2730
  type: "button",
@@ -2486,7 +2732,7 @@ function SelectionBar({
2486
2732
  onClick: onClear,
2487
2733
  disabled: busy,
2488
2734
  children: [
2489
- /* @__PURE__ */ jsx32(X7, { "aria-hidden": "true" }),
2735
+ /* @__PURE__ */ jsx33(X7, { "aria-hidden": "true" }),
2490
2736
  clearLabel
2491
2737
  ]
2492
2738
  }
@@ -2518,77 +2764,6 @@ function usePagedRows(all, initialPageSize = 25) {
2518
2764
  };
2519
2765
  return { rows, page: clamped, setPage, pageSize, setPageSize, totalItems };
2520
2766
  }
2521
-
2522
- // src/format.ts
2523
- function formatDate(value) {
2524
- const day = calendarDay(value);
2525
- if (day !== null) return day;
2526
- const d = toDate(value);
2527
- if (!d) return "\u2014";
2528
- return ymd(d.getFullYear(), d.getMonth() + 1, d.getDate());
2529
- }
2530
- function formatCalendarDate(value) {
2531
- const day = calendarDay(value);
2532
- if (day !== null) return day;
2533
- const d = toDate(value);
2534
- if (!d) return "\u2014";
2535
- return ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
2536
- }
2537
- function ymd(year, month, day) {
2538
- const pad = (n) => String(n).padStart(2, "0");
2539
- return `${year}-${pad(month)}-${pad(day)}`;
2540
- }
2541
- function calendarDay(value) {
2542
- if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
2543
- const d = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
2544
- return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value ? value : "\u2014";
2545
- }
2546
- function formatDateTime(value) {
2547
- const d = toDate(value);
2548
- if (!d) return "\u2014";
2549
- const pad = (n) => String(n).padStart(2, "0");
2550
- return `${formatDate(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
2551
- }
2552
- function formatMoney(value, { currency = "USD", decimals, perUnit = false, compact = false } = {}) {
2553
- if (value == null || Number.isNaN(value)) return "\u2014";
2554
- if (compact) return compactMoney(value, currency, decimals ?? 1);
2555
- const places = decimals ?? (perUnit ? 4 : 2);
2556
- return new Intl.NumberFormat("en-US", {
2557
- style: "currency",
2558
- currency,
2559
- minimumFractionDigits: places,
2560
- maximumFractionDigits: places
2561
- }).format(value);
2562
- }
2563
- function compactMoney(value, currency, decimals) {
2564
- let symbol = currency;
2565
- try {
2566
- symbol = new Intl.NumberFormat("en-US", { style: "currency", currency }).formatToParts(0).find((p) => p.type === "currency")?.value ?? currency;
2567
- } catch {
2568
- }
2569
- const abs = Math.abs(value);
2570
- const sign = value < 0 ? "-" : "";
2571
- for (const [size, suffix] of [[1e9, "B"], [1e6, "M"], [1e3, "K"]]) {
2572
- if (abs >= size) return `${sign}${symbol}${(abs / size).toFixed(decimals)}${suffix}`;
2573
- }
2574
- return `${sign}${symbol}${Math.round(abs)}`;
2575
- }
2576
- function formatNumber(value, decimals = 0) {
2577
- if (value == null || Number.isNaN(value)) return "\u2014";
2578
- return new Intl.NumberFormat("en-US", {
2579
- minimumFractionDigits: decimals,
2580
- maximumFractionDigits: decimals
2581
- }).format(value);
2582
- }
2583
- function formatPercent(value, decimals = 1) {
2584
- if (value == null || Number.isNaN(value)) return "\u2014";
2585
- return `${formatNumber(value, decimals)}%`;
2586
- }
2587
- function toDate(value) {
2588
- if (value == null || value === "") return null;
2589
- const d = value instanceof Date ? value : new Date(value);
2590
- return Number.isNaN(d.getTime()) ? null : d;
2591
- }
2592
2767
  export {
2593
2768
  Accordion,
2594
2769
  AccordionContent,
@@ -2712,6 +2887,7 @@ export {
2712
2887
  TabsRoot,
2713
2888
  TabsTrigger,
2714
2889
  Textarea,
2890
+ Timeline,
2715
2891
  ToastProvider,
2716
2892
  ToggleGroup,
2717
2893
  Tooltip,
@@ -2726,6 +2902,7 @@ export {
2726
2902
  formatMoney,
2727
2903
  formatNumber,
2728
2904
  formatPercent,
2905
+ timelineLayout,
2729
2906
  useDebouncedValue,
2730
2907
  useFieldControl,
2731
2908
  usePagedRows,
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
- {
2
- "name": "@campfire-interactive/ui",
3
- "version": "0.10.8",
4
- "description": "Shared UI primitives for the Campfire Suite — Radix behavior, plain CSS styled entirely from the design tokens, no Tailwind required",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist"
16
- ],
17
- "scripts": {
18
- "build": "tsup",
19
- "prepublishOnly": "npm run build",
20
- "dev": "tsup --watch",
21
- "test": "vitest run",
22
- "typecheck": "tsc --noEmit"
23
- },
24
- "peerDependencies": {
25
- "react": "^18.0.0 || ^19.0.0",
26
- "react-dom": "^18.0.0 || ^19.0.0"
27
- },
28
- "dependencies": {
29
- "@campfire-interactive/design-tokens": "*",
30
- "@radix-ui/react-accordion": "^1.2.12",
31
- "@radix-ui/react-checkbox": "^1.3.3",
32
- "@radix-ui/react-dialog": "^1.1.15",
33
- "@radix-ui/react-dropdown-menu": "^2.1.16",
34
- "@radix-ui/react-popover": "^1.1.15",
35
- "@radix-ui/react-select": "^2.2.6",
36
- "@radix-ui/react-slider": "^1.3.6",
37
- "@radix-ui/react-slot": "^1.2.3",
38
- "@radix-ui/react-switch": "^1.2.6",
39
- "@radix-ui/react-tabs": "^1.1.13",
40
- "@radix-ui/react-toggle-group": "^1.1.11",
41
- "@radix-ui/react-tooltip": "^1.2.8",
42
- "lucide-react": "^0.487.0"
43
- },
44
- "devDependencies": {
45
- "@types/react": "^19.0.0",
46
- "@types/react-dom": "^19.0.0",
47
- "react": "^19.0.0",
48
- "react-dom": "^19.0.0",
49
- "tsup": "^8.0.0",
50
- "typescript": "^5.3.3"
51
- },
52
- "publishConfig": {
53
- "registry": "https://registry.npmjs.org",
54
- "access": "public"
55
- }
56
- }
1
+ {
2
+ "name": "@campfire-interactive/ui",
3
+ "version": "0.11.0",
4
+ "description": "Shared UI primitives for the Campfire Suite — Radix behavior, plain CSS styled entirely from the design tokens, no Tailwind required",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "prepublishOnly": "npm run build",
20
+ "dev": "tsup --watch",
21
+ "test": "vitest run",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "peerDependencies": {
25
+ "react": "^18.0.0 || ^19.0.0",
26
+ "react-dom": "^18.0.0 || ^19.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@campfire-interactive/design-tokens": "*",
30
+ "@radix-ui/react-accordion": "^1.2.12",
31
+ "@radix-ui/react-checkbox": "^1.3.3",
32
+ "@radix-ui/react-dialog": "^1.1.15",
33
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
34
+ "@radix-ui/react-popover": "^1.1.15",
35
+ "@radix-ui/react-select": "^2.2.6",
36
+ "@radix-ui/react-slider": "^1.3.6",
37
+ "@radix-ui/react-slot": "^1.2.3",
38
+ "@radix-ui/react-switch": "^1.2.6",
39
+ "@radix-ui/react-tabs": "^1.1.13",
40
+ "@radix-ui/react-toggle-group": "^1.1.11",
41
+ "@radix-ui/react-tooltip": "^1.2.8",
42
+ "lucide-react": "^0.487.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/react": "^19.0.0",
46
+ "@types/react-dom": "^19.0.0",
47
+ "react": "^19.0.0",
48
+ "react-dom": "^19.0.0",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.3.3"
51
+ },
52
+ "publishConfig": {
53
+ "registry": "https://registry.npmjs.org",
54
+ "access": "public"
55
+ }
56
+ }