@campfire-interactive/ui 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +93 -1
- package/dist/index.js +238 -51
- package/package.json +56 -56
package/dist/index.d.ts
CHANGED
|
@@ -1716,6 +1716,98 @@ 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
|
+
|
|
1719
1811
|
interface SortState<K extends string> {
|
|
1720
1812
|
key: K;
|
|
1721
1813
|
direction: SortDirection;
|
|
@@ -2085,4 +2177,4 @@ declare function formatNumber(value: number | null | undefined, decimals?: numbe
|
|
|
2085
2177
|
*/
|
|
2086
2178
|
declare function formatPercent(value: number | null | undefined, decimals?: number): string;
|
|
2087
2179
|
|
|
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 };
|
|
2180
|
+
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, 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, cn, formatCalendarDate, formatDate, formatDateTime, formatMoney, formatNumber, formatPercent, kanbanLayout, kanbanStep, timelineLayout, useDebouncedValue, useFieldControl, usePagedRows, usePersistentState, useSortState, useToast };
|
package/dist/index.js
CHANGED
|
@@ -2489,10 +2489,194 @@ 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
|
+
|
|
2492
2676
|
// src/useSortState.ts
|
|
2493
|
-
import { useCallback as useCallback3, useMemo as useMemo5, useState as
|
|
2677
|
+
import { useCallback as useCallback3, useMemo as useMemo5, useState as useState7 } from "react";
|
|
2494
2678
|
function useSortState(initialKey, initialDirection = "asc") {
|
|
2495
|
-
const [sort, setSort] =
|
|
2679
|
+
const [sort, setSort] = useState7({ key: initialKey, direction: initialDirection });
|
|
2496
2680
|
const toggle = useCallback3((key) => {
|
|
2497
2681
|
setSort(
|
|
2498
2682
|
(current) => current.key === key ? { key, direction: current.direction === "asc" ? "desc" : "asc" } : { key, direction: "asc" }
|
|
@@ -2529,21 +2713,21 @@ function useSortState(initialKey, initialDirection = "asc") {
|
|
|
2529
2713
|
}
|
|
2530
2714
|
|
|
2531
2715
|
// src/SearchInput.tsx
|
|
2532
|
-
import { forwardRef as forwardRef18, useEffect as
|
|
2716
|
+
import { forwardRef as forwardRef18, useEffect as useEffect5, useRef as useRef5, useState as useState8 } from "react";
|
|
2533
2717
|
import { Search as Search2, X as X6 } from "lucide-react";
|
|
2534
2718
|
|
|
2535
2719
|
// src/SearchInput.css
|
|
2536
2720
|
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
2721
|
|
|
2538
2722
|
// src/SearchInput.tsx
|
|
2539
|
-
import { jsx as
|
|
2723
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
2540
2724
|
var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay = 250, placeholder = "Search\u2026", clearLabel = "Clear search", ...rest }, ref) {
|
|
2541
|
-
const [local, setLocal] =
|
|
2542
|
-
const timer =
|
|
2543
|
-
const latest =
|
|
2725
|
+
const [local, setLocal] = useState8(value);
|
|
2726
|
+
const timer = useRef5(void 0);
|
|
2727
|
+
const latest = useRef5(onChange);
|
|
2544
2728
|
latest.current = onChange;
|
|
2545
|
-
|
|
2546
|
-
|
|
2729
|
+
useEffect5(() => setLocal(value), [value]);
|
|
2730
|
+
useEffect5(() => () => clearTimeout(timer.current), []);
|
|
2547
2731
|
const push = (next) => {
|
|
2548
2732
|
setLocal(next);
|
|
2549
2733
|
clearTimeout(timer.current);
|
|
@@ -2555,7 +2739,7 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
|
|
|
2555
2739
|
setLocal("");
|
|
2556
2740
|
latest.current("");
|
|
2557
2741
|
};
|
|
2558
|
-
return /* @__PURE__ */
|
|
2742
|
+
return /* @__PURE__ */ jsx30(
|
|
2559
2743
|
Input,
|
|
2560
2744
|
{
|
|
2561
2745
|
...rest,
|
|
@@ -2564,8 +2748,8 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
|
|
|
2564
2748
|
value: local,
|
|
2565
2749
|
placeholder,
|
|
2566
2750
|
onChange: (e) => push(e.target.value),
|
|
2567
|
-
prefix: /* @__PURE__ */
|
|
2568
|
-
suffix: local ? /* @__PURE__ */
|
|
2751
|
+
prefix: /* @__PURE__ */ jsx30(Search2, { "aria-hidden": "true" }),
|
|
2752
|
+
suffix: local ? /* @__PURE__ */ jsx30("button", { type: "button", className: "cfi-ui-search__clear", onClick: clear, "aria-label": clearLabel, children: /* @__PURE__ */ jsx30(X6, { "aria-hidden": "true" }) }) : void 0
|
|
2569
2753
|
}
|
|
2570
2754
|
);
|
|
2571
2755
|
});
|
|
@@ -2577,7 +2761,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
2577
2761
|
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
2762
|
|
|
2579
2763
|
// src/Pagination.tsx
|
|
2580
|
-
import { jsx as
|
|
2764
|
+
import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2581
2765
|
function Pagination({
|
|
2582
2766
|
page,
|
|
2583
2767
|
pageSize,
|
|
@@ -2593,12 +2777,12 @@ function Pagination({
|
|
|
2593
2777
|
const from = (current - 1) * pageSize + 1;
|
|
2594
2778
|
const to = Math.min(current * pageSize, totalItems);
|
|
2595
2779
|
const range = labels?.range?.(from, to, totalItems) ?? `${from}\u2013${to} of ${totalItems.toLocaleString()}`;
|
|
2596
|
-
return /* @__PURE__ */
|
|
2780
|
+
return /* @__PURE__ */ jsxs30("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
|
|
2597
2781
|
showRange && // aria-live so a screen-reader user hears where they landed; paging
|
|
2598
2782
|
// otherwise moves focus nowhere and announces nothing.
|
|
2599
|
-
/* @__PURE__ */
|
|
2600
|
-
/* @__PURE__ */
|
|
2601
|
-
/* @__PURE__ */
|
|
2783
|
+
/* @__PURE__ */ jsx31("span", { className: "cfi-ui-pagination__range", "aria-live": "polite", children: range }),
|
|
2784
|
+
/* @__PURE__ */ jsxs30("div", { className: "cfi-ui-pagination__controls", children: [
|
|
2785
|
+
/* @__PURE__ */ jsx31(
|
|
2602
2786
|
"button",
|
|
2603
2787
|
{
|
|
2604
2788
|
type: "button",
|
|
@@ -2606,15 +2790,15 @@ function Pagination({
|
|
|
2606
2790
|
onClick: () => onPageChange(current - 1),
|
|
2607
2791
|
disabled: current <= 1,
|
|
2608
2792
|
"aria-label": labels?.previous ?? "Previous page",
|
|
2609
|
-
children: /* @__PURE__ */
|
|
2793
|
+
children: /* @__PURE__ */ jsx31(ChevronLeft, { "aria-hidden": "true" })
|
|
2610
2794
|
}
|
|
2611
2795
|
),
|
|
2612
|
-
/* @__PURE__ */
|
|
2796
|
+
/* @__PURE__ */ jsxs30("span", { className: "cfi-ui-pagination__page", children: [
|
|
2613
2797
|
current,
|
|
2614
2798
|
" / ",
|
|
2615
2799
|
pageCount
|
|
2616
2800
|
] }),
|
|
2617
|
-
/* @__PURE__ */
|
|
2801
|
+
/* @__PURE__ */ jsx31(
|
|
2618
2802
|
"button",
|
|
2619
2803
|
{
|
|
2620
2804
|
type: "button",
|
|
@@ -2622,7 +2806,7 @@ function Pagination({
|
|
|
2622
2806
|
onClick: () => onPageChange(current + 1),
|
|
2623
2807
|
disabled: current >= pageCount,
|
|
2624
2808
|
"aria-label": labels?.next ?? "Next page",
|
|
2625
|
-
children: /* @__PURE__ */
|
|
2809
|
+
children: /* @__PURE__ */ jsx31(ChevronRight, { "aria-hidden": "true" })
|
|
2626
2810
|
}
|
|
2627
2811
|
)
|
|
2628
2812
|
] })
|
|
@@ -2633,12 +2817,12 @@ function Pagination({
|
|
|
2633
2817
|
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
2818
|
|
|
2635
2819
|
// src/ListToolbar.tsx
|
|
2636
|
-
import { jsx as
|
|
2820
|
+
import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2637
2821
|
function ListToolbar({ search, filters, actions, className }) {
|
|
2638
|
-
return /* @__PURE__ */
|
|
2639
|
-
search && /* @__PURE__ */
|
|
2640
|
-
filters && /* @__PURE__ */
|
|
2641
|
-
actions && /* @__PURE__ */
|
|
2822
|
+
return /* @__PURE__ */ jsxs31("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
|
|
2823
|
+
search && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-list-toolbar__search", children: search }),
|
|
2824
|
+
filters && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
|
|
2825
|
+
actions && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
|
|
2642
2826
|
] });
|
|
2643
2827
|
}
|
|
2644
2828
|
|
|
@@ -2649,7 +2833,7 @@ import { ArrowLeft, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
|
2649
2833
|
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
2834
|
|
|
2651
2835
|
// src/PageHeader.tsx
|
|
2652
|
-
import { jsx as
|
|
2836
|
+
import { jsx as jsx33, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2653
2837
|
function PageHeader({
|
|
2654
2838
|
title,
|
|
2655
2839
|
description,
|
|
@@ -2663,32 +2847,32 @@ function PageHeader({
|
|
|
2663
2847
|
className
|
|
2664
2848
|
}) {
|
|
2665
2849
|
const Heading = `h${level}`;
|
|
2666
|
-
return /* @__PURE__ */
|
|
2850
|
+
return /* @__PURE__ */ jsxs32(
|
|
2667
2851
|
"div",
|
|
2668
2852
|
{
|
|
2669
2853
|
className: cn("cfi-ui", "cfi-ui-page-header", className),
|
|
2670
2854
|
"data-bordered": bordered ? "" : void 0,
|
|
2671
2855
|
children: [
|
|
2672
|
-
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */
|
|
2856
|
+
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx33("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
|
|
2673
2857
|
const last = i === breadcrumbs.length - 1;
|
|
2674
|
-
const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */
|
|
2675
|
-
return /* @__PURE__ */
|
|
2676
|
-
i > 0 && /* @__PURE__ */
|
|
2677
|
-
/* @__PURE__ */
|
|
2858
|
+
const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */ jsx33("a", { href: crumb.href, children: crumb.label }) : crumb.label;
|
|
2859
|
+
return /* @__PURE__ */ jsxs32("span", { className: "cfi-ui-page-header__crumb", "data-current": last ? "" : void 0, children: [
|
|
2860
|
+
i > 0 && /* @__PURE__ */ jsx33(ChevronRight2, { className: "cfi-ui-page-header__crumb-sep", "aria-hidden": "true" }),
|
|
2861
|
+
/* @__PURE__ */ jsx33("span", { "aria-current": last ? "page" : void 0, children: label })
|
|
2678
2862
|
] }, i);
|
|
2679
2863
|
}) }),
|
|
2680
|
-
onBack && /* @__PURE__ */
|
|
2681
|
-
/* @__PURE__ */
|
|
2864
|
+
onBack && /* @__PURE__ */ jsxs32("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
|
|
2865
|
+
/* @__PURE__ */ jsx33(ArrowLeft, { "aria-hidden": "true" }),
|
|
2682
2866
|
backLabel
|
|
2683
2867
|
] }),
|
|
2684
|
-
/* @__PURE__ */
|
|
2685
|
-
/* @__PURE__ */
|
|
2686
|
-
/* @__PURE__ */
|
|
2687
|
-
description && /* @__PURE__ */
|
|
2868
|
+
/* @__PURE__ */ jsxs32("div", { className: "cfi-ui-page-header__row", children: [
|
|
2869
|
+
/* @__PURE__ */ jsxs32("div", { className: "cfi-ui-page-header__titles", children: [
|
|
2870
|
+
/* @__PURE__ */ jsx33(Heading, { className: "cfi-ui-page-header__title", children: title }),
|
|
2871
|
+
description && /* @__PURE__ */ jsx33("p", { className: "cfi-ui-page-header__description", children: description })
|
|
2688
2872
|
] }),
|
|
2689
|
-
actions && /* @__PURE__ */
|
|
2873
|
+
actions && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-page-header__actions", children: actions })
|
|
2690
2874
|
] }),
|
|
2691
|
-
children && /* @__PURE__ */
|
|
2875
|
+
children && /* @__PURE__ */ jsx33("div", { className: "cfi-ui-page-header__meta", children })
|
|
2692
2876
|
]
|
|
2693
2877
|
}
|
|
2694
2878
|
);
|
|
@@ -2701,7 +2885,7 @@ import { X as X7 } from "lucide-react";
|
|
|
2701
2885
|
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
2886
|
|
|
2703
2887
|
// src/SelectionBar.tsx
|
|
2704
|
-
import { jsx as
|
|
2888
|
+
import { jsx as jsx34, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2705
2889
|
function SelectionBar({
|
|
2706
2890
|
count,
|
|
2707
2891
|
children,
|
|
@@ -2713,7 +2897,7 @@ function SelectionBar({
|
|
|
2713
2897
|
className
|
|
2714
2898
|
}) {
|
|
2715
2899
|
if (count === 0) return null;
|
|
2716
|
-
return /* @__PURE__ */
|
|
2900
|
+
return /* @__PURE__ */ jsxs33(
|
|
2717
2901
|
"div",
|
|
2718
2902
|
{
|
|
2719
2903
|
role: "status",
|
|
@@ -2722,9 +2906,9 @@ function SelectionBar({
|
|
|
2722
2906
|
"data-busy": busy ? "" : void 0,
|
|
2723
2907
|
className: cn("cfi-ui", "cfi-ui-selection-bar", className),
|
|
2724
2908
|
children: [
|
|
2725
|
-
/* @__PURE__ */
|
|
2726
|
-
children && /* @__PURE__ */
|
|
2727
|
-
onClear && /* @__PURE__ */
|
|
2909
|
+
/* @__PURE__ */ jsx34("span", { className: "cfi-ui-selection-bar__count", children: label(count) }),
|
|
2910
|
+
children && /* @__PURE__ */ jsx34("div", { className: "cfi-ui-selection-bar__actions", children }),
|
|
2911
|
+
onClear && /* @__PURE__ */ jsxs33(
|
|
2728
2912
|
"button",
|
|
2729
2913
|
{
|
|
2730
2914
|
type: "button",
|
|
@@ -2732,7 +2916,7 @@ function SelectionBar({
|
|
|
2732
2916
|
onClick: onClear,
|
|
2733
2917
|
disabled: busy,
|
|
2734
2918
|
children: [
|
|
2735
|
-
/* @__PURE__ */
|
|
2919
|
+
/* @__PURE__ */ jsx34(X7, { "aria-hidden": "true" }),
|
|
2736
2920
|
clearLabel
|
|
2737
2921
|
]
|
|
2738
2922
|
}
|
|
@@ -2743,14 +2927,14 @@ function SelectionBar({
|
|
|
2743
2927
|
}
|
|
2744
2928
|
|
|
2745
2929
|
// src/usePagedRows.ts
|
|
2746
|
-
import { useEffect as
|
|
2930
|
+
import { useEffect as useEffect6, useMemo as useMemo6, useState as useState9 } from "react";
|
|
2747
2931
|
function usePagedRows(all, initialPageSize = 25) {
|
|
2748
|
-
const [page, setPage] =
|
|
2749
|
-
const [pageSize, setSize] =
|
|
2932
|
+
const [page, setPage] = useState9(1);
|
|
2933
|
+
const [pageSize, setSize] = useState9(initialPageSize);
|
|
2750
2934
|
const totalItems = all.length;
|
|
2751
2935
|
const pageCount = Math.max(1, Math.ceil(totalItems / Math.max(1, pageSize)));
|
|
2752
2936
|
const clamped = Math.min(Math.max(1, page), pageCount);
|
|
2753
|
-
|
|
2937
|
+
useEffect6(() => {
|
|
2754
2938
|
if (clamped !== page) setPage(clamped);
|
|
2755
2939
|
}, [clamped, page]);
|
|
2756
2940
|
const rows = useMemo6(
|
|
@@ -2811,6 +2995,7 @@ export {
|
|
|
2811
2995
|
FilterBar,
|
|
2812
2996
|
IconButton,
|
|
2813
2997
|
Input,
|
|
2998
|
+
KanbanBoard,
|
|
2814
2999
|
Label3 as Label,
|
|
2815
3000
|
ListToolbar,
|
|
2816
3001
|
LoadingState,
|
|
@@ -2902,6 +3087,8 @@ export {
|
|
|
2902
3087
|
formatMoney,
|
|
2903
3088
|
formatNumber,
|
|
2904
3089
|
formatPercent,
|
|
3090
|
+
kanbanLayout,
|
|
3091
|
+
kanbanStep,
|
|
2905
3092
|
timelineLayout,
|
|
2906
3093
|
useDebouncedValue,
|
|
2907
3094
|
useFieldControl,
|
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@campfire-interactive/ui",
|
|
3
|
-
"version": "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.12.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
|
+
}
|