@campfire-interactive/ui 0.4.0 → 0.5.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 +98 -2
- package/dist/index.js +41 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import * as RadixMenu from '@radix-ui/react-dropdown-menu';
|
|
3
4
|
import * as RadixCheckbox from '@radix-ui/react-checkbox';
|
|
4
5
|
import * as RadixSwitch from '@radix-ui/react-switch';
|
|
@@ -169,6 +170,15 @@ interface FilterSelect {
|
|
|
169
170
|
value: string;
|
|
170
171
|
label: React.ReactNode;
|
|
171
172
|
}[];
|
|
173
|
+
/**
|
|
174
|
+
* What the select reads when nothing is chosen. Defaults to "Any".
|
|
175
|
+
*
|
|
176
|
+
* Built from the label it was — `All ${label.toLowerCase()}` — which reads
|
|
177
|
+
* fine for "All statuses" and badly for everything else: the first two call
|
|
178
|
+
* sites produced "All area" and "All who". A caller that wants a plural
|
|
179
|
+
* passes one; the default is a word that works under any label.
|
|
180
|
+
*/
|
|
181
|
+
placeholder?: string;
|
|
172
182
|
}
|
|
173
183
|
interface FilterToggle {
|
|
174
184
|
id: string;
|
|
@@ -184,6 +194,8 @@ interface FilterBarProps {
|
|
|
184
194
|
onChange: (next: FilterValue) => void;
|
|
185
195
|
/** The button's label. Defaults to "Filters". */
|
|
186
196
|
label?: string;
|
|
197
|
+
/** The empty state of every select that does not name its own. Defaults to "Any". */
|
|
198
|
+
anyLabel?: string;
|
|
187
199
|
clearLabel?: string;
|
|
188
200
|
/** Announced on each chip's remove button — "Remove {{name}} filter". */
|
|
189
201
|
removeLabel?: (name: string) => string;
|
|
@@ -212,7 +224,7 @@ interface FilterBarProps {
|
|
|
212
224
|
* wants a different chip — "OEM: 3 selected" — and inventing it before there
|
|
213
225
|
* is a screen for it is how a shared component grows an API nobody uses.
|
|
214
226
|
*/
|
|
215
|
-
declare function FilterBar({ selects, toggles, value, onChange, label, clearLabel, removeLabel, className, }: FilterBarProps): react.JSX.Element;
|
|
227
|
+
declare function FilterBar({ selects, toggles, value, onChange, label, anyLabel, clearLabel, removeLabel, className, }: FilterBarProps): react.JSX.Element;
|
|
216
228
|
|
|
217
229
|
type InputSize = 'sm' | 'md' | 'lg';
|
|
218
230
|
/**
|
|
@@ -1342,6 +1354,90 @@ interface PaginationProps {
|
|
|
1342
1354
|
*/
|
|
1343
1355
|
declare function Pagination({ page, pageSize, totalItems, onPageChange, showRange, labels, className, }: PaginationProps): react.JSX.Element | null;
|
|
1344
1356
|
|
|
1357
|
+
interface ListToolbarProps {
|
|
1358
|
+
/** The search field. Normally a `SearchInput`. */
|
|
1359
|
+
search?: ReactNode;
|
|
1360
|
+
/** The filter control. Normally a `FilterBar`. */
|
|
1361
|
+
filters?: ReactNode;
|
|
1362
|
+
/**
|
|
1363
|
+
* The page's actions — the Add button, a column chooser, an export. Pushed
|
|
1364
|
+
* to the right.
|
|
1365
|
+
*/
|
|
1366
|
+
actions?: ReactNode;
|
|
1367
|
+
className?: string;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* SEARCH, FILTERS AND ACTIONS, in the order every list in the suite wants them.
|
|
1371
|
+
*
|
|
1372
|
+
* ── Why this exists ───────────────────────────────────────────────────────
|
|
1373
|
+
*
|
|
1374
|
+
* `SearchInput`, `FilterBar`, `Table` and `Pagination` were all here already
|
|
1375
|
+
* and every screen wired its own row around them — so they drifted. Within one
|
|
1376
|
+
* app, one table put search left of the filters and another put it right; one
|
|
1377
|
+
* pushed the Add button into the card header and another into the toolbar. The
|
|
1378
|
+
* parts were shared and the arrangement was not, which is the half of
|
|
1379
|
+
* consistency users actually see.
|
|
1380
|
+
*
|
|
1381
|
+
* ── The order is the opinion ──────────────────────────────────────────────
|
|
1382
|
+
*
|
|
1383
|
+
* Search, then filters, then a gap, then actions. Search first because it is
|
|
1384
|
+
* how most people start and it is a text field rather than a value; filters
|
|
1385
|
+
* beside it because they narrow the same list; actions hard right because they
|
|
1386
|
+
* are the only controls here that CHANGE something rather than change what you
|
|
1387
|
+
* are looking at.
|
|
1388
|
+
*
|
|
1389
|
+
* ── What it is not ────────────────────────────────────────────────────────
|
|
1390
|
+
*
|
|
1391
|
+
* Not a data component. It takes no rows, owns no state and knows nothing
|
|
1392
|
+
* about paging — it is a row with three slots and a wrap rule. Pagination
|
|
1393
|
+
* lives UNDER the table, where the rows end, not up here: a pager above the
|
|
1394
|
+
* data is a control for something the reader has not seen yet.
|
|
1395
|
+
*
|
|
1396
|
+
* At phone width the slots stack and each takes the full row, so a search
|
|
1397
|
+
* field never ends up sharing 180px with a filter button.
|
|
1398
|
+
*/
|
|
1399
|
+
declare function ListToolbar({ search, filters, actions, className }: ListToolbarProps): react.JSX.Element;
|
|
1400
|
+
|
|
1401
|
+
interface PagedRows<T> {
|
|
1402
|
+
/** The slice to render. */
|
|
1403
|
+
rows: T[];
|
|
1404
|
+
/** 1-based, for `Pagination`. */
|
|
1405
|
+
page: number;
|
|
1406
|
+
setPage: (page: number) => void;
|
|
1407
|
+
pageSize: number;
|
|
1408
|
+
setPageSize: (size: number) => void;
|
|
1409
|
+
/** The unpaged total, for `Pagination`'s range readout. */
|
|
1410
|
+
totalItems: number;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* CLIENT-SIDE PAGING over rows already in hand.
|
|
1414
|
+
*
|
|
1415
|
+
* ── When this is the right answer, and when it is not ─────────────────────
|
|
1416
|
+
*
|
|
1417
|
+
* Use it when the whole list is already loaded and the problem is the DOM:
|
|
1418
|
+
* five hundred rows in a table is slow to render, slow to scroll and
|
|
1419
|
+
* impossible to read. It does NOT make a request smaller, and a list that can
|
|
1420
|
+
* outgrow one response needs a paged ENDPOINT instead — this hook over an
|
|
1421
|
+
* unbounded fetch is pagination theatre, and the worst version of it, because
|
|
1422
|
+
* the screen then looks like it handles scale.
|
|
1423
|
+
*
|
|
1424
|
+
* ── The bug this exists to prevent ────────────────────────────────────────
|
|
1425
|
+
*
|
|
1426
|
+
* Page 7 of 9, then type into the search box and the list narrows to three
|
|
1427
|
+
* rows: the table renders EMPTY, because page 7 of a one-page list is past the
|
|
1428
|
+
* end. Every hand-rolled version of this has had it, and it reads as "the
|
|
1429
|
+
* search is broken" rather than as a paging fault.
|
|
1430
|
+
*
|
|
1431
|
+
* So the page is CLAMPED on every render — not reset. Clamping keeps you on
|
|
1432
|
+
* page 3 when a row is deleted from a long list, where a reset would throw you
|
|
1433
|
+
* back to the top for no reason the reader can see; and it still pulls you to
|
|
1434
|
+
* the last real page when the list shrinks under you.
|
|
1435
|
+
*
|
|
1436
|
+
* Changing the page SIZE keeps the first row you were looking at on screen,
|
|
1437
|
+
* rather than keeping the page number and jumping somewhere unrelated.
|
|
1438
|
+
*/
|
|
1439
|
+
declare function usePagedRows<T>(all: readonly T[], initialPageSize?: number): PagedRows<T>;
|
|
1440
|
+
|
|
1345
1441
|
/**
|
|
1346
1442
|
* Date and money formatting, because eleven apps call `toLocaleDateString`
|
|
1347
1443
|
* raw and two wrote their own `formatMoney`.
|
|
@@ -1384,4 +1480,4 @@ declare function formatNumber(value: number | null | undefined, decimals?: numbe
|
|
|
1384
1480
|
*/
|
|
1385
1481
|
declare function formatPercent(value: number | null | undefined, decimals?: number): string;
|
|
1386
1482
|
|
|
1387
|
-
export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionRoot, type AccordionSection, AccordionTrigger, type AccordionTriggerProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, type BadgeTone, 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, 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, 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, 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, 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, useSortState, useToast };
|
|
1483
|
+
export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionRoot, type AccordionSection, AccordionTrigger, type AccordionTriggerProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Badge, type BadgeProps, type BadgeTone, 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, 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, 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, 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 };
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// src/styles/base.css
|
|
24
|
-
styleInject('@layer components {\n :where(:root) {\n --cfi-ui-surface: var(--cfi-surface, #ffffff);\n --cfi-ui-surface-raised: var(--cfi-surface-raised, #ffffff);\n --cfi-ui-surface-sunken: var(--cfi-surface-sunken, #f9fafb);\n --cfi-ui-surface-hover: var(--cfi-surface-hover, #f9fafb);\n --cfi-ui-surface-active: var(--cfi-surface-active, #f3f4f6);\n --cfi-ui-surface-selected: var(--cfi-surface-selected, #f3f4f6);\n --cfi-ui-surface-disabled: var(--cfi-surface-disabled, #f3f4f6);\n --cfi-ui-surface-muted: var(--cfi-surface-muted, #ececf0);\n --cfi-ui-input-bg: var(--cfi-input-bg, #f3f3f5);\n --cfi-ui-switch-bg: var(--cfi-switch-bg, #cbced4);\n --cfi-ui-content: var(--cfi-content, oklch(0.145 0 0));\n --cfi-ui-content-muted: var(--cfi-content-muted, #666676);\n --cfi-ui-content-subtle: var(--cfi-content-subtle, #9ca3af);\n --cfi-ui-content-inverse: var(--cfi-content-inverse, #ffffff);\n --cfi-ui-content-disabled: var(--cfi-content-disabled, #9ca3af);\n --cfi-ui-content-link: var(--cfi-content-link, #1d4ed8);\n --cfi-ui-border: var(--cfi-border, #e5e7eb);\n --cfi-ui-border-subtle: var(--cfi-border-subtle, #f3f4f6);\n --cfi-ui-border-strong: var(--cfi-border-strong, #d1d5db);\n --cfi-ui-accent: var(--cfi-action, #030213);\n --cfi-ui-accent-hover: var(--cfi-action-hover, #1a1a2e);\n --cfi-ui-accent-soft: var(--cfi-action-soft, #f3f4f6);\n --cfi-ui-accent-fg: var(--cfi-action-fg, #ffffff);\n --cfi-ui-success: var(--cfi-color-success, #10b981);\n --cfi-ui-success-soft: var(--cfi-color-success-soft, #ecfdf5);\n --cfi-ui-success-text: var(--cfi-color-success-text, #047857);\n --cfi-ui-success-border: var(--cfi-color-success-border, #a7f3d0);\n --cfi-ui-warning: var(--cfi-color-warning, #f59e0b);\n --cfi-ui-warning-soft: var(--cfi-color-warning-soft, #fffbeb);\n --cfi-ui-warning-text: var(--cfi-color-warning-text, #b45309);\n --cfi-ui-warning-border: var(--cfi-color-warning-border, #fde68a);\n --cfi-ui-error: var(--cfi-color-error, #ef4444);\n --cfi-ui-error-strong: var(--cfi-color-error-strong, #dc2626);\n --cfi-ui-error-soft: var(--cfi-color-error-soft, #fef2f2);\n --cfi-ui-error-text: var(--cfi-color-error-text, #b91c1c);\n --cfi-ui-error-border: var(--cfi-color-error-border, #fecaca);\n --cfi-ui-info: var(--cfi-color-info, #3b82f6);\n --cfi-ui-info-soft: var(--cfi-color-info-soft, #eff6ff);\n --cfi-ui-info-text: var(--cfi-color-info-text, #1d4ed8);\n --cfi-ui-info-border: var(--cfi-color-info-border, #bfdbfe);\n --cfi-ui-neutral-soft: var(--cfi-color-neutral-soft, #f3f4f6);\n --cfi-ui-neutral-text: var(--cfi-color-neutral-text, #374151);\n --cfi-ui-neutral-border: var(--cfi-color-neutral-border, #e5e7eb);\n --cfi-ui-focus-color: var(--cfi-focus-ring-color, var(--cfi-ui-accent));\n --cfi-ui-focus-width: var(--cfi-focus-ring-width, 2px);\n --cfi-ui-focus-offset: var(--cfi-focus-ring-offset, 2px);\n --cfi-ui-overlay: var(--cfi-overlay, rgb(17 24 39 / 0.45));\n --cfi-ui-font: var(--cfi-font-family, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);\n --cfi-ui-font-mono: var(--cfi-font-family-mono, ui-monospace, Consolas, monospace);\n --cfi-ui-text-xs: var(--cfi-font-size-xs, 0.75rem);\n --cfi-ui-text-sm: var(--cfi-font-size-sm, 0.875rem);\n --cfi-ui-text-base: var(--cfi-font-size-base, 1rem);\n --cfi-ui-text-lg: var(--cfi-font-size-lg, 1.125rem);\n --cfi-ui-text-xl: var(--cfi-font-size-xl, 1.25rem);\n --cfi-ui-weight-normal: var(--cfi-font-weight-normal, 400);\n --cfi-ui-weight-medium: var(--cfi-font-weight-medium, 500);\n --cfi-ui-weight-semibold: var(--cfi-font-weight-semibold, 600);\n --cfi-ui-leading-tight: var(--cfi-line-height-tight, 1.2);\n --cfi-ui-leading-snug: var(--cfi-line-height-snug, 1.35);\n --cfi-ui-leading-normal: var(--cfi-line-height-normal, 1.5);\n --cfi-ui-tracking-caps: var(--cfi-letter-spacing-caps, 0.08em);\n --cfi-ui-space-3xs: var(--cfi-spacing-3xs, 0.125rem);\n --cfi-ui-space-2xs: var(--cfi-spacing-2xs, 0.1875rem);\n --cfi-ui-space-xs: var(--cfi-spacing-xs, 0.25rem);\n --cfi-ui-space-sm: var(--cfi-spacing-sm, 0.5rem);\n --cfi-ui-space-md: var(--cfi-spacing-md, 1rem);\n --cfi-ui-space-lg: var(--cfi-spacing-lg, 1.5rem);\n --cfi-ui-space-xl: var(--cfi-spacing-xl, 2rem);\n --cfi-ui-radius-sm: var(--cfi-radius-sm, 0.375rem);\n --cfi-ui-radius-md: var(--cfi-radius-md, 0.5rem);\n --cfi-ui-radius-lg: var(--cfi-radius-lg, 0.625rem);\n --cfi-ui-radius-xl: var(--cfi-radius-xl, 0.875rem);\n --cfi-ui-radius-full: var(--cfi-radius-full, 9999px);\n --cfi-ui-shadow-xs: var(--cfi-shadow-xs, 0 1px 1px rgb(17 24 39 / 0.04));\n --cfi-ui-shadow-sm: var(--cfi-shadow-sm, 0 1px 2px rgb(17 24 39 / 0.05));\n --cfi-ui-shadow-md: var(--cfi-shadow-md, 0 4px 6px rgb(17 24 39 / 0.07));\n --cfi-ui-shadow-lg: var(--cfi-shadow-lg, 0 10px 15px rgb(17 24 39 / 0.1));\n --cfi-ui-shadow-xl: var(--cfi-shadow-xl, 0 20px 40px -12px rgb(17 24 39 / 0.22));\n --cfi-ui-transition-fast: var(--cfi-transition-fast, 120ms cubic-bezier(0.4, 0, 0.2, 1));\n --cfi-ui-transition-base: var(--cfi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1));\n --cfi-ui-z-dropdown: var(--cfi-z-dropdown,
|
|
24
|
+
styleInject('@layer components {\n :where(:root) {\n --cfi-ui-surface: var(--cfi-surface, #ffffff);\n --cfi-ui-surface-raised: var(--cfi-surface-raised, #ffffff);\n --cfi-ui-surface-sunken: var(--cfi-surface-sunken, #f9fafb);\n --cfi-ui-surface-hover: var(--cfi-surface-hover, #f9fafb);\n --cfi-ui-surface-active: var(--cfi-surface-active, #f3f4f6);\n --cfi-ui-surface-selected: var(--cfi-surface-selected, #f3f4f6);\n --cfi-ui-surface-disabled: var(--cfi-surface-disabled, #f3f4f6);\n --cfi-ui-surface-muted: var(--cfi-surface-muted, #ececf0);\n --cfi-ui-input-bg: var(--cfi-input-bg, #f3f3f5);\n --cfi-ui-switch-bg: var(--cfi-switch-bg, #cbced4);\n --cfi-ui-content: var(--cfi-content, oklch(0.145 0 0));\n --cfi-ui-content-muted: var(--cfi-content-muted, #666676);\n --cfi-ui-content-subtle: var(--cfi-content-subtle, #9ca3af);\n --cfi-ui-content-inverse: var(--cfi-content-inverse, #ffffff);\n --cfi-ui-content-disabled: var(--cfi-content-disabled, #9ca3af);\n --cfi-ui-content-link: var(--cfi-content-link, #1d4ed8);\n --cfi-ui-border: var(--cfi-border, #e5e7eb);\n --cfi-ui-border-subtle: var(--cfi-border-subtle, #f3f4f6);\n --cfi-ui-border-strong: var(--cfi-border-strong, #d1d5db);\n --cfi-ui-accent: var(--cfi-action, #030213);\n --cfi-ui-accent-hover: var(--cfi-action-hover, #1a1a2e);\n --cfi-ui-accent-soft: var(--cfi-action-soft, #f3f4f6);\n --cfi-ui-accent-fg: var(--cfi-action-fg, #ffffff);\n --cfi-ui-success: var(--cfi-color-success, #10b981);\n --cfi-ui-success-soft: var(--cfi-color-success-soft, #ecfdf5);\n --cfi-ui-success-text: var(--cfi-color-success-text, #047857);\n --cfi-ui-success-border: var(--cfi-color-success-border, #a7f3d0);\n --cfi-ui-warning: var(--cfi-color-warning, #f59e0b);\n --cfi-ui-warning-soft: var(--cfi-color-warning-soft, #fffbeb);\n --cfi-ui-warning-text: var(--cfi-color-warning-text, #b45309);\n --cfi-ui-warning-border: var(--cfi-color-warning-border, #fde68a);\n --cfi-ui-error: var(--cfi-color-error, #ef4444);\n --cfi-ui-error-strong: var(--cfi-color-error-strong, #dc2626);\n --cfi-ui-error-soft: var(--cfi-color-error-soft, #fef2f2);\n --cfi-ui-error-text: var(--cfi-color-error-text, #b91c1c);\n --cfi-ui-error-border: var(--cfi-color-error-border, #fecaca);\n --cfi-ui-info: var(--cfi-color-info, #3b82f6);\n --cfi-ui-info-soft: var(--cfi-color-info-soft, #eff6ff);\n --cfi-ui-info-text: var(--cfi-color-info-text, #1d4ed8);\n --cfi-ui-info-border: var(--cfi-color-info-border, #bfdbfe);\n --cfi-ui-neutral-soft: var(--cfi-color-neutral-soft, #f3f4f6);\n --cfi-ui-neutral-text: var(--cfi-color-neutral-text, #374151);\n --cfi-ui-neutral-border: var(--cfi-color-neutral-border, #e5e7eb);\n --cfi-ui-focus-color: var(--cfi-focus-ring-color, var(--cfi-ui-accent));\n --cfi-ui-focus-width: var(--cfi-focus-ring-width, 2px);\n --cfi-ui-focus-offset: var(--cfi-focus-ring-offset, 2px);\n --cfi-ui-overlay: var(--cfi-overlay, rgb(17 24 39 / 0.45));\n --cfi-ui-font: var(--cfi-font-family, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);\n --cfi-ui-font-mono: var(--cfi-font-family-mono, ui-monospace, Consolas, monospace);\n --cfi-ui-text-xs: var(--cfi-font-size-xs, 0.75rem);\n --cfi-ui-text-sm: var(--cfi-font-size-sm, 0.875rem);\n --cfi-ui-text-base: var(--cfi-font-size-base, 1rem);\n --cfi-ui-text-lg: var(--cfi-font-size-lg, 1.125rem);\n --cfi-ui-text-xl: var(--cfi-font-size-xl, 1.25rem);\n --cfi-ui-weight-normal: var(--cfi-font-weight-normal, 400);\n --cfi-ui-weight-medium: var(--cfi-font-weight-medium, 500);\n --cfi-ui-weight-semibold: var(--cfi-font-weight-semibold, 600);\n --cfi-ui-leading-tight: var(--cfi-line-height-tight, 1.2);\n --cfi-ui-leading-snug: var(--cfi-line-height-snug, 1.35);\n --cfi-ui-leading-normal: var(--cfi-line-height-normal, 1.5);\n --cfi-ui-tracking-caps: var(--cfi-letter-spacing-caps, 0.08em);\n --cfi-ui-space-3xs: var(--cfi-spacing-3xs, 0.125rem);\n --cfi-ui-space-2xs: var(--cfi-spacing-2xs, 0.1875rem);\n --cfi-ui-space-xs: var(--cfi-spacing-xs, 0.25rem);\n --cfi-ui-space-sm: var(--cfi-spacing-sm, 0.5rem);\n --cfi-ui-space-md: var(--cfi-spacing-md, 1rem);\n --cfi-ui-space-lg: var(--cfi-spacing-lg, 1.5rem);\n --cfi-ui-space-xl: var(--cfi-spacing-xl, 2rem);\n --cfi-ui-radius-sm: var(--cfi-radius-sm, 0.375rem);\n --cfi-ui-radius-md: var(--cfi-radius-md, 0.5rem);\n --cfi-ui-radius-lg: var(--cfi-radius-lg, 0.625rem);\n --cfi-ui-radius-xl: var(--cfi-radius-xl, 0.875rem);\n --cfi-ui-radius-full: var(--cfi-radius-full, 9999px);\n --cfi-ui-shadow-xs: var(--cfi-shadow-xs, 0 1px 1px rgb(17 24 39 / 0.04));\n --cfi-ui-shadow-sm: var(--cfi-shadow-sm, 0 1px 2px rgb(17 24 39 / 0.05));\n --cfi-ui-shadow-md: var(--cfi-shadow-md, 0 4px 6px rgb(17 24 39 / 0.07));\n --cfi-ui-shadow-lg: var(--cfi-shadow-lg, 0 10px 15px rgb(17 24 39 / 0.1));\n --cfi-ui-shadow-xl: var(--cfi-shadow-xl, 0 20px 40px -12px rgb(17 24 39 / 0.22));\n --cfi-ui-transition-fast: var(--cfi-transition-fast, 120ms cubic-bezier(0.4, 0, 0.2, 1));\n --cfi-ui-transition-base: var(--cfi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1));\n --cfi-ui-z-dropdown: max(var(--cfi-z-dropdown, 1350), calc(var(--cfi-z-popover, 1300) + 50));\n --cfi-ui-z-overlay: var(--cfi-z-overlay, 1100);\n --cfi-ui-z-modal: var(--cfi-z-modal, 1200);\n --cfi-ui-z-popover: var(--cfi-z-popover, 1300);\n --cfi-ui-z-toast: var(--cfi-z-toast, 1400);\n --cfi-ui-z-tooltip: var(--cfi-z-tooltip, 1500);\n --cfi-ui-control-height-sm: 2rem;\n --cfi-ui-control-height-md: 2.25rem;\n --cfi-ui-control-height-lg: 2.75rem;\n }\n .cfi-ui {\n box-sizing: border-box;\n font-family: var(--cfi-ui-font);\n }\n .cfi-ui *,\n .cfi-ui *::before,\n .cfi-ui *::after {\n box-sizing: border-box;\n }\n .cfi-ui:focus-visible,\n .cfi-ui :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-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n @keyframes cfi-ui-spin {\n to {\n transform: rotate(360deg);\n }\n }\n @keyframes cfi-ui-pulse {\n 50% {\n opacity: 0.45;\n }\n }\n @keyframes cfi-ui-fade-in {\n from {\n opacity: 0;\n }\n }\n @keyframes cfi-ui-zoom-in {\n from {\n opacity: 0;\n transform: scale(0.97);\n }\n }\n @keyframes cfi-ui-slide-in-right {\n from {\n transform: translateX(100%);\n }\n }\n @keyframes cfi-ui-slide-in-left {\n from {\n transform: translateX(-100%);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ui,\n .cfi-ui *,\n [data-cfi-ui-portal],\n [data-cfi-ui-portal] * {\n animation-duration: 1ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 1ms !important;\n }\n }\n}\n');
|
|
25
25
|
|
|
26
26
|
// src/lib/cn.ts
|
|
27
27
|
function cn(...values) {
|
|
@@ -504,6 +504,7 @@ function FilterBar({
|
|
|
504
504
|
value,
|
|
505
505
|
onChange,
|
|
506
506
|
label = "Filters",
|
|
507
|
+
anyLabel = "Any",
|
|
507
508
|
clearLabel = "Clear filters",
|
|
508
509
|
removeLabel = (name) => `Remove the ${name} filter`,
|
|
509
510
|
className
|
|
@@ -550,7 +551,7 @@ function FilterBar({
|
|
|
550
551
|
fullWidth: true,
|
|
551
552
|
"aria-label": s.label,
|
|
552
553
|
value: typeof value[s.id] === "string" ? value[s.id] : void 0,
|
|
553
|
-
placeholder:
|
|
554
|
+
placeholder: s.placeholder ?? anyLabel,
|
|
554
555
|
options: s.options,
|
|
555
556
|
onValueChange: (v) => set(s.id, v)
|
|
556
557
|
}
|
|
@@ -2040,6 +2041,42 @@ function Pagination({
|
|
|
2040
2041
|
] });
|
|
2041
2042
|
}
|
|
2042
2043
|
|
|
2044
|
+
// src/ListToolbar.css
|
|
2045
|
+
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-sm);\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 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");
|
|
2046
|
+
|
|
2047
|
+
// src/ListToolbar.tsx
|
|
2048
|
+
import { jsx as jsx26, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2049
|
+
function ListToolbar({ search, filters, actions, className }) {
|
|
2050
|
+
return /* @__PURE__ */ jsxs25("div", { className: cn("cfi-ui", "cfi-ui-list-toolbar", className), children: [
|
|
2051
|
+
search && /* @__PURE__ */ jsx26("div", { className: "cfi-ui-list-toolbar__search", children: search }),
|
|
2052
|
+
filters && /* @__PURE__ */ jsx26("div", { className: "cfi-ui-list-toolbar__filters", children: filters }),
|
|
2053
|
+
actions && /* @__PURE__ */ jsx26("div", { className: "cfi-ui-list-toolbar__actions", children: actions })
|
|
2054
|
+
] });
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// src/usePagedRows.ts
|
|
2058
|
+
import { useEffect as useEffect4, useMemo as useMemo6, useState as useState7 } from "react";
|
|
2059
|
+
function usePagedRows(all, initialPageSize = 25) {
|
|
2060
|
+
const [page, setPage] = useState7(1);
|
|
2061
|
+
const [pageSize, setSize] = useState7(initialPageSize);
|
|
2062
|
+
const totalItems = all.length;
|
|
2063
|
+
const pageCount = Math.max(1, Math.ceil(totalItems / Math.max(1, pageSize)));
|
|
2064
|
+
const clamped = Math.min(Math.max(1, page), pageCount);
|
|
2065
|
+
useEffect4(() => {
|
|
2066
|
+
if (clamped !== page) setPage(clamped);
|
|
2067
|
+
}, [clamped, page]);
|
|
2068
|
+
const rows = useMemo6(
|
|
2069
|
+
() => all.slice((clamped - 1) * pageSize, clamped * pageSize),
|
|
2070
|
+
[all, clamped, pageSize]
|
|
2071
|
+
);
|
|
2072
|
+
const setPageSize = (size) => {
|
|
2073
|
+
const firstRow = (clamped - 1) * pageSize;
|
|
2074
|
+
setSize(size);
|
|
2075
|
+
setPage(Math.floor(firstRow / Math.max(1, size)) + 1);
|
|
2076
|
+
};
|
|
2077
|
+
return { rows, page: clamped, setPage, pageSize, setPageSize, totalItems };
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2043
2080
|
// src/format.ts
|
|
2044
2081
|
function formatDate(value) {
|
|
2045
2082
|
const d = toDate(value);
|
|
@@ -2121,6 +2158,7 @@ export {
|
|
|
2121
2158
|
IconButton,
|
|
2122
2159
|
Input,
|
|
2123
2160
|
Label3 as Label,
|
|
2161
|
+
ListToolbar,
|
|
2124
2162
|
LoadingState,
|
|
2125
2163
|
Menu,
|
|
2126
2164
|
MenuCheckboxItem,
|
|
@@ -2204,6 +2242,7 @@ export {
|
|
|
2204
2242
|
formatNumber,
|
|
2205
2243
|
formatPercent,
|
|
2206
2244
|
useFieldControl,
|
|
2245
|
+
usePagedRows,
|
|
2207
2246
|
useSortState,
|
|
2208
2247
|
useToast
|
|
2209
2248
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campfire-interactive/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|