@campfire-interactive/ui 0.11.0 → 0.13.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 (3) hide show
  1. package/dist/index.d.ts +207 -1
  2. package/dist/index.js +586 -55
  3. package/package.json +56 -56
package/dist/index.d.ts CHANGED
@@ -1716,6 +1716,212 @@ declare function timelineLayout({ items, lanes, start, end, today, calendar, min
1716
1716
  */
1717
1717
  declare function Timeline({ items, lanes, start, end, today, todayLabel, calendar, beforeLabel, afterLabel, emptyLabel, className, ...rest }: TimelineProps): react.JSX.Element;
1718
1718
 
1719
+ type KanbanTone = 'neutral' | 'accent' | 'success' | 'warning' | 'error' | 'info';
1720
+ interface KanbanColumn {
1721
+ id: string;
1722
+ label: ReactNode;
1723
+ /** The dot beside the label. */
1724
+ tone?: KanbanTone;
1725
+ /**
1726
+ * The count beside the label. Defaults to the cards in the column; pass one
1727
+ * when the column holds fewer than it counts (done cards hidden by a filter).
1728
+ */
1729
+ count?: number;
1730
+ /**
1731
+ * A line under the cards, e.g. "4 done hidden". A function gets the lane's
1732
+ * id (`'__default'` without lanes), for a figure that differs lane to lane.
1733
+ */
1734
+ note?: ReactNode | ((lane: string) => ReactNode);
1735
+ }
1736
+ interface KanbanLane {
1737
+ id: string;
1738
+ /** Names the swimlane; shown above its row of cells. */
1739
+ label: ReactNode;
1740
+ /** A line after the label, e.g. "3 open". */
1741
+ detail?: ReactNode;
1742
+ /** Any CSS color for the lane's left edge, e.g. a program's color. */
1743
+ color?: string;
1744
+ }
1745
+ /** What the board needs of a card. The rest of the caller's object is passed back untouched. */
1746
+ interface KanbanItem {
1747
+ id: string;
1748
+ /** The column it sits in. An id not in `columns` puts it in the first. */
1749
+ column: string;
1750
+ /** The swimlane it sits in; omitted, or not in `lanes`, the first. */
1751
+ lane?: string;
1752
+ }
1753
+ interface KanbanBoardProps<T extends KanbanItem> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
1754
+ columns: KanbanColumn[];
1755
+ /** In the order each cell lists them; the board does not sort. */
1756
+ items: T[];
1757
+ /** Swimlanes, top to bottom. Omitted, one unlabeled lane holds everything. */
1758
+ lanes?: KanbanLane[];
1759
+ /** The card's content. The board draws the card around it, so keep buttons and links out of it. */
1760
+ renderCard: (item: T) => ReactNode;
1761
+ /** Plain text for the card, read in announcements ("Moved {item} to {column}"). */
1762
+ itemLabel: (item: T) => string;
1763
+ /** Plain text for a column in announcements. Defaults to its label when that is a string, else its id. */
1764
+ columnLabel?: (column: KanbanColumn) => string;
1765
+ /**
1766
+ * A card was dropped on another column in its own lane, or moved with
1767
+ * Alt+Left / Alt+Right. Omitted, the board is read-only: nothing drags.
1768
+ * The board does not move the card itself; the caller updates `items`.
1769
+ */
1770
+ onMove?: (item: T, column: string) => void;
1771
+ /** Click or Enter / Space on a card. */
1772
+ onOpen?: (item: T) => void;
1773
+ /** Shown in an empty cell. */
1774
+ emptyLabel?: ReactNode;
1775
+ /** The announcement after a move. */
1776
+ movedLabel?: (item: string, column: string) => string;
1777
+ /** How narrow a column may get before the board scrolls sideways. */
1778
+ minColumnWidth?: string;
1779
+ }
1780
+ interface KanbanLayout<T extends KanbanItem> {
1781
+ lanes: {
1782
+ lane: KanbanLane;
1783
+ cells: {
1784
+ column: KanbanColumn;
1785
+ items: T[];
1786
+ }[];
1787
+ }[];
1788
+ /** Cards per column across every lane, before any `count` override. */
1789
+ counts: Record<string, number>;
1790
+ }
1791
+ /**
1792
+ * Puts each card in its lane and column. Pure, so the rules are testable: a
1793
+ * card naming a column or lane the board does not have goes to the first,
1794
+ * rather than disappearing from a board that claims to show everything.
1795
+ */
1796
+ declare function kanbanLayout<T extends KanbanItem>(columns: KanbanColumn[], items: T[], lanes?: KanbanLane[]): KanbanLayout<T>;
1797
+ /** The column `delta` steps from `from`, or null past either end. */
1798
+ declare function kanbanStep(columns: KanbanColumn[], from: string, delta: number): string | null;
1799
+ /**
1800
+ * Cards in columns, optionally in swimlanes: tasks by status, deals by stage.
1801
+ *
1802
+ * Cards drag between columns within their own lane (a lane is what the card
1803
+ * belongs to, which a drop on the board is not the place to change). Every
1804
+ * drag has a keyboard twin: Alt+Left / Alt+Right moves the focused card one
1805
+ * column, focus stays on it, and the move is announced. The board never moves
1806
+ * a card on its own; `onMove` tells the caller, and the caller's `items` say
1807
+ * where it is.
1808
+ */
1809
+ declare function KanbanBoard<T extends KanbanItem>({ columns, items, lanes, renderCard, itemLabel, columnLabel, onMove, onOpen, emptyLabel, movedLabel, minColumnWidth, className, style, ...rest }: KanbanBoardProps<T>): react.JSX.Element;
1810
+
1811
+ type CalendarView = 'month' | 'week';
1812
+ /** What the grid needs of an item. The rest of the caller's object is passed back untouched. */
1813
+ interface CalendarItem {
1814
+ id: string;
1815
+ /**
1816
+ * A `YYYY-MM-DD` day, an ISO instant, or a Date, read by its UTC day (a
1817
+ * Prisma `@db.Date` arrives as `2028-01-01T00:00:00.000Z`). An item without
1818
+ * a date that parses is left off the grid and counted in `undatedLabel`.
1819
+ */
1820
+ date: Date | string | null | undefined;
1821
+ /** Any CSS color for the chip, e.g. a program's color. Defaults to the accent. White text sits on it. */
1822
+ color?: string;
1823
+ /** Drawn dashed in its color and never moved: a milestone beside the tasks. */
1824
+ readOnly?: boolean;
1825
+ /** Ringed in the error color: overdue. */
1826
+ flagged?: boolean;
1827
+ /** Struck through and faded. */
1828
+ done?: boolean;
1829
+ }
1830
+ interface CalendarGridLabels {
1831
+ today: string;
1832
+ previous: (view: CalendarView) => string;
1833
+ next: (view: CalendarView) => string;
1834
+ month: string;
1835
+ week: string;
1836
+ view: string;
1837
+ /** The add button on a day. */
1838
+ add: (day: string) => string;
1839
+ more: (count: number) => string;
1840
+ less: string;
1841
+ moved: (item: string, day: string) => string;
1842
+ }
1843
+ interface CalendarGridProps<T extends CalendarItem> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
1844
+ /** In the order each day lists them; the grid does not sort. */
1845
+ items: T[];
1846
+ /** The chip's content. The grid draws the chip around it, so keep buttons and links out of it. */
1847
+ renderItem: (item: T) => ReactNode;
1848
+ /** Plain text for the item, read as its name and in announcements. */
1849
+ itemLabel: (item: T) => string;
1850
+ /** Controlled view; leave out and the grid keeps its own, starting at `defaultView`. */
1851
+ view?: CalendarView;
1852
+ defaultView?: CalendarView;
1853
+ onViewChange?: (view: CalendarView) => void;
1854
+ /**
1855
+ * Any day in the month or week shown, `YYYY-MM-DD`. Controlled the same
1856
+ * way as `view`; uncontrolled it starts at today.
1857
+ */
1858
+ anchor?: string;
1859
+ defaultAnchor?: string;
1860
+ onAnchorChange?: (anchor: string) => void;
1861
+ /** Which day is today. Defaults to the viewer's local day. */
1862
+ today?: Date | string;
1863
+ /** 1 (Monday, the default) or 0 (Sunday). */
1864
+ weekStartsOn?: 0 | 1;
1865
+ /**
1866
+ * An item was dropped on another day, or moved with Alt+Arrow keys (Left /
1867
+ * Right a day, Up / Down a week). Omitted, nothing drags. The grid does not
1868
+ * move the item itself; the caller updates `items`.
1869
+ */
1870
+ onMove?: (item: T, day: string) => void;
1871
+ /** Click or Enter / Space on an item. */
1872
+ onOpen?: (item: T) => void;
1873
+ /** The add button on a day, or a click on a day's empty space. Omitted, there is no add. */
1874
+ onAddDay?: (day: string) => void;
1875
+ /** How many items a month cell lists before "+N more". Week cells list every item. */
1876
+ maxPerDay?: number;
1877
+ /** Shown under the grid: what the colors mean. */
1878
+ legend?: ReactNode;
1879
+ /** A line in the footer when some items have no date, e.g. "3 tasks have no due date". */
1880
+ undatedLabel?: (count: number) => ReactNode;
1881
+ /** More controls at the end of the header, beside the view switch. */
1882
+ toolbar?: ReactNode;
1883
+ /** Month and weekday names and the date read out for a day. */
1884
+ locale?: string;
1885
+ labels?: Partial<CalendarGridLabels>;
1886
+ }
1887
+ /** An item's day as `YYYY-MM-DD` (by UTC day), or null when it has none. */
1888
+ declare function calendarDayKey(value: Date | string | null | undefined): string | null;
1889
+ /** `day` moved `n` days. */
1890
+ declare function calendarAddDays(day: string, n: number): string;
1891
+ /**
1892
+ * The days a view shows: six weeks from the week holding the 1st for a month
1893
+ * (always 42, so the grid does not jump in height between months), the
1894
+ * anchor's week for a week. `inRange` is false for the days of the months
1895
+ * either side.
1896
+ */
1897
+ declare function calendarDays(view: CalendarView, anchor: string, weekStartsOn?: 0 | 1): {
1898
+ day: string;
1899
+ inRange: boolean;
1900
+ }[];
1901
+ /** The anchor `delta` months or weeks on. A month lands on its 1st, so a 31st never skips a short month. */
1902
+ declare function calendarShift(view: CalendarView, anchor: string, delta: number): string;
1903
+ /** Items by day, in the order given, and how many had no day. */
1904
+ declare function calendarBuckets<T extends CalendarItem>(items: T[]): {
1905
+ byDay: Map<string, T[]>;
1906
+ undated: number;
1907
+ };
1908
+ /**
1909
+ * A month or week of days with items on them: tasks by due date, beside
1910
+ * read-only milestones.
1911
+ *
1912
+ * Days are calendar days, read by UTC day as `Timeline` reads them, so a due
1913
+ * date is the same square in Detroit and in Silao. Every drag has a keyboard
1914
+ * twin: Alt+Left / Alt+Right moves the focused item a day, Alt+Up / Alt+Down
1915
+ * a week, focus stays on it (the grid follows it into the next month), and
1916
+ * the move is announced. The grid never moves an item on its own; `onMove`
1917
+ * tells the caller, and the caller's `items` say where it is.
1918
+ */
1919
+ declare function CalendarGrid<T extends CalendarItem>({ items, renderItem, itemLabel, view: viewProp, defaultView, onViewChange, anchor: anchorProp, defaultAnchor, onAnchorChange, today: todayProp, weekStartsOn, onMove, onOpen, onAddDay, maxPerDay, legend, undatedLabel, toolbar, locale, labels: labelsProp, className, ...rest }: CalendarGridProps<T>): react.JSX.Element;
1920
+ /** A swatch for a legend under the grid, drawn the way the grid draws an item. */
1921
+ declare function CalendarLegendItem({ color, readOnly, flagged, children, }: Pick<CalendarItem, 'color' | 'readOnly' | 'flagged'> & {
1922
+ children: ReactNode;
1923
+ }): react.JSX.Element;
1924
+
1719
1925
  interface SortState<K extends string> {
1720
1926
  key: K;
1721
1927
  direction: SortDirection;
@@ -2085,4 +2291,4 @@ declare function formatNumber(value: number | null | undefined, decimals?: numbe
2085
2291
  */
2086
2292
  declare function formatPercent(value: number | null | undefined, decimals?: number): string;
2087
2293
 
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 };
2294
+ 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, CalendarGrid, type CalendarGridLabels, type CalendarGridProps, type CalendarItem, CalendarLegendItem, type CalendarView, 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, KanbanBoard, type KanbanBoardProps, type KanbanColumn, type KanbanItem, type KanbanLane, type KanbanLayout, type KanbanTone, 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, calendarAddDays, calendarBuckets, calendarDayKey, calendarDays, calendarShift, cn, formatCalendarDate, formatDate, formatDateTime, formatMoney, formatNumber, formatPercent, kanbanLayout, kanbanStep, timelineLayout, useDebouncedValue, useFieldControl, usePagedRows, usePersistentState, useSortState, useToast };
package/dist/index.js CHANGED
@@ -777,13 +777,13 @@ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
777
777
  var Slider = forwardRef10(
778
778
  function Slider2({ showValue = false, formatValue, className, ...rest }, ref) {
779
779
  const values = rest.value ?? rest.defaultValue ?? [rest.min ?? 0];
780
- const format = formatValue ?? ((v) => String(v));
780
+ const format2 = formatValue ?? ((v) => String(v));
781
781
  return /* @__PURE__ */ jsxs12("div", { className: cn("cfi-ui", "cfi-ui-slider-wrap", className), children: [
782
782
  /* @__PURE__ */ jsxs12(RadixSlider.Root, { ref, className: "cfi-ui-slider", ...rest, children: [
783
783
  /* @__PURE__ */ jsx12(RadixSlider.Track, { className: "cfi-ui-slider__track", children: /* @__PURE__ */ jsx12(RadixSlider.Range, { className: "cfi-ui-slider__range" }) }),
784
784
  values.map((_, i) => /* @__PURE__ */ jsx12(RadixSlider.Thumb, { className: "cfi-ui-slider__thumb" }, i))
785
785
  ] }),
786
- showValue && /* @__PURE__ */ jsx12("output", { className: "cfi-ui-slider__value", children: values.map(format).join(" \u2013 ") })
786
+ showValue && /* @__PURE__ */ jsx12("output", { className: "cfi-ui-slider__value", children: values.map(format2).join(" \u2013 ") })
787
787
  ] });
788
788
  }
789
789
  );
@@ -2489,10 +2489,531 @@ function Timeline({
2489
2489
  ] }) });
2490
2490
  }
2491
2491
 
2492
+ // src/KanbanBoard.tsx
2493
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState6 } from "react";
2494
+
2495
+ // src/KanbanBoard.css
2496
+ styleInject("@layer components {\n :where(.cfi-ui-kanban) {\n min-width: 0;\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-kanban__scroll {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-sm);\n overflow-x: auto;\n padding-bottom: var(--cfi-ui-space-xs);\n }\n .cfi-ui-kanban__grid {\n display: grid;\n grid-template-columns: repeat(var(--cfi-ui-kanban-columns, 4), minmax(var(--cfi-ui-kanban-min, 13rem), 1fr));\n gap: var(--cfi-ui-space-sm);\n }\n .cfi-ui-kanban__column-head {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n padding: 0 var(--cfi-ui-space-xs);\n font-weight: var(--cfi-ui-weight-semibold);\n }\n .cfi-ui-kanban__dot {\n flex: none;\n width: 0.625rem;\n height: 0.625rem;\n border-radius: var(--cfi-ui-radius-full);\n background: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-kanban__column-head[data-tone=accent] .cfi-ui-kanban__dot {\n background: var(--cfi-ui-accent);\n }\n .cfi-ui-kanban__column-head[data-tone=success] .cfi-ui-kanban__dot {\n background: var(--cfi-ui-success);\n }\n .cfi-ui-kanban__column-head[data-tone=warning] .cfi-ui-kanban__dot {\n background: var(--cfi-ui-warning);\n }\n .cfi-ui-kanban__column-head[data-tone=error] .cfi-ui-kanban__dot {\n background: var(--cfi-ui-error);\n }\n .cfi-ui-kanban__column-head[data-tone=info] .cfi-ui-kanban__dot {\n background: var(--cfi-ui-info);\n }\n .cfi-ui-kanban__column-label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ui-kanban__count {\n padding: 0 var(--cfi-ui-space-xs);\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-surface-muted);\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-medium);\n font-variant-numeric: tabular-nums;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-kanban__lane[data-labeled] {\n padding: var(--cfi-ui-space-sm);\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-xl);\n background: var(--cfi-ui-surface);\n box-shadow: inset 4px 0 0 var(--cfi-ui-kanban-lane-color, var(--cfi-ui-border-strong));\n }\n .cfi-ui-kanban__lane-head {\n display: flex;\n align-items: baseline;\n flex-wrap: wrap;\n gap: var(--cfi-ui-space-xs) var(--cfi-ui-space-sm);\n margin-bottom: var(--cfi-ui-space-sm);\n padding-left: var(--cfi-ui-space-xs);\n }\n .cfi-ui-kanban__lane-label {\n font-weight: var(--cfi-ui-weight-semibold);\n }\n .cfi-ui-kanban__lane-detail {\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-kanban__cell {\n min-height: 6rem;\n padding: var(--cfi-ui-space-xs);\n border-radius: var(--cfi-ui-radius-lg);\n background: var(--cfi-ui-surface-sunken);\n transition: background var(--cfi-ui-transition-fast), box-shadow var(--cfi-ui-transition-fast);\n }\n .cfi-ui-kanban__cell[data-over] {\n background: var(--cfi-ui-accent-soft);\n box-shadow: inset 0 0 0 2px var(--cfi-ui-accent);\n }\n .cfi-ui-kanban__cards {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n .cfi-ui-kanban__card {\n padding: var(--cfi-ui-space-sm);\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-lg);\n background: var(--cfi-ui-surface);\n box-shadow: var(--cfi-ui-shadow-xs);\n transition:\n border-color var(--cfi-ui-transition-fast),\n box-shadow var(--cfi-ui-transition-fast),\n opacity var(--cfi-ui-transition-fast);\n }\n .cfi-ui-kanban__card[role=button] {\n cursor: pointer;\n }\n .cfi-ui-kanban__card[data-movable] {\n cursor: grab;\n }\n .cfi-ui-kanban__card[data-movable]:active {\n cursor: grabbing;\n }\n .cfi-ui-kanban__card[role=button]:hover,\n .cfi-ui-kanban__card[data-movable]:hover {\n border-color: var(--cfi-ui-border-strong);\n box-shadow: var(--cfi-ui-shadow-md);\n }\n .cfi-ui-kanban__card: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-kanban__card[data-dragging] {\n opacity: 0.4;\n }\n .cfi-ui-kanban__note,\n .cfi-ui-kanban__empty {\n margin: 0;\n padding: var(--cfi-ui-space-xs);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-kanban__live {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ui-kanban__cell,\n .cfi-ui-kanban__card {\n transition: none;\n }\n }\n}\n");
2497
+
2498
+ // src/KanbanBoard.tsx
2499
+ import { jsx as jsx29, jsxs as jsxs29 } from "react/jsx-runtime";
2500
+ var DEFAULT_LANE2 = { id: "__default", label: null };
2501
+ function kanbanLayout(columns, items, lanes) {
2502
+ const laneList = lanes && lanes.length > 0 ? lanes : [DEFAULT_LANE2];
2503
+ const columnIds = new Set(columns.map((c) => c.id));
2504
+ const laneIds = new Set(laneList.map((l) => l.id));
2505
+ const counts = Object.fromEntries(columns.map((c) => [c.id, 0]));
2506
+ const cells = /* @__PURE__ */ new Map();
2507
+ for (const item of items) {
2508
+ const column = columnIds.has(item.column) ? item.column : columns[0]?.id;
2509
+ if (column === void 0) continue;
2510
+ const lane = item.lane !== void 0 && laneIds.has(item.lane) ? item.lane : laneList[0].id;
2511
+ const key = `${lane}\0${column}`;
2512
+ const list = cells.get(key);
2513
+ if (list) list.push(item);
2514
+ else cells.set(key, [item]);
2515
+ counts[column] += 1;
2516
+ }
2517
+ return {
2518
+ lanes: laneList.map((lane) => ({
2519
+ lane,
2520
+ cells: columns.map((column) => ({ column, items: cells.get(`${lane.id}\0${column.id}`) ?? [] }))
2521
+ })),
2522
+ counts
2523
+ };
2524
+ }
2525
+ function kanbanStep(columns, from, delta) {
2526
+ const i = columns.findIndex((c) => c.id === from);
2527
+ const next = columns[(i < 0 ? 0 : i) + delta];
2528
+ return next ? next.id : null;
2529
+ }
2530
+ function KanbanBoard({
2531
+ columns,
2532
+ items,
2533
+ lanes,
2534
+ renderCard,
2535
+ itemLabel,
2536
+ columnLabel = (c) => typeof c.label === "string" ? c.label : c.id,
2537
+ onMove,
2538
+ onOpen,
2539
+ emptyLabel = "Nothing here",
2540
+ movedLabel = (item, column) => `Moved ${item} to ${column}`,
2541
+ minColumnWidth = "13rem",
2542
+ className,
2543
+ style,
2544
+ ...rest
2545
+ }) {
2546
+ const layout = kanbanLayout(columns, items, lanes);
2547
+ const [drag, setDrag] = useState6(null);
2548
+ const [over, setOver] = useState6(null);
2549
+ const [announcement, setAnnouncement] = useState6("");
2550
+ const rootRef = useRef4(null);
2551
+ const refocus = useRef4(null);
2552
+ useEffect4(() => {
2553
+ const id = refocus.current;
2554
+ if (!id || !rootRef.current) return;
2555
+ const card = rootRef.current.querySelector(`[data-kanban-card="${CSS.escape(id)}"]`);
2556
+ if (card) {
2557
+ card.focus();
2558
+ refocus.current = null;
2559
+ }
2560
+ });
2561
+ const byId = new Map(items.map((i) => [i.id, i]));
2562
+ const columnById = new Map(columns.map((c) => [c.id, c]));
2563
+ const move = (item, column, viaKeyboard) => {
2564
+ if (!onMove || column === item.column) return;
2565
+ if (viaKeyboard) refocus.current = item.id;
2566
+ onMove(item, column);
2567
+ const target = columnById.get(column);
2568
+ setAnnouncement(movedLabel(itemLabel(item), target ? columnLabel(target) : column));
2569
+ };
2570
+ const endDrag = () => {
2571
+ setDrag(null);
2572
+ setOver(null);
2573
+ };
2574
+ return /* @__PURE__ */ jsxs29(
2575
+ "div",
2576
+ {
2577
+ ref: rootRef,
2578
+ className: cn("cfi-ui", "cfi-ui-kanban", className),
2579
+ style: { "--cfi-ui-kanban-columns": columns.length, "--cfi-ui-kanban-min": minColumnWidth, ...style },
2580
+ ...rest,
2581
+ children: [
2582
+ /* @__PURE__ */ jsxs29("div", { className: "cfi-ui-kanban__scroll", children: [
2583
+ /* @__PURE__ */ jsx29("div", { className: "cfi-ui-kanban__grid cfi-ui-kanban__head", children: columns.map((c) => /* @__PURE__ */ jsxs29("div", { className: "cfi-ui-kanban__column-head", "data-tone": c.tone ?? "neutral", children: [
2584
+ /* @__PURE__ */ jsx29("span", { className: "cfi-ui-kanban__dot", "aria-hidden": "true" }),
2585
+ /* @__PURE__ */ jsx29("span", { className: "cfi-ui-kanban__column-label", children: c.label }),
2586
+ /* @__PURE__ */ jsx29("span", { className: "cfi-ui-kanban__count", children: c.count ?? layout.counts[c.id] })
2587
+ ] }, c.id)) }),
2588
+ layout.lanes.map(({ lane, cells }) => /* @__PURE__ */ jsxs29(
2589
+ "section",
2590
+ {
2591
+ className: "cfi-ui-kanban__lane",
2592
+ "data-labeled": lane.label ? true : void 0,
2593
+ style: lane.color ? { "--cfi-ui-kanban-lane-color": lane.color } : void 0,
2594
+ children: [
2595
+ lane.label ? /* @__PURE__ */ jsxs29("div", { className: "cfi-ui-kanban__lane-head", children: [
2596
+ /* @__PURE__ */ jsx29("span", { className: "cfi-ui-kanban__lane-label", children: lane.label }),
2597
+ lane.detail ? /* @__PURE__ */ jsx29("span", { className: "cfi-ui-kanban__lane-detail", children: lane.detail }) : null
2598
+ ] }) : null,
2599
+ /* @__PURE__ */ jsx29("div", { className: "cfi-ui-kanban__grid", children: cells.map(({ column, items: cellItems }) => {
2600
+ const zone = `${lane.id}\0${column.id}`;
2601
+ const accepts = drag !== null && drag.lane === lane.id && drag.column !== column.id;
2602
+ const note = typeof column.note === "function" ? column.note(lane.id) : column.note;
2603
+ return /* @__PURE__ */ jsxs29(
2604
+ "div",
2605
+ {
2606
+ className: "cfi-ui-kanban__cell",
2607
+ "data-over": over === zone || void 0,
2608
+ onDragOver: (e) => {
2609
+ if (!accepts) return;
2610
+ e.preventDefault();
2611
+ e.dataTransfer.dropEffect = "move";
2612
+ if (over !== zone) setOver(zone);
2613
+ },
2614
+ onDragLeave: (e) => {
2615
+ if (!e.currentTarget.contains(e.relatedTarget)) setOver((o) => o === zone ? null : o);
2616
+ },
2617
+ onDrop: (e) => {
2618
+ e.preventDefault();
2619
+ const item = drag ? byId.get(drag.id) : void 0;
2620
+ if (item && accepts) move(item, column.id, false);
2621
+ endDrag();
2622
+ },
2623
+ children: [
2624
+ /* @__PURE__ */ jsx29("ul", { className: "cfi-ui-kanban__cards", "aria-label": columnLabel(column), children: cellItems.map((item) => /* @__PURE__ */ jsx29("li", { children: /* @__PURE__ */ jsx29(
2625
+ "div",
2626
+ {
2627
+ className: "cfi-ui-kanban__card",
2628
+ "data-kanban-card": item.id,
2629
+ "data-dragging": drag?.id === item.id || void 0,
2630
+ "data-movable": onMove ? true : void 0,
2631
+ role: onOpen ? "button" : void 0,
2632
+ tabIndex: onOpen || onMove ? 0 : void 0,
2633
+ "aria-keyshortcuts": onMove ? "Alt+ArrowLeft Alt+ArrowRight" : void 0,
2634
+ draggable: onMove ? true : void 0,
2635
+ onDragStart: (e) => {
2636
+ e.dataTransfer.effectAllowed = "move";
2637
+ e.dataTransfer.setData("text/plain", item.id);
2638
+ setTimeout(() => setDrag({ id: item.id, lane: lane.id, column: column.id }), 0);
2639
+ },
2640
+ onDragEnd: endDrag,
2641
+ onClick: onOpen ? () => onOpen(item) : void 0,
2642
+ onKeyDown: (e) => {
2643
+ if (e.target !== e.currentTarget) return;
2644
+ if (onMove && e.altKey && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
2645
+ e.preventDefault();
2646
+ const to = kanbanStep(columns, column.id, e.key === "ArrowLeft" ? -1 : 1);
2647
+ if (to) move(item, to, true);
2648
+ return;
2649
+ }
2650
+ if (onOpen && (e.key === "Enter" || e.key === " ")) {
2651
+ e.preventDefault();
2652
+ onOpen(item);
2653
+ }
2654
+ },
2655
+ children: renderCard(item)
2656
+ }
2657
+ ) }, item.id)) }),
2658
+ note ? /* @__PURE__ */ jsx29("p", { className: "cfi-ui-kanban__note", children: note }) : null,
2659
+ cellItems.length === 0 && !note ? /* @__PURE__ */ jsx29("p", { className: "cfi-ui-kanban__empty", children: emptyLabel }) : null
2660
+ ]
2661
+ },
2662
+ column.id
2663
+ );
2664
+ }) })
2665
+ ]
2666
+ },
2667
+ lane.id
2668
+ ))
2669
+ ] }),
2670
+ /* @__PURE__ */ jsx29("div", { className: "cfi-ui-kanban__live", role: "status", "aria-live": "polite", children: announcement })
2671
+ ]
2672
+ }
2673
+ );
2674
+ }
2675
+
2676
+ // src/CalendarGrid.tsx
2677
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
2678
+ import { ChevronLeft, ChevronRight, Plus as Plus2 } from "lucide-react";
2679
+
2680
+ // src/CalendarGrid.css
2681
+ styleInject("@layer components {\n :where(.cfi-ui-calendar) {\n min-width: 0;\n overflow: hidden;\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-xl);\n background: var(--cfi-ui-surface);\n box-shadow: var(--cfi-ui-shadow-xs);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-calendar__header {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-ui-space-xs);\n padding: var(--cfi-ui-space-sm) var(--cfi-ui-space-md);\n border-bottom: 1px solid var(--cfi-ui-border);\n }\n .cfi-ui-calendar__title {\n margin: 0 0 0 var(--cfi-ui-space-xs);\n font-size: var(--cfi-ui-text-lg);\n font-weight: var(--cfi-ui-weight-semibold);\n line-height: var(--cfi-ui-leading-tight);\n }\n .cfi-ui-calendar__tools {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n margin-left: auto;\n }\n .cfi-ui-calendar__scroll {\n overflow-x: auto;\n }\n .cfi-ui-calendar__weekdays,\n .cfi-ui-calendar__grid {\n display: grid;\n grid-template-columns: repeat(7, minmax(7rem, 1fr));\n }\n .cfi-ui-calendar__weekdays {\n border-bottom: 1px solid var(--cfi-ui-border);\n background: var(--cfi-ui-surface-sunken);\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-semibold);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-calendar__weekdays > div {\n padding: var(--cfi-ui-space-2xs) var(--cfi-ui-space-sm);\n }\n .cfi-ui-calendar__day {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-3xs);\n min-width: 0;\n min-height: 7rem;\n padding: var(--cfi-ui-space-2xs);\n border-right: 1px solid var(--cfi-ui-border);\n border-bottom: 1px solid var(--cfi-ui-border);\n transition: background var(--cfi-ui-transition-fast), box-shadow var(--cfi-ui-transition-fast);\n }\n .cfi-ui-calendar__day:nth-child(7n) {\n border-right: 0;\n }\n .cfi-ui-calendar[data-view=month] .cfi-ui-calendar__day:nth-last-child(-n+7),\n .cfi-ui-calendar[data-view=week] .cfi-ui-calendar__day {\n border-bottom: 0;\n }\n .cfi-ui-calendar[data-view=week] .cfi-ui-calendar__day {\n min-height: 20rem;\n }\n .cfi-ui-calendar__day[data-outside] {\n background: var(--cfi-ui-surface-sunken);\n }\n .cfi-ui-calendar__day[data-addable] {\n cursor: pointer;\n }\n .cfi-ui-calendar__day[data-addable]:hover {\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-calendar__day[data-over] {\n background: var(--cfi-ui-accent-soft);\n box-shadow: inset 0 0 0 2px var(--cfi-ui-accent);\n }\n .cfi-ui-calendar__day-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .cfi-ui-calendar__date {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.5rem;\n height: 1.5rem;\n border-radius: var(--cfi-ui-radius-full);\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-medium);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ui-calendar__day[data-outside] .cfi-ui-calendar__date {\n color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-calendar__day[data-today] .cfi-ui-calendar__date {\n background: var(--cfi-ui-accent);\n color: var(--cfi-ui-accent-fg);\n font-weight: var(--cfi-ui-weight-semibold);\n }\n .cfi-ui-calendar__add {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n padding: 0;\n border: 0;\n border-radius: var(--cfi-ui-radius-sm);\n background: none;\n color: var(--cfi-ui-accent-text);\n cursor: pointer;\n opacity: 0;\n }\n .cfi-ui-calendar__add svg {\n width: 0.875rem;\n height: 0.875rem;\n }\n .cfi-ui-calendar__day:hover .cfi-ui-calendar__add,\n .cfi-ui-calendar__add:focus-visible {\n opacity: 1;\n }\n .cfi-ui-calendar__add:hover {\n background: var(--cfi-ui-accent-soft);\n }\n .cfi-ui-calendar__add:focus-visible,\n .cfi-ui-calendar__more: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-calendar__items {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-3xs);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n .cfi-ui-calendar__item {\n overflow: hidden;\n padding: 0.125rem var(--cfi-ui-space-2xs);\n border: 1px solid transparent;\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-calendar-item, var(--cfi-ui-accent));\n color: var(--cfi-ui-content-inverse);\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-medium);\n line-height: var(--cfi-ui-leading-snug);\n text-overflow: ellipsis;\n white-space: nowrap;\n box-shadow: var(--cfi-ui-shadow-xs);\n transition: filter var(--cfi-ui-transition-fast), opacity var(--cfi-ui-transition-fast);\n }\n .cfi-ui-calendar__item[role=button] {\n cursor: pointer;\n }\n .cfi-ui-calendar__item[data-movable] {\n cursor: grab;\n }\n .cfi-ui-calendar__item[data-movable]:active {\n cursor: grabbing;\n }\n .cfi-ui-calendar__item[role=button]:hover {\n filter: brightness(1.1);\n }\n .cfi-ui-calendar__item: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-calendar__item[data-read-only],\n .cfi-ui-calendar__swatch[data-read-only] {\n border: 1px dashed var(--cfi-ui-calendar-item, var(--cfi-ui-accent));\n background: var(--cfi-ui-surface);\n color: var(--cfi-ui-calendar-item, var(--cfi-ui-accent-text));\n box-shadow: none;\n }\n .cfi-ui-calendar__item[data-read-only][role=button]:hover {\n filter: none;\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-calendar__item[data-flagged],\n .cfi-ui-calendar__swatch[data-flagged] {\n box-shadow: 0 0 0 1px var(--cfi-ui-surface), 0 0 0 3px var(--cfi-ui-error);\n }\n .cfi-ui-calendar__item[data-done] {\n opacity: 0.6;\n text-decoration: line-through;\n }\n .cfi-ui-calendar__item[data-dragging] {\n opacity: 0.4;\n }\n .cfi-ui-calendar__more {\n align-self: flex-start;\n padding: 0 var(--cfi-ui-space-2xs);\n border: 0;\n border-radius: var(--cfi-ui-radius-sm);\n background: none;\n font: inherit;\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-medium);\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n }\n .cfi-ui-calendar__more:hover {\n background: var(--cfi-ui-surface-hover);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-calendar__footer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-ui-space-2xs) var(--cfi-ui-space-md);\n padding: var(--cfi-ui-space-sm) var(--cfi-ui-space-md);\n border-top: 1px solid var(--cfi-ui-border);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-calendar__undated {\n margin-left: auto;\n }\n .cfi-ui-calendar__legend-item {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-ui-space-2xs);\n }\n .cfi-ui-calendar__swatch {\n flex: none;\n width: 1.25rem;\n height: 0.75rem;\n border: 1px solid transparent;\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-calendar-item, var(--cfi-ui-accent));\n }\n .cfi-ui-calendar__live {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ui-calendar__day,\n .cfi-ui-calendar__item {\n transition: none;\n }\n }\n}\n");
2682
+
2683
+ // src/CalendarGrid.tsx
2684
+ import { jsx as jsx30, jsxs as jsxs30 } from "react/jsx-runtime";
2685
+ var DEFAULT_LABELS = {
2686
+ today: "Today",
2687
+ previous: (view) => `Previous ${view}`,
2688
+ next: (view) => `Next ${view}`,
2689
+ month: "Month",
2690
+ week: "Week",
2691
+ view: "Calendar view",
2692
+ add: (day) => `Add on ${day}`,
2693
+ more: (count) => `+${count} more`,
2694
+ less: "Show fewer",
2695
+ moved: (item, day) => `Moved ${item} to ${day}`
2696
+ };
2697
+ var DAY2 = 864e5;
2698
+ function ymd2(t) {
2699
+ return new Date(t).toISOString().slice(0, 10);
2700
+ }
2701
+ function calendarDayKey(value) {
2702
+ const t = timelineTime(value, true);
2703
+ return t === null ? null : ymd2(t);
2704
+ }
2705
+ function calendarAddDays(day, n) {
2706
+ return ymd2(timelineTime(day, true) + n * DAY2);
2707
+ }
2708
+ function calendarDays(view, anchor, weekStartsOn = 1) {
2709
+ const a = new Date(timelineTime(anchor, true));
2710
+ const from = view === "month" ? Date.UTC(a.getUTCFullYear(), a.getUTCMonth(), 1) : a.getTime();
2711
+ const back = (new Date(from).getUTCDay() - weekStartsOn + 7) % 7;
2712
+ const start = from - back * DAY2;
2713
+ const month = a.getUTCMonth();
2714
+ return Array.from({ length: view === "month" ? 42 : 7 }, (_, i) => {
2715
+ const t = start + i * DAY2;
2716
+ return { day: ymd2(t), inRange: view === "week" || new Date(t).getUTCMonth() === month };
2717
+ });
2718
+ }
2719
+ function calendarShift(view, anchor, delta) {
2720
+ if (view === "week") return calendarAddDays(anchor, delta * 7);
2721
+ const a = new Date(timelineTime(anchor, true));
2722
+ return ymd2(Date.UTC(a.getUTCFullYear(), a.getUTCMonth() + delta, 1));
2723
+ }
2724
+ function calendarBuckets(items) {
2725
+ const byDay = /* @__PURE__ */ new Map();
2726
+ let undated = 0;
2727
+ for (const item of items) {
2728
+ const day = calendarDayKey(item.date);
2729
+ if (day === null) {
2730
+ undated += 1;
2731
+ continue;
2732
+ }
2733
+ const list = byDay.get(day);
2734
+ if (list) list.push(item);
2735
+ else byDay.set(day, [item]);
2736
+ }
2737
+ return { byDay, undated };
2738
+ }
2739
+ function localToday() {
2740
+ const now = /* @__PURE__ */ new Date();
2741
+ return ymd2(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
2742
+ }
2743
+ function format(day, locale, options) {
2744
+ return new Date(timelineTime(day, true)).toLocaleDateString(locale, { ...options, timeZone: "UTC" });
2745
+ }
2746
+ function CalendarGrid({
2747
+ items,
2748
+ renderItem,
2749
+ itemLabel,
2750
+ view: viewProp,
2751
+ defaultView = "month",
2752
+ onViewChange,
2753
+ anchor: anchorProp,
2754
+ defaultAnchor,
2755
+ onAnchorChange,
2756
+ today: todayProp,
2757
+ weekStartsOn = 1,
2758
+ onMove,
2759
+ onOpen,
2760
+ onAddDay,
2761
+ maxPerDay = 3,
2762
+ legend,
2763
+ undatedLabel,
2764
+ toolbar,
2765
+ locale = "en-US",
2766
+ labels: labelsProp,
2767
+ className,
2768
+ ...rest
2769
+ }) {
2770
+ const labels = { ...DEFAULT_LABELS, ...labelsProp };
2771
+ const today = todayProp !== void 0 && calendarDayKey(todayProp) || localToday();
2772
+ const [ownView, setOwnView] = useState7(defaultView);
2773
+ const [ownAnchor, setOwnAnchor] = useState7(() => defaultAnchor && calendarDayKey(defaultAnchor) || today);
2774
+ const view = viewProp ?? ownView;
2775
+ const anchor = anchorProp && calendarDayKey(anchorProp) || ownAnchor;
2776
+ const setView = (next) => {
2777
+ if (viewProp === void 0) setOwnView(next);
2778
+ onViewChange?.(next);
2779
+ };
2780
+ const setAnchor = (next) => {
2781
+ if (anchorProp === void 0) setOwnAnchor(next);
2782
+ onAnchorChange?.(next);
2783
+ };
2784
+ const [drag, setDrag] = useState7(null);
2785
+ const [over, setOver] = useState7(null);
2786
+ const [expanded, setExpanded] = useState7(/* @__PURE__ */ new Set());
2787
+ const [announcement, setAnnouncement] = useState7("");
2788
+ const rootRef = useRef5(null);
2789
+ const refocus = useRef5(null);
2790
+ useEffect5(() => {
2791
+ const id = refocus.current;
2792
+ if (!id || !rootRef.current) return;
2793
+ const chip = rootRef.current.querySelector(`[data-calendar-item="${CSS.escape(id)}"]`);
2794
+ if (chip) {
2795
+ chip.focus();
2796
+ refocus.current = null;
2797
+ }
2798
+ });
2799
+ const days = calendarDays(view, anchor, weekStartsOn);
2800
+ const { byDay, undated } = calendarBuckets(items);
2801
+ const byId = new Map(items.map((i) => [i.id, i]));
2802
+ const shown = new Set(days.map((d) => d.day));
2803
+ const showsToday = shown.has(today) && (view === "week" || days.find((d) => d.day === today)?.inRange);
2804
+ const longDay = (day) => format(day, locale, { weekday: "long", month: "long", day: "numeric", year: "numeric" });
2805
+ const title = view === "month" ? format(anchor, locale, { month: "long", year: "numeric" }) : `${format(days[0].day, locale, { month: "short", day: "numeric" })} \u2013 ${format(days[6].day, locale, {
2806
+ month: "short",
2807
+ day: "numeric",
2808
+ year: "numeric"
2809
+ })}`;
2810
+ const move = (item, day, viaKeyboard) => {
2811
+ if (!onMove || item.readOnly || calendarDayKey(item.date) === day) return;
2812
+ if (viaKeyboard) {
2813
+ refocus.current = item.id;
2814
+ if (!shown.has(day) || view === "month" && !days.find((d) => d.day === day)?.inRange) setAnchor(day);
2815
+ }
2816
+ onMove(item, day);
2817
+ setAnnouncement(labels.moved(itemLabel(item), longDay(day)));
2818
+ };
2819
+ const endDrag = () => {
2820
+ setDrag(null);
2821
+ setOver(null);
2822
+ };
2823
+ return /* @__PURE__ */ jsxs30("div", { ref: rootRef, className: cn("cfi-ui", "cfi-ui-calendar", className), "data-view": view, ...rest, children: [
2824
+ /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-calendar__header", children: [
2825
+ /* @__PURE__ */ jsx30(Button, { size: "sm", variant: "secondary", onClick: () => setAnchor(today), disabled: showsToday || void 0, children: labels.today }),
2826
+ /* @__PURE__ */ jsx30(
2827
+ IconButton,
2828
+ {
2829
+ size: "sm",
2830
+ variant: "ghost",
2831
+ icon: /* @__PURE__ */ jsx30(ChevronLeft, { "aria-hidden": "true" }),
2832
+ label: labels.previous(view),
2833
+ onClick: () => setAnchor(calendarShift(view, anchor, -1))
2834
+ }
2835
+ ),
2836
+ /* @__PURE__ */ jsx30(
2837
+ IconButton,
2838
+ {
2839
+ size: "sm",
2840
+ variant: "ghost",
2841
+ icon: /* @__PURE__ */ jsx30(ChevronRight, { "aria-hidden": "true" }),
2842
+ label: labels.next(view),
2843
+ onClick: () => setAnchor(calendarShift(view, anchor, 1))
2844
+ }
2845
+ ),
2846
+ /* @__PURE__ */ jsx30("h2", { className: "cfi-ui-calendar__title", "aria-live": "polite", children: title }),
2847
+ /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-calendar__tools", children: [
2848
+ toolbar,
2849
+ /* @__PURE__ */ jsx30(
2850
+ ToggleGroup,
2851
+ {
2852
+ size: "sm",
2853
+ "aria-label": labels.view,
2854
+ value: view,
2855
+ onValueChange: setView,
2856
+ options: [
2857
+ { value: "month", label: labels.month },
2858
+ { value: "week", label: labels.week }
2859
+ ]
2860
+ }
2861
+ )
2862
+ ] })
2863
+ ] }),
2864
+ /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-calendar__scroll", children: [
2865
+ /* @__PURE__ */ jsx30("div", { className: "cfi-ui-calendar__weekdays", "aria-hidden": "true", children: days.slice(0, 7).map(({ day }) => /* @__PURE__ */ jsx30("div", { children: format(day, locale, { weekday: "short" }) }, day)) }),
2866
+ /* @__PURE__ */ jsx30("div", { className: "cfi-ui-calendar__grid", children: days.map(({ day, inRange }) => {
2867
+ const dayItems = byDay.get(day) ?? [];
2868
+ const capped = view === "month" && !expanded.has(day) && dayItems.length > maxPerDay + 1;
2869
+ const visible = capped ? dayItems.slice(0, maxPerDay) : dayItems;
2870
+ const accepts = drag !== null && calendarDayKey(byId.get(drag)?.date) !== day;
2871
+ const name = longDay(day);
2872
+ return /* @__PURE__ */ jsxs30(
2873
+ "div",
2874
+ {
2875
+ className: "cfi-ui-calendar__day",
2876
+ "data-outside": !inRange || void 0,
2877
+ "data-today": day === today || void 0,
2878
+ "data-over": over === day || void 0,
2879
+ "data-addable": onAddDay ? true : void 0,
2880
+ onClick: (e) => {
2881
+ if (onAddDay && (e.target === e.currentTarget || e.target.dataset.calendarEmpty)) onAddDay(day);
2882
+ },
2883
+ onDragOver: (e) => {
2884
+ if (!accepts) return;
2885
+ e.preventDefault();
2886
+ e.dataTransfer.dropEffect = "move";
2887
+ if (over !== day) setOver(day);
2888
+ },
2889
+ onDragLeave: (e) => {
2890
+ if (!e.currentTarget.contains(e.relatedTarget)) setOver((o) => o === day ? null : o);
2891
+ },
2892
+ onDrop: (e) => {
2893
+ e.preventDefault();
2894
+ const item = drag ? byId.get(drag) : void 0;
2895
+ if (item && accepts) move(item, day, false);
2896
+ endDrag();
2897
+ },
2898
+ children: [
2899
+ /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-calendar__day-head", "data-calendar-empty": "1", children: [
2900
+ /* @__PURE__ */ jsx30("span", { className: "cfi-ui-calendar__date", "data-calendar-empty": "1", "aria-current": day === today ? "date" : void 0, children: Number(day.slice(8)) }),
2901
+ onAddDay ? /* @__PURE__ */ jsx30(
2902
+ "button",
2903
+ {
2904
+ type: "button",
2905
+ className: "cfi-ui-calendar__add",
2906
+ "aria-label": labels.add(name),
2907
+ title: labels.add(name),
2908
+ onClick: (e) => {
2909
+ e.stopPropagation();
2910
+ onAddDay(day);
2911
+ },
2912
+ children: /* @__PURE__ */ jsx30(Plus2, { "aria-hidden": "true" })
2913
+ }
2914
+ ) : null
2915
+ ] }),
2916
+ /* @__PURE__ */ jsx30("ul", { className: "cfi-ui-calendar__items", "aria-label": name, "data-calendar-empty": "1", children: visible.map((item) => {
2917
+ const movable = onMove && !item.readOnly;
2918
+ return /* @__PURE__ */ jsx30("li", { children: /* @__PURE__ */ jsx30(
2919
+ "div",
2920
+ {
2921
+ className: "cfi-ui-calendar__item",
2922
+ "data-calendar-item": item.id,
2923
+ "data-read-only": item.readOnly || void 0,
2924
+ "data-flagged": item.flagged || void 0,
2925
+ "data-done": item.done || void 0,
2926
+ "data-dragging": drag === item.id || void 0,
2927
+ "data-movable": movable || void 0,
2928
+ style: item.color ? { "--cfi-ui-calendar-item": item.color } : void 0,
2929
+ title: itemLabel(item),
2930
+ role: onOpen ? "button" : void 0,
2931
+ tabIndex: onOpen || movable ? 0 : void 0,
2932
+ "aria-keyshortcuts": movable ? "Alt+ArrowLeft Alt+ArrowRight Alt+ArrowUp Alt+ArrowDown" : void 0,
2933
+ draggable: movable || void 0,
2934
+ onDragStart: (e) => {
2935
+ e.stopPropagation();
2936
+ e.dataTransfer.effectAllowed = "move";
2937
+ e.dataTransfer.setData("text/plain", item.id);
2938
+ setTimeout(() => setDrag(item.id), 0);
2939
+ },
2940
+ onDragEnd: endDrag,
2941
+ onClick: (e) => {
2942
+ e.stopPropagation();
2943
+ onOpen?.(item);
2944
+ },
2945
+ onKeyDown: (e) => {
2946
+ if (e.target !== e.currentTarget) return;
2947
+ const step = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[e.key];
2948
+ if (movable && e.altKey && step !== void 0) {
2949
+ e.preventDefault();
2950
+ move(item, calendarAddDays(day, step), true);
2951
+ return;
2952
+ }
2953
+ if (onOpen && (e.key === "Enter" || e.key === " ")) {
2954
+ e.preventDefault();
2955
+ onOpen(item);
2956
+ }
2957
+ },
2958
+ children: renderItem(item)
2959
+ }
2960
+ ) }, item.id);
2961
+ }) }),
2962
+ capped || expanded.has(day) ? /* @__PURE__ */ jsx30(
2963
+ "button",
2964
+ {
2965
+ type: "button",
2966
+ className: "cfi-ui-calendar__more",
2967
+ onClick: (e) => {
2968
+ e.stopPropagation();
2969
+ setExpanded((all) => {
2970
+ const next = new Set(all);
2971
+ if (next.has(day)) next.delete(day);
2972
+ else next.add(day);
2973
+ return next;
2974
+ });
2975
+ },
2976
+ children: capped ? labels.more(dayItems.length - visible.length) : labels.less
2977
+ }
2978
+ ) : null
2979
+ ]
2980
+ },
2981
+ day
2982
+ );
2983
+ }) })
2984
+ ] }),
2985
+ legend || undated > 0 && undatedLabel ? /* @__PURE__ */ jsxs30("div", { className: "cfi-ui-calendar__footer", children: [
2986
+ legend,
2987
+ undated > 0 && undatedLabel ? /* @__PURE__ */ jsx30("span", { className: "cfi-ui-calendar__undated", children: undatedLabel(undated) }) : null
2988
+ ] }) : null,
2989
+ /* @__PURE__ */ jsx30("div", { className: "cfi-ui-calendar__live", role: "status", "aria-live": "polite", children: announcement })
2990
+ ] });
2991
+ }
2992
+ function CalendarLegendItem({
2993
+ color,
2994
+ readOnly,
2995
+ flagged,
2996
+ children
2997
+ }) {
2998
+ return /* @__PURE__ */ jsxs30("span", { className: "cfi-ui-calendar__legend-item", children: [
2999
+ /* @__PURE__ */ jsx30(
3000
+ "span",
3001
+ {
3002
+ className: "cfi-ui-calendar__swatch",
3003
+ "data-read-only": readOnly || void 0,
3004
+ "data-flagged": flagged || void 0,
3005
+ style: color ? { "--cfi-ui-calendar-item": color } : void 0,
3006
+ "aria-hidden": "true"
3007
+ }
3008
+ ),
3009
+ children
3010
+ ] });
3011
+ }
3012
+
2492
3013
  // src/useSortState.ts
2493
- import { useCallback as useCallback3, useMemo as useMemo5, useState as useState6 } from "react";
3014
+ import { useCallback as useCallback3, useMemo as useMemo5, useState as useState8 } from "react";
2494
3015
  function useSortState(initialKey, initialDirection = "asc") {
2495
- const [sort, setSort] = useState6({ key: initialKey, direction: initialDirection });
3016
+ const [sort, setSort] = useState8({ key: initialKey, direction: initialDirection });
2496
3017
  const toggle = useCallback3((key) => {
2497
3018
  setSort(
2498
3019
  (current) => current.key === key ? { key, direction: current.direction === "asc" ? "desc" : "asc" } : { key, direction: "asc" }
@@ -2529,21 +3050,21 @@ function useSortState(initialKey, initialDirection = "asc") {
2529
3050
  }
2530
3051
 
2531
3052
  // src/SearchInput.tsx
2532
- import { forwardRef as forwardRef18, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
3053
+ import { forwardRef as forwardRef18, useEffect as useEffect6, useRef as useRef6, useState as useState9 } from "react";
2533
3054
  import { Search as Search2, X as X6 } from "lucide-react";
2534
3055
 
2535
3056
  // src/SearchInput.css
2536
3057
  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");
2537
3058
 
2538
3059
  // src/SearchInput.tsx
2539
- import { jsx as jsx29 } from "react/jsx-runtime";
3060
+ import { jsx as jsx31 } from "react/jsx-runtime";
2540
3061
  var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay = 250, placeholder = "Search\u2026", clearLabel = "Clear search", ...rest }, ref) {
2541
- const [local, setLocal] = useState7(value);
2542
- const timer = useRef4(void 0);
2543
- const latest = useRef4(onChange);
3062
+ const [local, setLocal] = useState9(value);
3063
+ const timer = useRef6(void 0);
3064
+ const latest = useRef6(onChange);
2544
3065
  latest.current = onChange;
2545
- useEffect4(() => setLocal(value), [value]);
2546
- useEffect4(() => () => clearTimeout(timer.current), []);
3066
+ useEffect6(() => setLocal(value), [value]);
3067
+ useEffect6(() => () => clearTimeout(timer.current), []);
2547
3068
  const push = (next) => {
2548
3069
  setLocal(next);
2549
3070
  clearTimeout(timer.current);
@@ -2555,7 +3076,7 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
2555
3076
  setLocal("");
2556
3077
  latest.current("");
2557
3078
  };
2558
- return /* @__PURE__ */ jsx29(
3079
+ return /* @__PURE__ */ jsx31(
2559
3080
  Input,
2560
3081
  {
2561
3082
  ...rest,
@@ -2564,20 +3085,20 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
2564
3085
  value: local,
2565
3086
  placeholder,
2566
3087
  onChange: (e) => push(e.target.value),
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
3088
+ prefix: /* @__PURE__ */ jsx31(Search2, { "aria-hidden": "true" }),
3089
+ suffix: local ? /* @__PURE__ */ jsx31("button", { type: "button", className: "cfi-ui-search__clear", onClick: clear, "aria-label": clearLabel, children: /* @__PURE__ */ jsx31(X6, { "aria-hidden": "true" }) }) : void 0
2569
3090
  }
2570
3091
  );
2571
3092
  });
2572
3093
 
2573
3094
  // src/Pagination.tsx
2574
- import { ChevronLeft, ChevronRight } from "lucide-react";
3095
+ import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
2575
3096
 
2576
3097
  // src/Pagination.css
2577
3098
  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");
2578
3099
 
2579
3100
  // src/Pagination.tsx
2580
- import { jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
3101
+ import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
2581
3102
  function Pagination({
2582
3103
  page,
2583
3104
  pageSize,
@@ -2593,12 +3114,12 @@ function Pagination({
2593
3114
  const from = (current - 1) * pageSize + 1;
2594
3115
  const to = Math.min(current * pageSize, totalItems);
2595
3116
  const range = labels?.range?.(from, to, totalItems) ?? `${from}\u2013${to} of ${totalItems.toLocaleString()}`;
2596
- return /* @__PURE__ */ jsxs29("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
3117
+ return /* @__PURE__ */ jsxs31("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
2597
3118
  showRange && // aria-live so a screen-reader user hears where they landed; paging
2598
3119
  // otherwise moves focus nowhere and announces nothing.
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(
3120
+ /* @__PURE__ */ jsx32("span", { className: "cfi-ui-pagination__range", "aria-live": "polite", children: range }),
3121
+ /* @__PURE__ */ jsxs31("div", { className: "cfi-ui-pagination__controls", children: [
3122
+ /* @__PURE__ */ jsx32(
2602
3123
  "button",
2603
3124
  {
2604
3125
  type: "button",
@@ -2606,15 +3127,15 @@ function Pagination({
2606
3127
  onClick: () => onPageChange(current - 1),
2607
3128
  disabled: current <= 1,
2608
3129
  "aria-label": labels?.previous ?? "Previous page",
2609
- children: /* @__PURE__ */ jsx30(ChevronLeft, { "aria-hidden": "true" })
3130
+ children: /* @__PURE__ */ jsx32(ChevronLeft2, { "aria-hidden": "true" })
2610
3131
  }
2611
3132
  ),
2612
- /* @__PURE__ */ jsxs29("span", { className: "cfi-ui-pagination__page", children: [
3133
+ /* @__PURE__ */ jsxs31("span", { className: "cfi-ui-pagination__page", children: [
2613
3134
  current,
2614
3135
  " / ",
2615
3136
  pageCount
2616
3137
  ] }),
2617
- /* @__PURE__ */ jsx30(
3138
+ /* @__PURE__ */ jsx32(
2618
3139
  "button",
2619
3140
  {
2620
3141
  type: "button",
@@ -2622,7 +3143,7 @@ function Pagination({
2622
3143
  onClick: () => onPageChange(current + 1),
2623
3144
  disabled: current >= pageCount,
2624
3145
  "aria-label": labels?.next ?? "Next page",
2625
- children: /* @__PURE__ */ jsx30(ChevronRight, { "aria-hidden": "true" })
3146
+ children: /* @__PURE__ */ jsx32(ChevronRight2, { "aria-hidden": "true" })
2626
3147
  }
2627
3148
  )
2628
3149
  ] })
@@ -2633,23 +3154,23 @@ function Pagination({
2633
3154
  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");
2634
3155
 
2635
3156
  // src/ListToolbar.tsx
2636
- import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
3157
+ import { jsx as jsx33, jsxs as jsxs32 } from "react/jsx-runtime";
2637
3158
  function ListToolbar({ search, filters, actions, className }) {
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 })
3159
+ return /* @__PURE__ */ jsxs32("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
3160
+ search && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-list-toolbar__search", children: search }),
3161
+ filters && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
3162
+ actions && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
2642
3163
  ] });
2643
3164
  }
2644
3165
 
2645
3166
  // src/PageHeader.tsx
2646
- import { ArrowLeft, ChevronRight as ChevronRight2 } from "lucide-react";
3167
+ import { ArrowLeft, ChevronRight as ChevronRight3 } from "lucide-react";
2647
3168
 
2648
3169
  // src/PageHeader.css
2649
3170
  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");
2650
3171
 
2651
3172
  // src/PageHeader.tsx
2652
- import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
3173
+ import { jsx as jsx34, jsxs as jsxs33 } from "react/jsx-runtime";
2653
3174
  function PageHeader({
2654
3175
  title,
2655
3176
  description,
@@ -2663,32 +3184,32 @@ function PageHeader({
2663
3184
  className
2664
3185
  }) {
2665
3186
  const Heading = `h${level}`;
2666
- return /* @__PURE__ */ jsxs31(
3187
+ return /* @__PURE__ */ jsxs33(
2667
3188
  "div",
2668
3189
  {
2669
3190
  className: cn("cfi-ui", "cfi-ui-page-header", className),
2670
3191
  "data-bordered": bordered ? "" : void 0,
2671
3192
  children: [
2672
- breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx32("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
3193
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx34("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
2673
3194
  const last = i === breadcrumbs.length - 1;
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 })
3195
+ const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */ jsx34("a", { href: crumb.href, children: crumb.label }) : crumb.label;
3196
+ return /* @__PURE__ */ jsxs33("span", { className: "cfi-ui-page-header__crumb", "data-current": last ? "" : void 0, children: [
3197
+ i > 0 && /* @__PURE__ */ jsx34(ChevronRight3, { className: "cfi-ui-page-header__crumb-sep", "aria-hidden": "true" }),
3198
+ /* @__PURE__ */ jsx34("span", { "aria-current": last ? "page" : void 0, children: label })
2678
3199
  ] }, i);
2679
3200
  }) }),
2680
- onBack && /* @__PURE__ */ jsxs31("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
2681
- /* @__PURE__ */ jsx32(ArrowLeft, { "aria-hidden": "true" }),
3201
+ onBack && /* @__PURE__ */ jsxs33("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
3202
+ /* @__PURE__ */ jsx34(ArrowLeft, { "aria-hidden": "true" }),
2682
3203
  backLabel
2683
3204
  ] }),
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 })
3205
+ /* @__PURE__ */ jsxs33("div", { className: "cfi-ui-page-header__row", children: [
3206
+ /* @__PURE__ */ jsxs33("div", { className: "cfi-ui-page-header__titles", children: [
3207
+ /* @__PURE__ */ jsx34(Heading, { className: "cfi-ui-page-header__title", children: title }),
3208
+ description && /* @__PURE__ */ jsx34("p", { className: "cfi-ui-page-header__description", children: description })
2688
3209
  ] }),
2689
- actions && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-page-header__actions", children: actions })
3210
+ actions && /* @__PURE__ */ jsx34("div", { className: "cfi-ui-page-header__actions", children: actions })
2690
3211
  ] }),
2691
- children && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-page-header__meta", children })
3212
+ children && /* @__PURE__ */ jsx34("div", { className: "cfi-ui-page-header__meta", children })
2692
3213
  ]
2693
3214
  }
2694
3215
  );
@@ -2701,7 +3222,7 @@ import { X as X7 } from "lucide-react";
2701
3222
  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");
2702
3223
 
2703
3224
  // src/SelectionBar.tsx
2704
- import { jsx as jsx33, jsxs as jsxs32 } from "react/jsx-runtime";
3225
+ import { jsx as jsx35, jsxs as jsxs34 } from "react/jsx-runtime";
2705
3226
  function SelectionBar({
2706
3227
  count,
2707
3228
  children,
@@ -2713,7 +3234,7 @@ function SelectionBar({
2713
3234
  className
2714
3235
  }) {
2715
3236
  if (count === 0) return null;
2716
- return /* @__PURE__ */ jsxs32(
3237
+ return /* @__PURE__ */ jsxs34(
2717
3238
  "div",
2718
3239
  {
2719
3240
  role: "status",
@@ -2722,9 +3243,9 @@ function SelectionBar({
2722
3243
  "data-busy": busy ? "" : void 0,
2723
3244
  className: cn("cfi-ui", "cfi-ui-selection-bar", className),
2724
3245
  children: [
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(
3246
+ /* @__PURE__ */ jsx35("span", { className: "cfi-ui-selection-bar__count", children: label(count) }),
3247
+ children && /* @__PURE__ */ jsx35("div", { className: "cfi-ui-selection-bar__actions", children }),
3248
+ onClear && /* @__PURE__ */ jsxs34(
2728
3249
  "button",
2729
3250
  {
2730
3251
  type: "button",
@@ -2732,7 +3253,7 @@ function SelectionBar({
2732
3253
  onClick: onClear,
2733
3254
  disabled: busy,
2734
3255
  children: [
2735
- /* @__PURE__ */ jsx33(X7, { "aria-hidden": "true" }),
3256
+ /* @__PURE__ */ jsx35(X7, { "aria-hidden": "true" }),
2736
3257
  clearLabel
2737
3258
  ]
2738
3259
  }
@@ -2743,14 +3264,14 @@ function SelectionBar({
2743
3264
  }
2744
3265
 
2745
3266
  // src/usePagedRows.ts
2746
- import { useEffect as useEffect5, useMemo as useMemo6, useState as useState8 } from "react";
3267
+ import { useEffect as useEffect7, useMemo as useMemo6, useState as useState10 } from "react";
2747
3268
  function usePagedRows(all, initialPageSize = 25) {
2748
- const [page, setPage] = useState8(1);
2749
- const [pageSize, setSize] = useState8(initialPageSize);
3269
+ const [page, setPage] = useState10(1);
3270
+ const [pageSize, setSize] = useState10(initialPageSize);
2750
3271
  const totalItems = all.length;
2751
3272
  const pageCount = Math.max(1, Math.ceil(totalItems / Math.max(1, pageSize)));
2752
3273
  const clamped = Math.min(Math.max(1, page), pageCount);
2753
- useEffect5(() => {
3274
+ useEffect7(() => {
2754
3275
  if (clamped !== page) setPage(clamped);
2755
3276
  }, [clamped, page]);
2756
3277
  const rows = useMemo6(
@@ -2777,6 +3298,8 @@ export {
2777
3298
  BootScreen,
2778
3299
  Button,
2779
3300
  ButtonGroup,
3301
+ CalendarGrid,
3302
+ CalendarLegendItem,
2780
3303
  Card,
2781
3304
  CardBody,
2782
3305
  CardDescription,
@@ -2811,6 +3334,7 @@ export {
2811
3334
  FilterBar,
2812
3335
  IconButton,
2813
3336
  Input,
3337
+ KanbanBoard,
2814
3338
  Label3 as Label,
2815
3339
  ListToolbar,
2816
3340
  LoadingState,
@@ -2895,6 +3419,11 @@ export {
2895
3419
  TooltipProvider,
2896
3420
  TooltipRoot,
2897
3421
  TooltipTrigger,
3422
+ calendarAddDays,
3423
+ calendarBuckets,
3424
+ calendarDayKey,
3425
+ calendarDays,
3426
+ calendarShift,
2898
3427
  cn,
2899
3428
  formatCalendarDate,
2900
3429
  formatDate,
@@ -2902,6 +3431,8 @@ export {
2902
3431
  formatMoney,
2903
3432
  formatNumber,
2904
3433
  formatPercent,
3434
+ kanbanLayout,
3435
+ kanbanStep,
2905
3436
  timelineLayout,
2906
3437
  useDebouncedValue,
2907
3438
  useFieldControl,
package/package.json CHANGED
@@ -1,56 +1,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
- }
1
+ {
2
+ "name": "@campfire-interactive/ui",
3
+ "version": "0.13.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
+ }