@campfire-interactive/ui 0.9.2 → 0.10.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 +175 -18
- package/dist/index.js +300 -95
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -363,7 +363,11 @@ declare const Checkbox: react.ForwardRefExoticComponent<CheckboxProps & react.Re
|
|
|
363
363
|
interface SwitchProps extends React.ComponentPropsWithoutRef<typeof RadixSwitch.Root> {
|
|
364
364
|
label?: React.ReactNode;
|
|
365
365
|
description?: React.ReactNode;
|
|
366
|
-
/**
|
|
366
|
+
/**
|
|
367
|
+
* Where the CONTROL sits beside its label. `end` (the default) puts the
|
|
368
|
+
* label first and the switch after it — the shape a settings row uses.
|
|
369
|
+
* `start` puts the switch first.
|
|
370
|
+
*/
|
|
367
371
|
labelPosition?: 'start' | 'end';
|
|
368
372
|
}
|
|
369
373
|
/**
|
|
@@ -372,6 +376,12 @@ interface SwitchProps extends React.ComponentPropsWithoutRef<typeof RadixSwitch.
|
|
|
372
376
|
* A switch takes effect immediately; a checkbox is a value you then save. That
|
|
373
377
|
* distinction is the whole reason both exist, and mixing them is why some
|
|
374
378
|
* settings screens in the fleet appear to save on toggle and others don't.
|
|
379
|
+
*
|
|
380
|
+
* NAMED WITHOUT AN `id`. The label is tied to the control by `htmlFor`, and
|
|
381
|
+
* that used to need the caller's `id` — without one, a screen reader announced
|
|
382
|
+
* an unnamed switch (found in pm's Settings → Gates, 2026-10-01). It now makes
|
|
383
|
+
* its own when none is passed, and the description is wired as the control's
|
|
384
|
+
* description the same way.
|
|
375
385
|
*/
|
|
376
386
|
declare const Switch: react.ForwardRefExoticComponent<SwitchProps & react.RefAttributes<HTMLButtonElement>>;
|
|
377
387
|
|
|
@@ -423,7 +433,9 @@ declare function DialogBody({ className, ...rest }: React.HTMLAttributes<HTMLDiv
|
|
|
423
433
|
declare function DialogFooter({ className, ...rest }: React.HTMLAttributes<HTMLDivElement>): react.JSX.Element;
|
|
424
434
|
interface ModalProps {
|
|
425
435
|
open: boolean;
|
|
426
|
-
onOpenChange
|
|
436
|
+
onOpenChange?: (open: boolean) => void;
|
|
437
|
+
/** Called when the dialog asks to close (Esc, ×, outside click). */
|
|
438
|
+
onClose?: () => void;
|
|
427
439
|
title: React.ReactNode;
|
|
428
440
|
/** Sub-heading under the title. Also becomes the dialog's accessible description. */
|
|
429
441
|
description?: React.ReactNode;
|
|
@@ -453,7 +465,7 @@ interface ModalProps {
|
|
|
453
465
|
* its Save button on screen. That single detail is missing from most of the
|
|
454
466
|
* hand-built copies.
|
|
455
467
|
*/
|
|
456
|
-
declare function Modal({ open, onOpenChange, title, description, size, side, footer, hideClose, dismissible, className, children, }: ModalProps): react.JSX.Element;
|
|
468
|
+
declare function Modal({ open, onOpenChange, onClose, title, description, size, side, footer, hideClose, dismissible, className, children, }: ModalProps): react.JSX.Element;
|
|
457
469
|
/**
|
|
458
470
|
* A `Modal` that slides in from the side.
|
|
459
471
|
*
|
|
@@ -479,7 +491,9 @@ declare const SheetBody: typeof DialogBody;
|
|
|
479
491
|
declare const SheetFooter: typeof DialogFooter;
|
|
480
492
|
interface ConfirmDialogProps {
|
|
481
493
|
open: boolean;
|
|
482
|
-
onOpenChange
|
|
494
|
+
onOpenChange?: (open: boolean) => void;
|
|
495
|
+
/** Called on Cancel, Esc, or any other request to close. */
|
|
496
|
+
onClose?: () => void;
|
|
483
497
|
title: React.ReactNode;
|
|
484
498
|
/** What is about to happen, and what cannot be undone. */
|
|
485
499
|
description?: React.ReactNode;
|
|
@@ -504,7 +518,7 @@ interface ConfirmDialogProps {
|
|
|
504
518
|
* confirm should not count as an answer either way. Esc still cancels, and
|
|
505
519
|
* focus starts on Cancel, so a held Enter key cannot confirm a deletion.
|
|
506
520
|
*/
|
|
507
|
-
declare function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive, loading, onConfirm, children, }: ConfirmDialogProps): react.JSX.Element;
|
|
521
|
+
declare function ConfirmDialog({ open, onOpenChange, onClose, title, description, confirmLabel, cancelLabel, destructive, loading, onConfirm, children, }: ConfirmDialogProps): react.JSX.Element;
|
|
508
522
|
|
|
509
523
|
declare const PopoverRoot: react.FC<RadixPopover.PopoverProps>;
|
|
510
524
|
declare const PopoverTrigger: react.ForwardRefExoticComponent<RadixPopover.PopoverTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
@@ -1345,28 +1359,40 @@ interface ToggleOption<T extends string = string> {
|
|
|
1345
1359
|
/** Accessible name for an icon-only option. Required when there is no label. */
|
|
1346
1360
|
ariaLabel?: string;
|
|
1347
1361
|
}
|
|
1348
|
-
interface
|
|
1362
|
+
interface ToggleGroupBaseProps<T extends string> {
|
|
1349
1363
|
options: ToggleOption<T>[];
|
|
1364
|
+
size?: 'sm' | 'md';
|
|
1365
|
+
className?: string;
|
|
1366
|
+
'aria-label': string;
|
|
1367
|
+
}
|
|
1368
|
+
interface ToggleGroupSingleProps<T extends string> extends ToggleGroupBaseProps<T> {
|
|
1369
|
+
type?: 'single';
|
|
1350
1370
|
value?: T;
|
|
1351
1371
|
onValueChange?: (value: T) => void;
|
|
1352
|
-
size?: 'sm' | 'md';
|
|
1353
1372
|
/**
|
|
1354
1373
|
* Allows deselecting the active option, leaving nothing selected. Off by
|
|
1355
1374
|
* default — a view switcher with no view selected shows nothing.
|
|
1356
1375
|
*/
|
|
1357
1376
|
allowEmpty?: boolean;
|
|
1358
|
-
className?: string;
|
|
1359
|
-
'aria-label': string;
|
|
1360
1377
|
}
|
|
1378
|
+
interface ToggleGroupMultipleProps<T extends string> extends ToggleGroupBaseProps<T> {
|
|
1379
|
+
/** Any number of options on at once, including none — a set of filters. */
|
|
1380
|
+
type: 'multiple';
|
|
1381
|
+
value?: T[];
|
|
1382
|
+
onValueChange?: (value: T[]) => void;
|
|
1383
|
+
}
|
|
1384
|
+
type ToggleGroupProps<T extends string = string> = ToggleGroupSingleProps<T> | ToggleGroupMultipleProps<T>;
|
|
1361
1385
|
/**
|
|
1362
|
-
* A segmented
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1386
|
+
* A segmented control — a view switcher, a unit toggle, a density picker
|
|
1387
|
+
* (`type="single"`, the default), or a set of on/off filters drawn the same
|
|
1388
|
+
* way (`type="multiple"`).
|
|
1389
|
+
*
|
|
1390
|
+
* Single: clicking the active option does nothing unless `allowEmpty` is set.
|
|
1391
|
+
* Radix clears the value by default, which is how a chart switcher ends up
|
|
1392
|
+
* rendering an empty panel. Multiple: every click toggles, and an empty
|
|
1393
|
+
* selection is a normal state, so there is no `allowEmpty`.
|
|
1368
1394
|
*/
|
|
1369
|
-
declare function ToggleGroup<T extends string = string>(
|
|
1395
|
+
declare function ToggleGroup<T extends string = string>(props: ToggleGroupProps<T>): react.JSX.Element;
|
|
1370
1396
|
|
|
1371
1397
|
/**
|
|
1372
1398
|
* The states every data-backed screen has to render.
|
|
@@ -1442,6 +1468,124 @@ interface ComingSoonProps {
|
|
|
1442
1468
|
* invented figures is worse than an honest gap.
|
|
1443
1469
|
*/
|
|
1444
1470
|
declare function ComingSoon({ title, detail, className }: ComingSoonProps): react.JSX.Element;
|
|
1471
|
+
interface BootScreenProps {
|
|
1472
|
+
/** `loading` while the app fetches its config; `error` when that failed. */
|
|
1473
|
+
state: 'loading' | 'error';
|
|
1474
|
+
error?: unknown;
|
|
1475
|
+
/** Defaults to "Loading…". */
|
|
1476
|
+
label?: string;
|
|
1477
|
+
/** Defaults to "Could not start the app". */
|
|
1478
|
+
title?: string;
|
|
1479
|
+
/** Defaults to reloading the page, which is the only retry a boot has. */
|
|
1480
|
+
onRetry?: () => void;
|
|
1481
|
+
retryLabel?: string;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* The whole viewport before the app exists: config loading, or config failed.
|
|
1485
|
+
*
|
|
1486
|
+
* Every adopted app's `main.tsx` hand-wrote both, with a raw `#dc2626` and an
|
|
1487
|
+
* unstyled `<button>`, because the page states above assume a layout to sit
|
|
1488
|
+
* in. This is them, centered on the screen. It renders before any provider,
|
|
1489
|
+
* so it takes no context and no translations.
|
|
1490
|
+
*/
|
|
1491
|
+
declare function BootScreen({ state, error, label, title, onRetry, retryLabel, }: BootScreenProps): react.JSX.Element;
|
|
1492
|
+
|
|
1493
|
+
type StatTone = 'neutral' | 'success' | 'warning' | 'error';
|
|
1494
|
+
interface StatTileProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title' | 'onClick'> {
|
|
1495
|
+
label: ReactNode;
|
|
1496
|
+
/** Already formatted: `$1.3M`, `12/18`, `—`. A number is thousands-separated. */
|
|
1497
|
+
value: ReactNode;
|
|
1498
|
+
/** One muted line under the value: a breakdown, a date, a unit. Truncates. */
|
|
1499
|
+
hint?: ReactNode;
|
|
1500
|
+
/** Colors the value. `neutral` (default) leaves it at body color. */
|
|
1501
|
+
tone?: StatTone;
|
|
1502
|
+
/**
|
|
1503
|
+
* Dims the value when it is a zero, a dash, or otherwise "nothing to see".
|
|
1504
|
+
* A zero is a real answer worth showing, but at full weight a row of zeros
|
|
1505
|
+
* carries the same claim as the numbers that moved and the eye goes nowhere.
|
|
1506
|
+
*/
|
|
1507
|
+
quiet?: boolean;
|
|
1508
|
+
/** Top-right corner: a confidence ring, an info button, a sparkline. */
|
|
1509
|
+
aside?: ReactNode;
|
|
1510
|
+
/** Under the hint: a trend delta, a volatility meter. */
|
|
1511
|
+
footer?: ReactNode;
|
|
1512
|
+
/** Makes the whole tile a button — a drill-down. */
|
|
1513
|
+
onClick?: () => void;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* A KPI: a label, one big number, and at most a line of context.
|
|
1517
|
+
*
|
|
1518
|
+
* Six copies existed across the three adopted apps (four in pm alone) and
|
|
1519
|
+
* agreed on everything that matters — muted label, tabular figure, optional
|
|
1520
|
+
* sub-line, a tone for the bad news — while disagreeing on type size,
|
|
1521
|
+
* padding and which of three palettes "amber" meant. Tabular figures are the
|
|
1522
|
+
* one non-negotiable: tiles sit in a row and their digits should line up.
|
|
1523
|
+
*/
|
|
1524
|
+
declare function StatTile({ label, value, hint, tone, quiet, aside, footer, onClick, className, ...rest }: StatTileProps): react.JSX.Element;
|
|
1525
|
+
interface StatGridProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
1526
|
+
/** Narrowest a tile gets before the row wraps. Default `11rem`. */
|
|
1527
|
+
minTileWidth?: string;
|
|
1528
|
+
}
|
|
1529
|
+
/** A row of `StatTile`s that wraps by width, not by breakpoint. */
|
|
1530
|
+
declare function StatGrid({ minTileWidth, className, style, ...rest }: StatGridProps): react.JSX.Element;
|
|
1531
|
+
|
|
1532
|
+
interface ExternalLinkProps {
|
|
1533
|
+
/**
|
|
1534
|
+
* Null when the id behind the label is missing. Renders the children and
|
|
1535
|
+
* nothing else: the honest rendering of "we have the number but not the
|
|
1536
|
+
* link" is the number, not underlined text that does nothing.
|
|
1537
|
+
*/
|
|
1538
|
+
href: string | null | undefined;
|
|
1539
|
+
/** What the reader is about to open: "Open quote Q-2026-164 in Opportunity Management". */
|
|
1540
|
+
title?: string;
|
|
1541
|
+
className?: string;
|
|
1542
|
+
children: ReactNode;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* A link to a record in ANOTHER app, drawn the same way every time.
|
|
1546
|
+
*
|
|
1547
|
+
* pm and analytics each had one, and master-data built its URLs a third way.
|
|
1548
|
+
* A cross-app link says three things an in-app link does not: that it leaves
|
|
1549
|
+
* the app (the arrow), where it goes (the title), and when the id is missing,
|
|
1550
|
+
* that it is not a link at all (null `href`).
|
|
1551
|
+
*
|
|
1552
|
+
* New tab, deliberately: the reader is mid-task and the other app is a
|
|
1553
|
+
* reference. Build the href with shell-header's `getAppUrl`, never from an
|
|
1554
|
+
* env var, so it follows the environment the reader is in.
|
|
1555
|
+
*/
|
|
1556
|
+
declare function ExternalLink({ href, title, className, children }: ExternalLinkProps): react.JSX.Element;
|
|
1557
|
+
interface FileButtonProps extends Omit<ButtonProps, 'onClick' | 'asChild'> {
|
|
1558
|
+
/** The input's `accept`: `.csv,.xlsx`, `image/*`. */
|
|
1559
|
+
accept?: string;
|
|
1560
|
+
/** Called with the chosen file. The input is reset after, so choosing the same file again fires again. */
|
|
1561
|
+
onFile: (file: File) => void;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* A button that opens the file picker.
|
|
1565
|
+
*
|
|
1566
|
+
* Five copies across pm and master-data, each a hidden `<input type="file">`
|
|
1567
|
+
* plus a ref plus a button that clicks it, and most forgot to reset the
|
|
1568
|
+
* input — so re-uploading the same file after a failed import did nothing.
|
|
1569
|
+
*/
|
|
1570
|
+
declare function FileButton({ accept, onFile, children, ...button }: FileButtonProps): react.JSX.Element;
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* `value`, once it has stopped changing for `delayMs`.
|
|
1574
|
+
*
|
|
1575
|
+
* For a value that drives a request (a search string into a query key).
|
|
1576
|
+
* Do not wrap a `SearchInput`'s value in this: `SearchInput` already
|
|
1577
|
+
* debounces its `onChange`, and master-data's seven list hooks waited twice.
|
|
1578
|
+
*/
|
|
1579
|
+
declare function useDebouncedValue<T>(value: T, delayMs?: number): T;
|
|
1580
|
+
/**
|
|
1581
|
+
* `useState` that survives a reload, per viewer and per browser.
|
|
1582
|
+
*
|
|
1583
|
+
* For conveniences only — a collapsed panel, a chosen tab, column widths.
|
|
1584
|
+
* Storage can be absent or throw (a private window, blocked site data), so
|
|
1585
|
+
* every read and write is guarded and the state still works in memory; and a
|
|
1586
|
+
* stored value that no longer parses falls back to `initial`.
|
|
1587
|
+
*/
|
|
1588
|
+
declare function usePersistentState<T>(key: string, initial: T): [T, (next: T | ((prev: T) => T)) => void];
|
|
1445
1589
|
|
|
1446
1590
|
interface SortState<K extends string> {
|
|
1447
1591
|
key: K;
|
|
@@ -1775,6 +1919,13 @@ declare function usePagedRows<T>(all: readonly T[], initialPageSize?: number): P
|
|
|
1775
1919
|
* one that matches a browser setting.
|
|
1776
1920
|
*/
|
|
1777
1921
|
declare function formatDate(value: Date | string | number | null | undefined): string;
|
|
1922
|
+
/**
|
|
1923
|
+
* A calendar date — an SOP, a due date, a birthday — that the API sends as a
|
|
1924
|
+
* UTC-midnight timestamp (`2028-01-01T00:00:00.000Z`, which is how a Prisma
|
|
1925
|
+
* `@db.Date` serializes). Reads the UTC day, so it is the same day in Detroit
|
|
1926
|
+
* and in Silao. Use `formatDate` for an instant (created, updated).
|
|
1927
|
+
*/
|
|
1928
|
+
declare function formatCalendarDate(value: Date | string | number | null | undefined): string;
|
|
1778
1929
|
/** `2026-09-11 14:30`. Same reasoning as `formatDate`, plus a 24-hour clock. */
|
|
1779
1930
|
declare function formatDateTime(value: Date | string | number | null | undefined): string;
|
|
1780
1931
|
interface MoneyOptions {
|
|
@@ -1788,8 +1939,14 @@ interface MoneyOptions {
|
|
|
1788
1939
|
decimals?: number;
|
|
1789
1940
|
/** Set for a per-piece figure to get 4 decimals without naming the number. */
|
|
1790
1941
|
perUnit?: boolean;
|
|
1942
|
+
/**
|
|
1943
|
+
* Scale to the magnitude for a tile or an axis: `$1.3M`, `$52.0K`, `$640`.
|
|
1944
|
+
* `decimals` (default 1) applies to the scaled figure; under 1,000 rounds
|
|
1945
|
+
* to whole units. Never for a per-unit price, which needs its 4 places.
|
|
1946
|
+
*/
|
|
1947
|
+
compact?: boolean;
|
|
1791
1948
|
}
|
|
1792
|
-
declare function formatMoney(value: number | null | undefined, { currency, decimals, perUnit }?: MoneyOptions): string;
|
|
1949
|
+
declare function formatMoney(value: number | null | undefined, { currency, decimals, perUnit, compact }?: MoneyOptions): string;
|
|
1793
1950
|
/** Thousands-separated, no currency. `null` renders as an em dash, not `0`. */
|
|
1794
1951
|
declare function formatNumber(value: number | null | undefined, decimals?: number): string;
|
|
1795
1952
|
/**
|
|
@@ -1799,4 +1956,4 @@ declare function formatNumber(value: number | null | undefined, decimals?: numbe
|
|
|
1799
1956
|
*/
|
|
1800
1957
|
declare function formatPercent(value: number | null | undefined, decimals?: number): string;
|
|
1801
1958
|
|
|
1802
|
-
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, 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, Field, type FieldProps, Fieldset, type FieldsetProps, 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, 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, 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, formatDate, formatDateTime, formatMoney, formatNumber, formatPercent, useFieldControl, usePagedRows, useSortState, useToast };
|
|
1959
|
+
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, 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 };
|
package/dist/index.js
CHANGED
|
@@ -124,7 +124,7 @@ import * as RadixMenu from "@radix-ui/react-dropdown-menu";
|
|
|
124
124
|
import { Check } from "lucide-react";
|
|
125
125
|
|
|
126
126
|
// src/Floating.css
|
|
127
|
-
styleInject("@layer components {\n .cfi-ui-floating {\n z-index: var(--cfi-ui-z-popover);\n background: var(--cfi-ui-surface-raised);\n color: var(--cfi-ui-content);\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-md);\n box-shadow: var(--cfi-ui-shadow-lg);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n animation: cfi-ui-zoom-in var(--cfi-ui-transition-fast);\n max-height: var(--radix-popper-available-height, none);\n overflow: hidden auto;\n }\n .cfi-ui-floating__arrow {\n fill: var(--cfi-ui-surface-raised);\n filter: drop-shadow(0 -1px 0 var(--cfi-ui-border));\n }\n .cfi-ui-popover {\n padding: var(--cfi-ui-space-md);\n min-width: 12rem;\n }\n .cfi-ui-popover[data-flush] {\n padding: 0;\n }\n .cfi-ui-menu {\n
|
|
127
|
+
styleInject("@layer components {\n .cfi-ui-floating {\n z-index: var(--cfi-ui-z-popover);\n background: var(--cfi-ui-surface-raised);\n color: var(--cfi-ui-content);\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-md);\n box-shadow: var(--cfi-ui-shadow-lg);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n animation: cfi-ui-zoom-in var(--cfi-ui-transition-fast);\n max-height: var(--radix-popper-available-height, none);\n overflow: hidden auto;\n }\n .cfi-ui-floating__arrow {\n fill: var(--cfi-ui-surface-raised);\n filter: drop-shadow(0 -1px 0 var(--cfi-ui-border));\n }\n .cfi-ui-popover {\n padding: var(--cfi-ui-space-md);\n min-width: 12rem;\n }\n .cfi-ui-popover[data-flush] {\n padding: 0;\n }\n .cfi-ui-menu {\n min-width: 11rem;\n padding: var(--cfi-ui-space-xs);\n }\n .cfi-ui-menu__item {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n padding: 0.375rem 0.5rem;\n border-radius: var(--cfi-ui-radius-sm);\n font-size: var(--cfi-ui-text-sm);\n line-height: var(--cfi-ui-leading-normal);\n color: var(--cfi-ui-content);\n cursor: pointer;\n user-select: none;\n outline: none;\n }\n .cfi-ui-menu__item[data-highlighted] {\n background: var(--cfi-ui-surface-active);\n }\n .cfi-ui-menu__item[data-disabled] {\n color: var(--cfi-ui-content-disabled);\n cursor: not-allowed;\n }\n .cfi-ui-menu__item[data-variant=danger] {\n color: var(--cfi-ui-error-text);\n }\n .cfi-ui-menu__item[data-variant=danger][data-highlighted] {\n background: var(--cfi-ui-error-soft);\n }\n .cfi-ui-menu__item > svg,\n .cfi-ui-menu__item .cfi-ui-menu__icon > svg {\n width: 0.875rem;\n height: 0.875rem;\n flex: none;\n }\n .cfi-ui-menu__icon {\n display: inline-flex;\n flex: none;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-menu__item[data-variant=danger] .cfi-ui-menu__icon {\n color: inherit;\n }\n .cfi-ui-menu__shortcut {\n margin-left: auto;\n padding-left: var(--cfi-ui-space-md);\n font-family: var(--cfi-ui-font-mono);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-menu__label {\n padding: 0.375rem 0.5rem 0.25rem;\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-menu__separator {\n height: 1px;\n margin: var(--cfi-ui-space-xs) calc(-1 * var(--cfi-ui-space-xs));\n background: var(--cfi-ui-border);\n }\n .cfi-ui-select-content {\n padding: var(--cfi-ui-space-xs);\n min-width: var(--radix-select-trigger-width, 11rem);\n }\n .cfi-ui-select-viewport {\n max-height: min(18rem, var(--radix-select-content-available-height, 18rem));\n }\n .cfi-ui-select-item {\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n padding: 0.375rem 0.5rem;\n border-radius: var(--cfi-ui-radius-sm);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content);\n cursor: pointer;\n user-select: none;\n outline: none;\n }\n .cfi-ui-select-item[data-highlighted] {\n background: var(--cfi-ui-surface-active);\n }\n .cfi-ui-select-item[data-state=checked] {\n font-weight: var(--cfi-ui-weight-medium);\n }\n .cfi-ui-select-item[data-disabled] {\n color: var(--cfi-ui-content-disabled);\n cursor: not-allowed;\n }\n .cfi-ui-select-item__indicator {\n display: inline-flex;\n flex: none;\n width: 0.875rem;\n color: var(--cfi-ui-accent);\n }\n .cfi-ui-select-item__indicator > svg {\n width: 0.875rem;\n height: 0.875rem;\n }\n .cfi-ui-select-empty {\n padding: var(--cfi-ui-space-sm);\n font-size: var(--cfi-ui-text-sm);\n color: var(--cfi-ui-content-muted);\n text-align: center;\n }\n .cfi-ui-tooltip {\n z-index: var(--cfi-ui-z-tooltip);\n max-width: 20rem;\n padding: 0.3125rem 0.5rem;\n border-radius: var(--cfi-ui-radius-sm);\n background: var(--cfi-ui-content);\n color: var(--cfi-ui-surface);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-xs);\n line-height: var(--cfi-ui-leading-normal);\n box-shadow: var(--cfi-ui-shadow-md);\n animation: cfi-ui-zoom-in var(--cfi-ui-transition-fast);\n }\n .cfi-ui-tooltip__arrow {\n fill: var(--cfi-ui-content);\n }\n}\n");
|
|
128
128
|
|
|
129
129
|
// src/Menu.tsx
|
|
130
130
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -481,7 +481,7 @@ function Select({
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
// src/Switch.tsx
|
|
484
|
-
import { forwardRef as forwardRef7 } from "react";
|
|
484
|
+
import { forwardRef as forwardRef7, useId } from "react";
|
|
485
485
|
import * as RadixSwitch from "@radix-ui/react-switch";
|
|
486
486
|
|
|
487
487
|
// src/Switch.css
|
|
@@ -491,11 +491,15 @@ styleInject("@layer components {\n .cfi-ui-switch {\n flex: none;\n posit
|
|
|
491
491
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
492
492
|
var Switch = forwardRef7(
|
|
493
493
|
function Switch2({ label, description, labelPosition = "end", className, id, ...rest }, ref) {
|
|
494
|
+
const generated = useId();
|
|
495
|
+
const controlId = id ?? generated;
|
|
496
|
+
const descriptionId = description ? `${controlId}-description` : void 0;
|
|
494
497
|
const control = /* @__PURE__ */ jsx7(
|
|
495
498
|
RadixSwitch.Root,
|
|
496
499
|
{
|
|
497
500
|
ref,
|
|
498
|
-
id,
|
|
501
|
+
id: controlId,
|
|
502
|
+
"aria-describedby": descriptionId,
|
|
499
503
|
className: cn("cfi-ui", "cfi-ui-switch", !label && className),
|
|
500
504
|
...rest,
|
|
501
505
|
children: /* @__PURE__ */ jsx7(RadixSwitch.Thumb, { className: "cfi-ui-switch__thumb" })
|
|
@@ -505,8 +509,8 @@ var Switch = forwardRef7(
|
|
|
505
509
|
return /* @__PURE__ */ jsxs7("div", { "data-label-position": labelPosition, className: cn("cfi-ui", "cfi-ui-switch-row", className), children: [
|
|
506
510
|
labelPosition === "start" && control,
|
|
507
511
|
/* @__PURE__ */ jsxs7("div", { className: "cfi-ui-switch-row__text", children: [
|
|
508
|
-
/* @__PURE__ */ jsx7("label", { htmlFor:
|
|
509
|
-
description && /* @__PURE__ */ jsx7("span", { className: "cfi-ui-switch-row__description", children: description })
|
|
512
|
+
/* @__PURE__ */ jsx7("label", { htmlFor: controlId, className: "cfi-ui-switch-row__label", children: label }),
|
|
513
|
+
description && /* @__PURE__ */ jsx7("span", { id: descriptionId, className: "cfi-ui-switch-row__description", children: description })
|
|
510
514
|
] }),
|
|
511
515
|
labelPosition === "end" && control
|
|
512
516
|
] });
|
|
@@ -659,7 +663,7 @@ var Textarea = forwardRef8(function Textarea2({ invalid, className, rows = 3, ..
|
|
|
659
663
|
});
|
|
660
664
|
|
|
661
665
|
// src/Field.tsx
|
|
662
|
-
import { Children, cloneElement as cloneElement2, createContext, isValidElement as isValidElement2, useContext, useId } from "react";
|
|
666
|
+
import { Children, cloneElement as cloneElement2, createContext, isValidElement as isValidElement2, useContext, useId as useId2 } from "react";
|
|
663
667
|
|
|
664
668
|
// src/Field.css
|
|
665
669
|
styleInject('@layer components {\n .cfi-ui-label {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-ui-space-3xs);\n font-size: var(--cfi-ui-text-sm);\n font-weight: var(--cfi-ui-weight-medium);\n color: var(--cfi-ui-content);\n line-height: var(--cfi-ui-leading-normal);\n }\n .cfi-ui-label__required {\n color: var(--cfi-ui-error);\n font-weight: var(--cfi-ui-weight-semibold);\n }\n .cfi-ui-field {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: var(--cfi-ui-space-xs);\n }\n .cfi-ui-field__label {\n justify-self: start;\n }\n .cfi-ui-field__body {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: var(--cfi-ui-space-xs);\n min-width: 0;\n }\n .cfi-ui-field__hint,\n .cfi-ui-field__error {\n margin: 0;\n font-size: var(--cfi-ui-text-xs);\n line-height: var(--cfi-ui-leading-normal);\n }\n .cfi-ui-field__hint {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-field__error {\n color: var(--cfi-ui-error-text);\n font-weight: var(--cfi-ui-weight-medium);\n }\n .cfi-ui-field[data-orientation=horizontal] {\n grid-template-columns: minmax(8rem, 12rem) 1fr;\n align-items: start;\n gap: var(--cfi-ui-space-md);\n }\n .cfi-ui-field[data-orientation=horizontal] .cfi-ui-field__label {\n padding-top: 0.4375rem;\n }\n @media (max-width: 640px) {\n .cfi-ui-field[data-orientation=horizontal] {\n grid-template-columns: 1fr;\n gap: var(--cfi-ui-space-xs);\n }\n .cfi-ui-field[data-orientation=horizontal] .cfi-ui-field__label {\n padding-top: 0;\n }\n }\n .cfi-ui-fieldset {\n min-width: 0;\n margin: 0;\n padding: 0;\n border: 0;\n }\n .cfi-ui-fieldset__legend {\n padding: 0 0 var(--cfi-ui-space-sm);\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-fieldset__body {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: var(--cfi-ui-space-md);\n }\n .cfi-ui-fieldset[data-columns="2"] .cfi-ui-fieldset__body {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n .cfi-ui-fieldset[data-columns="3"] .cfi-ui-fieldset__body {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n @media (max-width: 720px) {\n .cfi-ui-fieldset[data-columns="2"] .cfi-ui-fieldset__body,\n .cfi-ui-fieldset[data-columns="3"] .cfi-ui-fieldset__body {\n grid-template-columns: 1fr;\n }\n }\n}\n');
|
|
@@ -686,7 +690,7 @@ function Field({
|
|
|
686
690
|
children,
|
|
687
691
|
id
|
|
688
692
|
}) {
|
|
689
|
-
const generatedId =
|
|
693
|
+
const generatedId = useId2();
|
|
690
694
|
const controlId = id ?? `cfi-field-${generatedId}`;
|
|
691
695
|
const hintId = `${controlId}-hint`;
|
|
692
696
|
const errorId = `${controlId}-error`;
|
|
@@ -842,9 +846,16 @@ function DialogBody({ className, ...rest }) {
|
|
|
842
846
|
function DialogFooter({ className, ...rest }) {
|
|
843
847
|
return /* @__PURE__ */ jsx13("div", { className: cn("cfi-ui-dialog__footer", className), ...rest });
|
|
844
848
|
}
|
|
849
|
+
function closeHandler(onOpenChange, onClose) {
|
|
850
|
+
return (open) => {
|
|
851
|
+
onOpenChange?.(open);
|
|
852
|
+
if (!open) onClose?.();
|
|
853
|
+
};
|
|
854
|
+
}
|
|
845
855
|
function Modal({
|
|
846
856
|
open,
|
|
847
857
|
onOpenChange,
|
|
858
|
+
onClose,
|
|
848
859
|
title,
|
|
849
860
|
description,
|
|
850
861
|
size = "md",
|
|
@@ -856,7 +867,7 @@ function Modal({
|
|
|
856
867
|
children
|
|
857
868
|
}) {
|
|
858
869
|
const block = dismissible ? void 0 : (e) => e.preventDefault();
|
|
859
|
-
return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs13(
|
|
870
|
+
return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange: closeHandler(onOpenChange, onClose), children: /* @__PURE__ */ jsxs13(
|
|
860
871
|
DialogContent,
|
|
861
872
|
{
|
|
862
873
|
size,
|
|
@@ -899,6 +910,7 @@ var SheetFooter = DialogFooter;
|
|
|
899
910
|
function ConfirmDialog({
|
|
900
911
|
open,
|
|
901
912
|
onOpenChange,
|
|
913
|
+
onClose,
|
|
902
914
|
title,
|
|
903
915
|
description,
|
|
904
916
|
confirmLabel = "Confirm",
|
|
@@ -908,7 +920,7 @@ function ConfirmDialog({
|
|
|
908
920
|
onConfirm,
|
|
909
921
|
children
|
|
910
922
|
}) {
|
|
911
|
-
return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs13(
|
|
923
|
+
return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange: closeHandler(onOpenChange, onClose), children: /* @__PURE__ */ jsxs13(
|
|
912
924
|
DialogContent,
|
|
913
925
|
{
|
|
914
926
|
size: "sm",
|
|
@@ -1087,7 +1099,7 @@ function CreatableSelect({
|
|
|
1087
1099
|
}
|
|
1088
1100
|
|
|
1089
1101
|
// src/Combobox.tsx
|
|
1090
|
-
import { Fragment as Fragment4, useEffect, useId as
|
|
1102
|
+
import { Fragment as Fragment4, useEffect, useId as useId3, useMemo as useMemo3, useRef, useState as useState3 } from "react";
|
|
1091
1103
|
import { Check as Check4, ChevronDown as ChevronDown3, Search, X as X3 } from "lucide-react";
|
|
1092
1104
|
|
|
1093
1105
|
// src/Combobox.css
|
|
@@ -1123,7 +1135,7 @@ function Combobox({
|
|
|
1123
1135
|
const [open, setOpen] = useState3(false);
|
|
1124
1136
|
const [search, setSearch] = useState3("");
|
|
1125
1137
|
const [highlighted, setHighlighted] = useState3(0);
|
|
1126
|
-
const listId =
|
|
1138
|
+
const listId = useId3();
|
|
1127
1139
|
const searchRef = useRef(null);
|
|
1128
1140
|
const listRef = useRef(null);
|
|
1129
1141
|
const filtered = useMemo3(() => {
|
|
@@ -1962,15 +1974,42 @@ function Accordion({
|
|
|
1962
1974
|
/* @__PURE__ */ jsx24(AccordionContent, { children: section.content })
|
|
1963
1975
|
] }, section.value)) });
|
|
1964
1976
|
}
|
|
1965
|
-
function ToggleGroup({
|
|
1966
|
-
options,
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1977
|
+
function ToggleGroup(props) {
|
|
1978
|
+
const { options, size = "md", className, "aria-label": ariaLabel } = props;
|
|
1979
|
+
const shared = {
|
|
1980
|
+
"data-size": size,
|
|
1981
|
+
className: cn("cfi-ui", "cfi-ui-toggle-group", className),
|
|
1982
|
+
"aria-label": ariaLabel
|
|
1983
|
+
};
|
|
1984
|
+
const items = options.map((option) => /* @__PURE__ */ jsxs24(
|
|
1985
|
+
RadixToggleGroup.Item,
|
|
1986
|
+
{
|
|
1987
|
+
value: option.value,
|
|
1988
|
+
disabled: option.disabled,
|
|
1989
|
+
"aria-label": option.ariaLabel,
|
|
1990
|
+
title: option.ariaLabel,
|
|
1991
|
+
className: "cfi-ui-toggle-group__item",
|
|
1992
|
+
children: [
|
|
1993
|
+
option.icon && /* @__PURE__ */ jsx24("span", { className: "cfi-ui-toggle-group__icon", children: option.icon }),
|
|
1994
|
+
option.label
|
|
1995
|
+
]
|
|
1996
|
+
},
|
|
1997
|
+
option.value
|
|
1998
|
+
));
|
|
1999
|
+
if (props.type === "multiple") {
|
|
2000
|
+
const { value: value2, onValueChange: onValueChange2 } = props;
|
|
2001
|
+
return /* @__PURE__ */ jsx24(
|
|
2002
|
+
RadixToggleGroup.Root,
|
|
2003
|
+
{
|
|
2004
|
+
type: "multiple",
|
|
2005
|
+
value: value2,
|
|
2006
|
+
onValueChange: (next) => onValueChange2?.(next),
|
|
2007
|
+
...shared,
|
|
2008
|
+
children: items
|
|
2009
|
+
}
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
const { value, onValueChange, allowEmpty = false } = props;
|
|
1974
2013
|
return /* @__PURE__ */ jsx24(
|
|
1975
2014
|
RadixToggleGroup.Root,
|
|
1976
2015
|
{
|
|
@@ -1980,24 +2019,8 @@ function ToggleGroup({
|
|
|
1980
2019
|
if (!next && !allowEmpty) return;
|
|
1981
2020
|
onValueChange?.(next);
|
|
1982
2021
|
},
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
...aria,
|
|
1986
|
-
children: options.map((option) => /* @__PURE__ */ jsxs24(
|
|
1987
|
-
RadixToggleGroup.Item,
|
|
1988
|
-
{
|
|
1989
|
-
value: option.value,
|
|
1990
|
-
disabled: option.disabled,
|
|
1991
|
-
"aria-label": option.ariaLabel,
|
|
1992
|
-
title: option.ariaLabel,
|
|
1993
|
-
className: "cfi-ui-toggle-group__item",
|
|
1994
|
-
children: [
|
|
1995
|
-
option.icon && /* @__PURE__ */ jsx24("span", { className: "cfi-ui-toggle-group__icon", children: option.icon }),
|
|
1996
|
-
option.label
|
|
1997
|
-
]
|
|
1998
|
-
},
|
|
1999
|
-
option.value
|
|
2000
|
-
))
|
|
2022
|
+
...shared,
|
|
2023
|
+
children: items
|
|
2001
2024
|
}
|
|
2002
2025
|
);
|
|
2003
2026
|
}
|
|
@@ -2006,7 +2029,7 @@ function ToggleGroup({
|
|
|
2006
2029
|
import { AlertCircle as AlertCircle2, Loader2 } from "lucide-react";
|
|
2007
2030
|
|
|
2008
2031
|
// src/PageState.css
|
|
2009
|
-
styleInject("@layer components {\n :where(.cfi-ui-pagestate) {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--cfi-ui-space-sm);\n padding-block: var(--cfi-ui-space-2xl, 4rem);\n text-align: center;\n color: var(--cfi-ui-content);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n }\n .cfi-ui-pagestate[data-size=inline] {\n padding-block: var(--cfi-ui-space-md);\n gap: var(--cfi-ui-space-xs);\n }\n .cfi-ui-pagestate:has(> .cfi-ui-pagestate__spinner) {\n flex-direction: row;\n }\n .cfi-ui-pagestate__spinner {\n width: 1rem;\n height: 1rem;\n flex: none;\n color: var(--cfi-ui-content-muted);\n animation: cfi-ui-pagestate-spin 1s linear infinite;\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ui-pagestate__spinner {\n animation-duration: 2.4s;\n }\n }\n .cfi-ui-pagestate__icon {\n display: inline-flex;\n color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-pagestate__icon > svg {\n width: 1.5rem;\n height: 1.5rem;\n }\n .cfi-ui-pagestate__icon[data-tone=error] {\n width: 1.5rem;\n height: 1.5rem;\n color: var(--cfi-ui-error-strong);\n }\n .cfi-ui-pagestate__title {\n margin: 0;\n font-weight: var(--cfi-ui-weight-medium);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-pagestate__muted {\n margin: 0;\n max-width: 34rem;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-pagestate__title + .cfi-ui-pagestate__muted {\n margin-top: var(--cfi-ui-space-3xs);\n }\n .cfi-ui-pagestate__action {\n margin-top: var(--cfi-ui-space-xs);\n }\n .cfi-ui-pagestate__retry {\n height: var(--cfi-ui-control-height-sm);\n padding-inline: var(--cfi-ui-space-sm);\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 font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n line-height: normal;\n cursor: pointer;\n }\n .cfi-ui-pagestate__retry:hover {\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-pagestate__retry: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@keyframes cfi-ui-pagestate-spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
2032
|
+
styleInject("@layer components {\n :where(.cfi-ui-pagestate) {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--cfi-ui-space-sm);\n padding-block: var(--cfi-ui-space-2xl, 4rem);\n text-align: center;\n color: var(--cfi-ui-content);\n font-family: var(--cfi-ui-font);\n font-size: var(--cfi-ui-text-sm);\n }\n .cfi-ui-pagestate[data-size=inline] {\n padding-block: var(--cfi-ui-space-md);\n gap: var(--cfi-ui-space-xs);\n }\n .cfi-ui-pagestate:has(> .cfi-ui-pagestate__spinner) {\n flex-direction: row;\n }\n .cfi-ui-pagestate__spinner {\n width: 1rem;\n height: 1rem;\n flex: none;\n color: var(--cfi-ui-content-muted);\n animation: cfi-ui-pagestate-spin 1s linear infinite;\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ui-pagestate__spinner {\n animation-duration: 2.4s;\n }\n }\n .cfi-ui-pagestate__icon {\n display: inline-flex;\n color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-pagestate__icon > svg {\n width: 1.5rem;\n height: 1.5rem;\n }\n .cfi-ui-pagestate__icon[data-tone=error] {\n width: 1.5rem;\n height: 1.5rem;\n color: var(--cfi-ui-error-strong);\n }\n .cfi-ui-pagestate__title {\n margin: 0;\n font-weight: var(--cfi-ui-weight-medium);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-pagestate__muted {\n margin: 0;\n max-width: 34rem;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-pagestate__title + .cfi-ui-pagestate__muted {\n margin-top: var(--cfi-ui-space-3xs);\n }\n .cfi-ui-pagestate__action {\n margin-top: var(--cfi-ui-space-xs);\n }\n .cfi-ui-pagestate__retry {\n height: var(--cfi-ui-control-height-sm);\n padding-inline: var(--cfi-ui-space-sm);\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 font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n line-height: normal;\n cursor: pointer;\n }\n .cfi-ui-pagestate__retry:hover {\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-pagestate__retry: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 :where(.cfi-ui-boot) {\n display: grid;\n place-items: center;\n min-height: 100dvh;\n background: var(--cfi-ui-surface);\n }\n}\n@keyframes cfi-ui-pagestate-spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
2010
2033
|
|
|
2011
2034
|
// src/PageState.tsx
|
|
2012
2035
|
import { jsx as jsx25, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
@@ -2070,17 +2093,160 @@ function ComingSoon({ title, detail, className }) {
|
|
|
2070
2093
|
}
|
|
2071
2094
|
);
|
|
2072
2095
|
}
|
|
2096
|
+
function BootScreen({
|
|
2097
|
+
state,
|
|
2098
|
+
error,
|
|
2099
|
+
label,
|
|
2100
|
+
title = "Could not start the app",
|
|
2101
|
+
onRetry = () => window.location.reload(),
|
|
2102
|
+
retryLabel
|
|
2103
|
+
}) {
|
|
2104
|
+
return /* @__PURE__ */ jsx25("div", { className: "cfi-ui cfi-ui-boot", children: state === "loading" ? /* @__PURE__ */ jsx25(LoadingState, { label }) : /* @__PURE__ */ jsx25(ErrorState, { error, title, onRetry, retryLabel }) });
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
// src/StatTile.css
|
|
2108
|
+
styleInject("@layer components {\n :where(.cfi-ui-stat) {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-ui-space-3xs);\n min-width: 0;\n padding: 0.75rem var(--cfi-ui-space-md);\n background: var(--cfi-ui-surface);\n border: 1px solid var(--cfi-ui-border);\n border-radius: var(--cfi-ui-radius-lg);\n font-family: var(--cfi-ui-font);\n color: var(--cfi-ui-content);\n text-align: start;\n }\n .cfi-ui-stat--action {\n cursor: pointer;\n font: inherit;\n transition: background-color var(--cfi-ui-transition-fast);\n }\n .cfi-ui-stat--action:hover {\n background: var(--cfi-ui-surface-hover);\n }\n .cfi-ui-stat--action: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-stat__top {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--cfi-ui-space-xs);\n }\n .cfi-ui-stat__label {\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-stat__aside {\n flex: none;\n }\n .cfi-ui-stat__value {\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 font-variant-numeric: tabular-nums;\n }\n .cfi-ui-stat__value[data-tone=success] {\n color: var(--cfi-ui-success-text);\n }\n .cfi-ui-stat__value[data-tone=warning] {\n color: var(--cfi-ui-warning-text);\n }\n .cfi-ui-stat__value[data-tone=error] {\n color: var(--cfi-ui-error-text);\n }\n .cfi-ui-stat__value[data-quiet] {\n font-weight: var(--cfi-ui-weight-normal);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-stat__hint {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-stat__footer {\n margin-top: var(--cfi-ui-space-3xs);\n font-size: var(--cfi-ui-text-sm);\n }\n :where(.cfi-ui-stat-grid) {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--cfi-ui-stat-min, 11rem), 1fr));\n gap: var(--cfi-ui-space-sm);\n }\n}\n");
|
|
2109
|
+
|
|
2110
|
+
// src/StatTile.tsx
|
|
2111
|
+
import { Fragment as Fragment6, jsx as jsx26, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2112
|
+
function StatTile({
|
|
2113
|
+
label,
|
|
2114
|
+
value,
|
|
2115
|
+
hint,
|
|
2116
|
+
tone = "neutral",
|
|
2117
|
+
quiet = false,
|
|
2118
|
+
aside,
|
|
2119
|
+
footer,
|
|
2120
|
+
onClick,
|
|
2121
|
+
className,
|
|
2122
|
+
...rest
|
|
2123
|
+
}) {
|
|
2124
|
+
const body = /* @__PURE__ */ jsxs26(Fragment6, { children: [
|
|
2125
|
+
/* @__PURE__ */ jsxs26("div", { className: "cfi-ui-stat__top", children: [
|
|
2126
|
+
/* @__PURE__ */ jsx26("span", { className: "cfi-ui-stat__label", children: label }),
|
|
2127
|
+
aside && /* @__PURE__ */ jsx26("span", { className: "cfi-ui-stat__aside", children: aside })
|
|
2128
|
+
] }),
|
|
2129
|
+
/* @__PURE__ */ jsx26("span", { className: "cfi-ui-stat__value", "data-tone": tone, "data-quiet": quiet || void 0, children: typeof value === "number" ? value.toLocaleString("en-US") : value }),
|
|
2130
|
+
hint && /* @__PURE__ */ jsx26("span", { className: "cfi-ui-stat__hint", children: hint }),
|
|
2131
|
+
footer && /* @__PURE__ */ jsx26("div", { className: "cfi-ui-stat__footer", children: footer })
|
|
2132
|
+
] });
|
|
2133
|
+
if (onClick) {
|
|
2134
|
+
return /* @__PURE__ */ jsx26(
|
|
2135
|
+
"button",
|
|
2136
|
+
{
|
|
2137
|
+
type: "button",
|
|
2138
|
+
onClick,
|
|
2139
|
+
className: cn("cfi-ui", "cfi-ui-stat", "cfi-ui-stat--action", className),
|
|
2140
|
+
...rest,
|
|
2141
|
+
children: body
|
|
2142
|
+
}
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
return /* @__PURE__ */ jsx26("div", { className: cn("cfi-ui", "cfi-ui-stat", className), ...rest, children: body });
|
|
2146
|
+
}
|
|
2147
|
+
function StatGrid({ minTileWidth = "11rem", className, style, ...rest }) {
|
|
2148
|
+
return /* @__PURE__ */ jsx26(
|
|
2149
|
+
"div",
|
|
2150
|
+
{
|
|
2151
|
+
className: cn("cfi-ui", "cfi-ui-stat-grid", className),
|
|
2152
|
+
style: { ["--cfi-ui-stat-min"]: minTileWidth, ...style },
|
|
2153
|
+
...rest
|
|
2154
|
+
}
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// src/ExternalLink.tsx
|
|
2159
|
+
import { useRef as useRef3 } from "react";
|
|
2160
|
+
import { ArrowUpRight } from "lucide-react";
|
|
2161
|
+
|
|
2162
|
+
// src/ExternalLink.css
|
|
2163
|
+
styleInject("@layer components {\n :where(.cfi-ui-extlink) {\n display: inline-flex;\n align-items: center;\n gap: 0.125rem;\n color: var(--cfi-ui-accent);\n text-decoration: none;\n }\n .cfi-ui-extlink:hover {\n text-decoration: underline;\n text-underline-offset: 2px;\n }\n .cfi-ui-extlink:focus-visible {\n outline: var(--cfi-ui-focus-width) solid var(--cfi-ui-focus-color);\n outline-offset: var(--cfi-ui-focus-offset);\n border-radius: var(--cfi-ui-radius-sm);\n }\n .cfi-ui-extlink__icon {\n flex: none;\n width: 0.75rem;\n height: 0.75rem;\n opacity: 0.7;\n }\n}\n");
|
|
2164
|
+
|
|
2165
|
+
// src/ExternalLink.tsx
|
|
2166
|
+
import { Fragment as Fragment7, jsx as jsx27, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2167
|
+
function ExternalLink({ href, title, className, children }) {
|
|
2168
|
+
if (!href) return /* @__PURE__ */ jsx27(Fragment7, { children });
|
|
2169
|
+
return /* @__PURE__ */ jsxs27(
|
|
2170
|
+
"a",
|
|
2171
|
+
{
|
|
2172
|
+
href,
|
|
2173
|
+
target: "_blank",
|
|
2174
|
+
rel: "noopener noreferrer",
|
|
2175
|
+
title,
|
|
2176
|
+
className: cn("cfi-ui", "cfi-ui-extlink", className),
|
|
2177
|
+
children: [
|
|
2178
|
+
children,
|
|
2179
|
+
/* @__PURE__ */ jsx27(ArrowUpRight, { className: "cfi-ui-extlink__icon", "aria-hidden": "true" })
|
|
2180
|
+
]
|
|
2181
|
+
}
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
function FileButton({ accept, onFile, children, ...button }) {
|
|
2185
|
+
const input = useRef3(null);
|
|
2186
|
+
return /* @__PURE__ */ jsxs27(Fragment7, { children: [
|
|
2187
|
+
/* @__PURE__ */ jsx27(
|
|
2188
|
+
"input",
|
|
2189
|
+
{
|
|
2190
|
+
ref: input,
|
|
2191
|
+
type: "file",
|
|
2192
|
+
accept,
|
|
2193
|
+
hidden: true,
|
|
2194
|
+
onChange: (e) => {
|
|
2195
|
+
const file = e.target.files?.[0];
|
|
2196
|
+
e.target.value = "";
|
|
2197
|
+
if (file) onFile(file);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
),
|
|
2201
|
+
/* @__PURE__ */ jsx27(Button, { ...button, onClick: () => input.current?.click(), children })
|
|
2202
|
+
] });
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// src/hooks.ts
|
|
2206
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState5 } from "react";
|
|
2207
|
+
function useDebouncedValue(value, delayMs = 300) {
|
|
2208
|
+
const [debounced, setDebounced] = useState5(value);
|
|
2209
|
+
useEffect3(() => {
|
|
2210
|
+
const timer = setTimeout(() => setDebounced(value), delayMs);
|
|
2211
|
+
return () => clearTimeout(timer);
|
|
2212
|
+
}, [value, delayMs]);
|
|
2213
|
+
return debounced;
|
|
2214
|
+
}
|
|
2215
|
+
function usePersistentState(key, initial) {
|
|
2216
|
+
const [value, setValue] = useState5(() => {
|
|
2217
|
+
try {
|
|
2218
|
+
const raw = window.localStorage.getItem(key);
|
|
2219
|
+
return raw === null ? initial : JSON.parse(raw);
|
|
2220
|
+
} catch {
|
|
2221
|
+
return initial;
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
const set = useCallback2(
|
|
2225
|
+
(next) => {
|
|
2226
|
+
setValue((prev) => {
|
|
2227
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
2228
|
+
try {
|
|
2229
|
+
window.localStorage.setItem(key, JSON.stringify(resolved));
|
|
2230
|
+
} catch {
|
|
2231
|
+
}
|
|
2232
|
+
return resolved;
|
|
2233
|
+
});
|
|
2234
|
+
},
|
|
2235
|
+
[key]
|
|
2236
|
+
);
|
|
2237
|
+
return [value, set];
|
|
2238
|
+
}
|
|
2073
2239
|
|
|
2074
2240
|
// src/useSortState.ts
|
|
2075
|
-
import { useCallback as
|
|
2241
|
+
import { useCallback as useCallback3, useMemo as useMemo5, useState as useState6 } from "react";
|
|
2076
2242
|
function useSortState(initialKey, initialDirection = "asc") {
|
|
2077
|
-
const [sort, setSort] =
|
|
2078
|
-
const toggle =
|
|
2243
|
+
const [sort, setSort] = useState6({ key: initialKey, direction: initialDirection });
|
|
2244
|
+
const toggle = useCallback3((key) => {
|
|
2079
2245
|
setSort(
|
|
2080
2246
|
(current) => current.key === key ? { key, direction: current.direction === "asc" ? "desc" : "asc" } : { key, direction: "asc" }
|
|
2081
2247
|
);
|
|
2082
2248
|
}, []);
|
|
2083
|
-
const sortProps =
|
|
2249
|
+
const sortProps = useCallback3(
|
|
2084
2250
|
(key) => ({
|
|
2085
2251
|
sortable: true,
|
|
2086
2252
|
sortDirection: sort.key === key ? sort.direction : null,
|
|
@@ -2088,7 +2254,7 @@ function useSortState(initialKey, initialDirection = "asc") {
|
|
|
2088
2254
|
}),
|
|
2089
2255
|
[sort, toggle]
|
|
2090
2256
|
);
|
|
2091
|
-
const sortBy =
|
|
2257
|
+
const sortBy = useCallback3(
|
|
2092
2258
|
(rows, accessor) => {
|
|
2093
2259
|
const dir = sort.direction === "asc" ? 1 : -1;
|
|
2094
2260
|
return [...rows].sort((a, b) => {
|
|
@@ -2111,21 +2277,21 @@ function useSortState(initialKey, initialDirection = "asc") {
|
|
|
2111
2277
|
}
|
|
2112
2278
|
|
|
2113
2279
|
// src/SearchInput.tsx
|
|
2114
|
-
import { forwardRef as forwardRef18, useEffect as
|
|
2280
|
+
import { forwardRef as forwardRef18, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
|
|
2115
2281
|
import { Search as Search2, X as X6 } from "lucide-react";
|
|
2116
2282
|
|
|
2117
2283
|
// src/SearchInput.css
|
|
2118
2284
|
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");
|
|
2119
2285
|
|
|
2120
2286
|
// src/SearchInput.tsx
|
|
2121
|
-
import { jsx as
|
|
2287
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
2122
2288
|
var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay = 250, placeholder = "Search\u2026", clearLabel = "Clear search", ...rest }, ref) {
|
|
2123
|
-
const [local, setLocal] =
|
|
2124
|
-
const timer =
|
|
2125
|
-
const latest =
|
|
2289
|
+
const [local, setLocal] = useState7(value);
|
|
2290
|
+
const timer = useRef4(void 0);
|
|
2291
|
+
const latest = useRef4(onChange);
|
|
2126
2292
|
latest.current = onChange;
|
|
2127
|
-
|
|
2128
|
-
|
|
2293
|
+
useEffect4(() => setLocal(value), [value]);
|
|
2294
|
+
useEffect4(() => () => clearTimeout(timer.current), []);
|
|
2129
2295
|
const push = (next) => {
|
|
2130
2296
|
setLocal(next);
|
|
2131
2297
|
clearTimeout(timer.current);
|
|
@@ -2137,7 +2303,7 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
|
|
|
2137
2303
|
setLocal("");
|
|
2138
2304
|
latest.current("");
|
|
2139
2305
|
};
|
|
2140
|
-
return /* @__PURE__ */
|
|
2306
|
+
return /* @__PURE__ */ jsx28(
|
|
2141
2307
|
Input,
|
|
2142
2308
|
{
|
|
2143
2309
|
...rest,
|
|
@@ -2146,8 +2312,8 @@ var SearchInput = forwardRef18(function SearchInput2({ value, onChange, delay =
|
|
|
2146
2312
|
value: local,
|
|
2147
2313
|
placeholder,
|
|
2148
2314
|
onChange: (e) => push(e.target.value),
|
|
2149
|
-
prefix: /* @__PURE__ */
|
|
2150
|
-
suffix: local ? /* @__PURE__ */
|
|
2315
|
+
prefix: /* @__PURE__ */ jsx28(Search2, { "aria-hidden": "true" }),
|
|
2316
|
+
suffix: local ? /* @__PURE__ */ jsx28("button", { type: "button", className: "cfi-ui-search__clear", onClick: clear, "aria-label": clearLabel, children: /* @__PURE__ */ jsx28(X6, { "aria-hidden": "true" }) }) : void 0
|
|
2151
2317
|
}
|
|
2152
2318
|
);
|
|
2153
2319
|
});
|
|
@@ -2159,7 +2325,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
2159
2325
|
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");
|
|
2160
2326
|
|
|
2161
2327
|
// src/Pagination.tsx
|
|
2162
|
-
import { jsx as
|
|
2328
|
+
import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2163
2329
|
function Pagination({
|
|
2164
2330
|
page,
|
|
2165
2331
|
pageSize,
|
|
@@ -2175,12 +2341,12 @@ function Pagination({
|
|
|
2175
2341
|
const from = (current - 1) * pageSize + 1;
|
|
2176
2342
|
const to = Math.min(current * pageSize, totalItems);
|
|
2177
2343
|
const range = labels?.range?.(from, to, totalItems) ?? `${from}\u2013${to} of ${totalItems.toLocaleString()}`;
|
|
2178
|
-
return /* @__PURE__ */
|
|
2344
|
+
return /* @__PURE__ */ jsxs28("nav", { className: cn("cfi-ui", "cfi-ui-pagination", className), "aria-label": "Pagination", children: [
|
|
2179
2345
|
showRange && // aria-live so a screen-reader user hears where they landed; paging
|
|
2180
2346
|
// otherwise moves focus nowhere and announces nothing.
|
|
2181
|
-
/* @__PURE__ */
|
|
2182
|
-
/* @__PURE__ */
|
|
2183
|
-
/* @__PURE__ */
|
|
2347
|
+
/* @__PURE__ */ jsx29("span", { className: "cfi-ui-pagination__range", "aria-live": "polite", children: range }),
|
|
2348
|
+
/* @__PURE__ */ jsxs28("div", { className: "cfi-ui-pagination__controls", children: [
|
|
2349
|
+
/* @__PURE__ */ jsx29(
|
|
2184
2350
|
"button",
|
|
2185
2351
|
{
|
|
2186
2352
|
type: "button",
|
|
@@ -2188,15 +2354,15 @@ function Pagination({
|
|
|
2188
2354
|
onClick: () => onPageChange(current - 1),
|
|
2189
2355
|
disabled: current <= 1,
|
|
2190
2356
|
"aria-label": labels?.previous ?? "Previous page",
|
|
2191
|
-
children: /* @__PURE__ */
|
|
2357
|
+
children: /* @__PURE__ */ jsx29(ChevronLeft, { "aria-hidden": "true" })
|
|
2192
2358
|
}
|
|
2193
2359
|
),
|
|
2194
|
-
/* @__PURE__ */
|
|
2360
|
+
/* @__PURE__ */ jsxs28("span", { className: "cfi-ui-pagination__page", children: [
|
|
2195
2361
|
current,
|
|
2196
2362
|
" / ",
|
|
2197
2363
|
pageCount
|
|
2198
2364
|
] }),
|
|
2199
|
-
/* @__PURE__ */
|
|
2365
|
+
/* @__PURE__ */ jsx29(
|
|
2200
2366
|
"button",
|
|
2201
2367
|
{
|
|
2202
2368
|
type: "button",
|
|
@@ -2204,7 +2370,7 @@ function Pagination({
|
|
|
2204
2370
|
onClick: () => onPageChange(current + 1),
|
|
2205
2371
|
disabled: current >= pageCount,
|
|
2206
2372
|
"aria-label": labels?.next ?? "Next page",
|
|
2207
|
-
children: /* @__PURE__ */
|
|
2373
|
+
children: /* @__PURE__ */ jsx29(ChevronRight, { "aria-hidden": "true" })
|
|
2208
2374
|
}
|
|
2209
2375
|
)
|
|
2210
2376
|
] })
|
|
@@ -2215,12 +2381,12 @@ function Pagination({
|
|
|
2215
2381
|
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");
|
|
2216
2382
|
|
|
2217
2383
|
// src/ListToolbar.tsx
|
|
2218
|
-
import { jsx as
|
|
2384
|
+
import { jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2219
2385
|
function ListToolbar({ search, filters, actions, className }) {
|
|
2220
|
-
return /* @__PURE__ */
|
|
2221
|
-
search && /* @__PURE__ */
|
|
2222
|
-
filters && /* @__PURE__ */
|
|
2223
|
-
actions && /* @__PURE__ */
|
|
2386
|
+
return /* @__PURE__ */ jsxs29("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
|
|
2387
|
+
search && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__search", children: search }),
|
|
2388
|
+
filters && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
|
|
2389
|
+
actions && /* @__PURE__ */ jsx30("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
|
|
2224
2390
|
] });
|
|
2225
2391
|
}
|
|
2226
2392
|
|
|
@@ -2231,7 +2397,7 @@ import { ArrowLeft, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
|
2231
2397
|
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");
|
|
2232
2398
|
|
|
2233
2399
|
// src/PageHeader.tsx
|
|
2234
|
-
import { jsx as
|
|
2400
|
+
import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2235
2401
|
function PageHeader({
|
|
2236
2402
|
title,
|
|
2237
2403
|
description,
|
|
@@ -2245,32 +2411,32 @@ function PageHeader({
|
|
|
2245
2411
|
className
|
|
2246
2412
|
}) {
|
|
2247
2413
|
const Heading = `h${level}`;
|
|
2248
|
-
return /* @__PURE__ */
|
|
2414
|
+
return /* @__PURE__ */ jsxs30(
|
|
2249
2415
|
"div",
|
|
2250
2416
|
{
|
|
2251
2417
|
className: cn("cfi-ui", "cfi-ui-page-header", className),
|
|
2252
2418
|
"data-bordered": bordered ? "" : void 0,
|
|
2253
2419
|
children: [
|
|
2254
|
-
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */
|
|
2420
|
+
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx31("nav", { className: "cfi-ui-page-header__crumbs", "aria-label": "Breadcrumb", children: breadcrumbs.map((crumb, i) => {
|
|
2255
2421
|
const last = i === breadcrumbs.length - 1;
|
|
2256
|
-
const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */
|
|
2257
|
-
return /* @__PURE__ */
|
|
2258
|
-
i > 0 && /* @__PURE__ */
|
|
2259
|
-
/* @__PURE__ */
|
|
2422
|
+
const label = crumb.render ? crumb.render(crumb.label) : crumb.href ? /* @__PURE__ */ jsx31("a", { href: crumb.href, children: crumb.label }) : crumb.label;
|
|
2423
|
+
return /* @__PURE__ */ jsxs30("span", { className: "cfi-ui-page-header__crumb", "data-current": last ? "" : void 0, children: [
|
|
2424
|
+
i > 0 && /* @__PURE__ */ jsx31(ChevronRight2, { className: "cfi-ui-page-header__crumb-sep", "aria-hidden": "true" }),
|
|
2425
|
+
/* @__PURE__ */ jsx31("span", { "aria-current": last ? "page" : void 0, children: label })
|
|
2260
2426
|
] }, i);
|
|
2261
2427
|
}) }),
|
|
2262
|
-
onBack && /* @__PURE__ */
|
|
2263
|
-
/* @__PURE__ */
|
|
2428
|
+
onBack && /* @__PURE__ */ jsxs30("button", { type: "button", className: "cfi-ui-page-header__back", onClick: onBack, children: [
|
|
2429
|
+
/* @__PURE__ */ jsx31(ArrowLeft, { "aria-hidden": "true" }),
|
|
2264
2430
|
backLabel
|
|
2265
2431
|
] }),
|
|
2266
|
-
/* @__PURE__ */
|
|
2267
|
-
/* @__PURE__ */
|
|
2268
|
-
/* @__PURE__ */
|
|
2269
|
-
description && /* @__PURE__ */
|
|
2432
|
+
/* @__PURE__ */ jsxs30("div", { className: "cfi-ui-page-header__row", children: [
|
|
2433
|
+
/* @__PURE__ */ jsxs30("div", { className: "cfi-ui-page-header__titles", children: [
|
|
2434
|
+
/* @__PURE__ */ jsx31(Heading, { className: "cfi-ui-page-header__title", children: title }),
|
|
2435
|
+
description && /* @__PURE__ */ jsx31("p", { className: "cfi-ui-page-header__description", children: description })
|
|
2270
2436
|
] }),
|
|
2271
|
-
actions && /* @__PURE__ */
|
|
2437
|
+
actions && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-page-header__actions", children: actions })
|
|
2272
2438
|
] }),
|
|
2273
|
-
children && /* @__PURE__ */
|
|
2439
|
+
children && /* @__PURE__ */ jsx31("div", { className: "cfi-ui-page-header__meta", children })
|
|
2274
2440
|
]
|
|
2275
2441
|
}
|
|
2276
2442
|
);
|
|
@@ -2283,7 +2449,7 @@ import { X as X7 } from "lucide-react";
|
|
|
2283
2449
|
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");
|
|
2284
2450
|
|
|
2285
2451
|
// src/SelectionBar.tsx
|
|
2286
|
-
import { jsx as
|
|
2452
|
+
import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2287
2453
|
function SelectionBar({
|
|
2288
2454
|
count,
|
|
2289
2455
|
children,
|
|
@@ -2295,7 +2461,7 @@ function SelectionBar({
|
|
|
2295
2461
|
className
|
|
2296
2462
|
}) {
|
|
2297
2463
|
if (count === 0) return null;
|
|
2298
|
-
return /* @__PURE__ */
|
|
2464
|
+
return /* @__PURE__ */ jsxs31(
|
|
2299
2465
|
"div",
|
|
2300
2466
|
{
|
|
2301
2467
|
role: "status",
|
|
@@ -2304,9 +2470,9 @@ function SelectionBar({
|
|
|
2304
2470
|
"data-busy": busy ? "" : void 0,
|
|
2305
2471
|
className: cn("cfi-ui", "cfi-ui-selection-bar", className),
|
|
2306
2472
|
children: [
|
|
2307
|
-
/* @__PURE__ */
|
|
2308
|
-
children && /* @__PURE__ */
|
|
2309
|
-
onClear && /* @__PURE__ */
|
|
2473
|
+
/* @__PURE__ */ jsx32("span", { className: "cfi-ui-selection-bar__count", children: label(count) }),
|
|
2474
|
+
children && /* @__PURE__ */ jsx32("div", { className: "cfi-ui-selection-bar__actions", children }),
|
|
2475
|
+
onClear && /* @__PURE__ */ jsxs31(
|
|
2310
2476
|
"button",
|
|
2311
2477
|
{
|
|
2312
2478
|
type: "button",
|
|
@@ -2314,7 +2480,7 @@ function SelectionBar({
|
|
|
2314
2480
|
onClick: onClear,
|
|
2315
2481
|
disabled: busy,
|
|
2316
2482
|
children: [
|
|
2317
|
-
/* @__PURE__ */
|
|
2483
|
+
/* @__PURE__ */ jsx32(X7, { "aria-hidden": "true" }),
|
|
2318
2484
|
clearLabel
|
|
2319
2485
|
]
|
|
2320
2486
|
}
|
|
@@ -2325,14 +2491,14 @@ function SelectionBar({
|
|
|
2325
2491
|
}
|
|
2326
2492
|
|
|
2327
2493
|
// src/usePagedRows.ts
|
|
2328
|
-
import { useEffect as
|
|
2494
|
+
import { useEffect as useEffect5, useMemo as useMemo6, useState as useState8 } from "react";
|
|
2329
2495
|
function usePagedRows(all, initialPageSize = 25) {
|
|
2330
|
-
const [page, setPage] =
|
|
2331
|
-
const [pageSize, setSize] =
|
|
2496
|
+
const [page, setPage] = useState8(1);
|
|
2497
|
+
const [pageSize, setSize] = useState8(initialPageSize);
|
|
2332
2498
|
const totalItems = all.length;
|
|
2333
2499
|
const pageCount = Math.max(1, Math.ceil(totalItems / Math.max(1, pageSize)));
|
|
2334
2500
|
const clamped = Math.min(Math.max(1, page), pageCount);
|
|
2335
|
-
|
|
2501
|
+
useEffect5(() => {
|
|
2336
2502
|
if (clamped !== page) setPage(clamped);
|
|
2337
2503
|
}, [clamped, page]);
|
|
2338
2504
|
const rows = useMemo6(
|
|
@@ -2349,10 +2515,27 @@ function usePagedRows(all, initialPageSize = 25) {
|
|
|
2349
2515
|
|
|
2350
2516
|
// src/format.ts
|
|
2351
2517
|
function formatDate(value) {
|
|
2518
|
+
const day = calendarDay(value);
|
|
2519
|
+
if (day !== null) return day;
|
|
2520
|
+
const d = toDate(value);
|
|
2521
|
+
if (!d) return "\u2014";
|
|
2522
|
+
return ymd(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
|
2523
|
+
}
|
|
2524
|
+
function formatCalendarDate(value) {
|
|
2525
|
+
const day = calendarDay(value);
|
|
2526
|
+
if (day !== null) return day;
|
|
2352
2527
|
const d = toDate(value);
|
|
2353
2528
|
if (!d) return "\u2014";
|
|
2529
|
+
return ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
|
2530
|
+
}
|
|
2531
|
+
function ymd(year, month, day) {
|
|
2354
2532
|
const pad = (n) => String(n).padStart(2, "0");
|
|
2355
|
-
return `${
|
|
2533
|
+
return `${year}-${pad(month)}-${pad(day)}`;
|
|
2534
|
+
}
|
|
2535
|
+
function calendarDay(value) {
|
|
2536
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
2537
|
+
const d = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
2538
|
+
return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value ? value : "\u2014";
|
|
2356
2539
|
}
|
|
2357
2540
|
function formatDateTime(value) {
|
|
2358
2541
|
const d = toDate(value);
|
|
@@ -2360,8 +2543,9 @@ function formatDateTime(value) {
|
|
|
2360
2543
|
const pad = (n) => String(n).padStart(2, "0");
|
|
2361
2544
|
return `${formatDate(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
2362
2545
|
}
|
|
2363
|
-
function formatMoney(value, { currency = "USD", decimals, perUnit = false } = {}) {
|
|
2546
|
+
function formatMoney(value, { currency = "USD", decimals, perUnit = false, compact = false } = {}) {
|
|
2364
2547
|
if (value == null || Number.isNaN(value)) return "\u2014";
|
|
2548
|
+
if (compact) return compactMoney(value, currency, decimals ?? 1);
|
|
2365
2549
|
const places = decimals ?? (perUnit ? 4 : 2);
|
|
2366
2550
|
return new Intl.NumberFormat("en-US", {
|
|
2367
2551
|
style: "currency",
|
|
@@ -2370,6 +2554,19 @@ function formatMoney(value, { currency = "USD", decimals, perUnit = false } = {}
|
|
|
2370
2554
|
maximumFractionDigits: places
|
|
2371
2555
|
}).format(value);
|
|
2372
2556
|
}
|
|
2557
|
+
function compactMoney(value, currency, decimals) {
|
|
2558
|
+
let symbol = currency;
|
|
2559
|
+
try {
|
|
2560
|
+
symbol = new Intl.NumberFormat("en-US", { style: "currency", currency }).formatToParts(0).find((p) => p.type === "currency")?.value ?? currency;
|
|
2561
|
+
} catch {
|
|
2562
|
+
}
|
|
2563
|
+
const abs = Math.abs(value);
|
|
2564
|
+
const sign = value < 0 ? "-" : "";
|
|
2565
|
+
for (const [size, suffix] of [[1e9, "B"], [1e6, "M"], [1e3, "K"]]) {
|
|
2566
|
+
if (abs >= size) return `${sign}${symbol}${(abs / size).toFixed(decimals)}${suffix}`;
|
|
2567
|
+
}
|
|
2568
|
+
return `${sign}${symbol}${Math.round(abs)}`;
|
|
2569
|
+
}
|
|
2373
2570
|
function formatNumber(value, decimals = 0) {
|
|
2374
2571
|
if (value == null || Number.isNaN(value)) return "\u2014";
|
|
2375
2572
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -2396,6 +2593,7 @@ export {
|
|
|
2396
2593
|
Avatar,
|
|
2397
2594
|
AvatarGroup,
|
|
2398
2595
|
Badge,
|
|
2596
|
+
BootScreen,
|
|
2399
2597
|
Button,
|
|
2400
2598
|
ButtonGroup,
|
|
2401
2599
|
Card,
|
|
@@ -2425,8 +2623,10 @@ export {
|
|
|
2425
2623
|
DialogTrigger,
|
|
2426
2624
|
EmptyState,
|
|
2427
2625
|
ErrorState,
|
|
2626
|
+
ExternalLink,
|
|
2428
2627
|
Field,
|
|
2429
2628
|
Fieldset,
|
|
2629
|
+
FileButton,
|
|
2430
2630
|
FilterBar,
|
|
2431
2631
|
IconButton,
|
|
2432
2632
|
Input,
|
|
@@ -2484,6 +2684,8 @@ export {
|
|
|
2484
2684
|
Skeleton,
|
|
2485
2685
|
Slider,
|
|
2486
2686
|
SplitButton,
|
|
2687
|
+
StatGrid,
|
|
2688
|
+
StatTile,
|
|
2487
2689
|
Switch,
|
|
2488
2690
|
TabSet,
|
|
2489
2691
|
Table,
|
|
@@ -2511,13 +2713,16 @@ export {
|
|
|
2511
2713
|
TooltipRoot,
|
|
2512
2714
|
TooltipTrigger,
|
|
2513
2715
|
cn,
|
|
2716
|
+
formatCalendarDate,
|
|
2514
2717
|
formatDate,
|
|
2515
2718
|
formatDateTime,
|
|
2516
2719
|
formatMoney,
|
|
2517
2720
|
formatNumber,
|
|
2518
2721
|
formatPercent,
|
|
2722
|
+
useDebouncedValue,
|
|
2519
2723
|
useFieldControl,
|
|
2520
2724
|
usePagedRows,
|
|
2725
|
+
usePersistentState,
|
|
2521
2726
|
useSortState,
|
|
2522
2727
|
useToast
|
|
2523
2728
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campfire-interactive/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Shared UI primitives for the Campfire Suite — Radix behavior, plain CSS styled entirely from the design tokens, no Tailwind required",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|