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