@sableui/react 0.8.1 → 0.8.2
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/AGENTS.md +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/components/data-table/data-table.meta.js +4 -0
- package/dist/file-hashes.json +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/llms/components/data-table.md +1 -0
- package/llms/tasks/getting-started.md +6 -0
- package/llms/tasks/tables-with-tanstack.md +157 -0
- package/llms-full.txt +167 -0
- package/package.json +1 -1
- package/registry.json +6 -1
- package/skills/sable/SKILL.md +9 -0
package/AGENTS.md
CHANGED
|
@@ -55,7 +55,7 @@ Pick a theme as a starting point, then override tokens on top of it. Every token
|
|
|
55
55
|
|
|
56
56
|
The machine-readable version of this section is the `consume` block in `registry.json`.
|
|
57
57
|
|
|
58
|
-
1. **Install** from npm: `pnpm add @sableui/react` (or the project's package manager). No registry configuration or token is needed.
|
|
58
|
+
1. **Install** from npm: `pnpm add @sableui/react` (or the project's package manager). No registry configuration or token is needed. Sable depends on no router, data or form library, and keeps whatever the project already uses: links, menus and nav items take any router's link through `render` (`render={<Link to="/inbox" />}` is TanStack Router's or React Router's `Link` alike). For a blank-slate app, the suggested stack is Vite + TypeScript with TanStack Router, TanStack Query, TanStack Form for forms with validation logic, and TanStack Table once a table outgrows `DataTable` (`sable docs tables-with-tanstack`).
|
|
59
59
|
2. **Dedupe React** in the bundler (`resolve.dedupe: ["react","react-dom"]`) — Base UI must not resolve a second React, or you get an "invalid hook call" blank page.
|
|
60
60
|
3. **CSS, in order:** `@import "tailwindcss";` → `@import "@sableui/react/styles.css";` → `@source "<node_modules>/@sableui/react/dist/**/*.{js,mjs}";` so Tailwind scans the package's classes. That ships every component's classes (about 32 KB gzipped); before shipping, `sable sources --write src/sable-sources.css` lists just the modules the app imports, imported in place of that `@source`, and `sable check` fails when it goes stale.
|
|
61
61
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## 0.8.2
|
|
4
|
+
|
|
5
|
+
- **A suggested stack for blank-slate apps.** Sable still depends on no router,
|
|
6
|
+
data or form library, and an app keeps the ones it has. Starting from
|
|
7
|
+
nothing, the skill, `AGENTS.md`, `sable docs getting-started` and the install
|
|
8
|
+
recipe (`registry.json` → `consume.stack`) now suggest Vite + TypeScript with
|
|
9
|
+
TanStack Router, TanStack Query, TanStack Form for forms with validation
|
|
10
|
+
logic, and TanStack Table once a table outgrows `DataTable`.
|
|
11
|
+
- **`sable docs tables-with-tanstack`:** when to move from `DataTable` to
|
|
12
|
+
TanStack Table (server-side data, column visibility or resizing, grouping,
|
|
13
|
+
virtualised rows), and how to render it with Sable's `Table` parts,
|
|
14
|
+
`Checkbox`, `Button` and `Pagination`, with server data through TanStack
|
|
15
|
+
Query and bulk-action selection. `DataTable` stays dependency-free.
|
|
16
|
+
|
|
3
17
|
## 0.8.1
|
|
4
18
|
|
|
5
19
|
- **`sable check` shows doctor's notes.** Warnings and notes from `sable doctor`
|
|
@@ -31,6 +31,10 @@ var dataTableMeta = {
|
|
|
31
31
|
guidance: "do",
|
|
32
32
|
description: "Pair server-side datasets with Pagination and pass already-sorted/paged rows."
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
guidance: "do",
|
|
36
|
+
description: "Move to TanStack Table, rendered with Sable's Table parts, once the table needs server-side sorting or filtering, column visibility or resizing, grouping, or virtualised rows. DataTable stays dependency-free and does not grow into a data grid; `sable docs tables-with-tanstack` maps one onto the other."
|
|
37
|
+
},
|
|
34
38
|
{
|
|
35
39
|
guidance: "dont",
|
|
36
40
|
description: "Make every row clickable and also fill it with competing buttons. Choose a clear row interaction model."
|
package/dist/file-hashes.json
CHANGED
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"dist/components/context-menu/context-menu.meta.js": "6915409a31be6ef0",
|
|
180
180
|
"dist/components/context-menu/index.js": "b4a609ee13e4bdd5",
|
|
181
181
|
"dist/components/data-table/data-table.js": "3d7492b8ac0447b4",
|
|
182
|
-
"dist/components/data-table/data-table.meta.js": "
|
|
182
|
+
"dist/components/data-table/data-table.meta.js": "0aa598b375e5bfeb",
|
|
183
183
|
"dist/components/data-table/index.js": "181835d5e3b6c366",
|
|
184
184
|
"dist/components/date-picker/date-picker.js": "7dd1d41202bede97",
|
|
185
185
|
"dist/components/date-picker/date-picker.meta.js": "a56f356cf633d7e8",
|
|
@@ -470,8 +470,8 @@
|
|
|
470
470
|
"dist/icon-sets/brands.js": "9a37d8739f14f2a9",
|
|
471
471
|
"dist/icon-sets/reicon.d.ts": "52f6e41dc8137b4e",
|
|
472
472
|
"dist/icon-sets/reicon.js": "89a930f17cc90cde",
|
|
473
|
-
"dist/index.d.ts": "
|
|
474
|
-
"dist/index.js": "
|
|
473
|
+
"dist/index.d.ts": "5412b556b1d05aa9",
|
|
474
|
+
"dist/index.js": "3f9d4da26572073f",
|
|
475
475
|
"dist/ios/chrome.css": "a7ca9e3735d8658e",
|
|
476
476
|
"dist/ios/chrome.js": "37091fef7b68bd28",
|
|
477
477
|
"dist/ios/frame.css": "dd2703c944ce575a",
|
package/dist/index.d.ts
CHANGED
|
@@ -8291,6 +8291,6 @@ interface UseCopyResult {
|
|
|
8291
8291
|
*/
|
|
8292
8292
|
declare function useCopy(text: string, { timeout }?: UseCopyOptions): UseCopyResult;
|
|
8293
8293
|
|
|
8294
|
-
declare const SABLE_UI_VERSION: "0.8.
|
|
8294
|
+
declare const SABLE_UI_VERSION: "0.8.2";
|
|
8295
8295
|
|
|
8296
8296
|
export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, type AppHeaderProps, AppShell, type AppShellProps, AreaChart, type AreaChartProps, type ArtTone, Attachment, type AttachmentProps, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, type AvatarGroupCountProps, type AvatarGroupProps, AvatarImage, type AvatarProps, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, type BadgeAppearance, type BadgeProps, type BadgeTone, Banner, type BannerProps, BarChart, type BarChartProps, BottomNav, BottomNavItem, type BottomNavItemProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbPage, type BreadcrumbPageProps, BreadcrumbSeparator, type BreadcrumbSeparatorProps, Breadcrumbs, type BreadcrumbsProps, Browser, type BrowserProps, Bubble, type BubbleProps, BubbleReaction, type BubbleReactionProps, BubbleReactions, type BubbleReactionsProps, Button, type ButtonBleed, type ButtonProps, CHART_SERIES_COUNT, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, type CalendarProps, Card, CardAction, CardContent, type CardContentProps, CardDescription, CardFooter, type CardFooterProps, CardHeader, CardList, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselProps, type CarouselSlidesToShow, type ChartCategories, type ChartCenterLabel, type ChartCurve, type ChartDatum, type ChartSeries, type ChartTone, type ChartTooltipIndicator, type ChartTooltipOptions, type ChartValueFormatter, ChatComposer, type ChatComposerProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxCardTone, CheckboxGroup, type CheckboxGroupProps, Code, CodeBlock, type CodeBlockProps, CodeHighlightProvider, type CodeHighlighter, type CodeProps, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, type ColorFieldProps, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxItemGroup, ComboboxItemIndicator, ComboboxList, ComboboxPortal, type ComboboxProps, ComboboxValue, ComboboxVirtualList, type ComboboxVirtualListProps, type CommandFilter, type CommandItem, CommandMenu, CommandMenuGroup, type CommandMenuGroupProps, CommandMenuItem, type CommandMenuItemProps, CommandMenuPanel, type CommandMenuPanelProps, type CommandMenuProps, CommandMenuTrigger, type CommandMenuTriggerProps, type ComponentGroup, type ComponentGuidance, ComponentMeta, type ComponentRegister, type ConstraintFlag, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DataTable, type DataTableColumn, type DataTableProps, type DataTableRowKey, DatePicker, type DatePickerProps, DateTile, type DateTileProps, Density, DensityProvider, Density as DensityValue, DestructiveActionModal, type DestructiveActionModalProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, type DialogSize, DialogTitle, DialogTrigger, DisclosurePicker, DisclosurePickerEmpty, DisclosurePickerList, type DisclosurePickerListProps, DisclosurePickerOption, type DisclosurePickerOptionProps, DisclosurePickerPanel, type DisclosurePickerPanelProps, type DisclosurePickerProps, type DisclosurePickerSearch, DisclosurePickerTrigger, type DisclosurePickerTriggerProps, EmptyState, type EmptyStateProps, Entity, type EntityProps, Feedback, type FeedbackData, type FeedbackProps, type FeedbackRating, Field, type FieldProps, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, type FileDropzoneProps, type FileRejection, type FileRejectionCode, FontPicker, type FontPickerFont, type FontPickerOption, type FontPickerProps, Form, type FormProps, GlidingHighlight, type GlidingHighlightProps, GradientArt, type GradientArtProps, type Hsv, InlineStatus, type InlineStatusProps, type StatusTone as InlineTone, Input, type InputProps$1 as InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, type LabelProps$2 as LabelProps, LineChart, type LineChartProps, Marker, type MarkerProps, Marquee, type MarqueeProps, MediaImage, type MediaImageProps, MediaTile, type MediaTileProps, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, type MessageProps, MessageScroller, type MessageScrollerHandle, type MessageScrollerProps, Meter, MeterIndicator, MeterLabel, type MeterSize, type MeterTone, MeterTrack, MeterValue, MiddleTruncate, type MiddleTruncateProps, type MotionPolicy, MotionProvider, type MotionProviderProps, Note, type NoteAction, type NoteProps, NotificationBadge, type NotificationBadgeProps, NumberField, type NumberFieldProps, type OrbMode, type OrbSize, OtpField, type OtpFieldProps, OverflowMenu, type OverflowMenuProps, PageHeader, type PageHeaderProps, PageLoader, type PageLoaderProps, Pagination, type PaginationProps, Panel, PanelGroup, PanelResizer, type PanelResizerProps, PanelSection, type PanelWidthBounds, Phone, type PhoneProps, PieChart, type PieChartProps, Pill, type PillProps, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, Presence, type PresenceProps, type Presentation, PresentationProvider, Progress, ProgressIndicator, ProgressLabel, type ProgressSize, ProgressTrack, ProgressValue, PropertyPair, PropertyRow, RadarChart, type RadarChartProps, RadialChart, type RadialChartProps, Radio, RadioCard, type RadioCardProps, type RadioCardTone, RadioGroup, RelativeTime, type RelativeTimeProps, Reveal, type RevealProps, type Rgb, SABLE_UI_VERSION, SEGMENTED_BAR_TONES, STATUS_TONES, STATUS_TONE_ICON, type ScrollCommandOptions, Scroller, type ScrollerProps, ScrubField, type ScrubFieldProps, SearchInput, type SearchInputProps, SectionHeader, type SectionHeaderProps, SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment, type SegmentedBarTone, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps$3 as SeparatorProps, SettingsMenu, type SettingsMenuProps, SettingsRow, type SettingsRowProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetList, SheetPortal, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SideNav, type SideNavActiveVariant, SideNavGroup, type SideNavGroupProps, SideNavItem, type SideNavItemProps, type SideNavProps, type SideNavSize, Sidebar, SidebarBrand, type SidebarBrandProps, SidebarFooter, SidebarHeader, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarProps, SimpleTooltip, Skeleton, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, type SparklineProps, Spinner, type SpinnerProps, StatusBar, StatusBarGroup, StatusBarSeparator, StatusDot, type StatusDotProps, type StatusTone, type StepStatus, Stepper, StepperItem, type StepperItemProps, type StepperOrientation, type StepperProps, StreamingText, type StreamingTextProps, Switch, TRANSPARENT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, type TextRevealProps, Textarea, type TextareaProps, ThemeId, ThemeManifest, ThemeMode$1 as ThemeManifestMode, type ThemeMode, ThemePreview, type ThemePreviewProps, ThemeSelector, type ThemeSelectorProps, ThemeSwitcher, type ThemeSwitcherProps, ThinkingOrb, type ThinkingOrbProps, ToastAction, type ToastApi, ToastClose, ToastContent, ToastDescription, type ToastManagerApi, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, ToastRoot, ToastTitle, type ToastType, ToastViewport, Toggle, ToggleGroup, type ToggleGroupAppearance, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, type ToolbarButtonProps, ToolbarGroup, type ToolbarGroupProps, ToolbarInput, type ToolbarInputProps, ToolbarLink, type ToolbarLinkProps, type ToolbarProps, ToolbarSeparator, type ToolbarSeparatorProps, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, type UseCopyOptions, type UseCopyResult, type UseInViewOptions, type UsePanelWidthOptions, Video, type VideoProps, ViewTransitionName, type ViewTransitionNameProps, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, appHeaderMeta, appShellMeta, areaChartMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bannerMeta, bannerVariants, barChartMeta, bottomNavMeta, breadcrumbsMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, calendarMeta, cardMeta, carouselMeta, chartColor, toneColor as chartToneColor, chatComposerMeta, checkboxCardMeta, checkboxMeta, cn, codeBlockMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, dataTableMeta, datePickerMeta, dateTileMeta, destructiveActionModalMeta, dialogMeta, disclosurePickerMeta, emptyStateMeta, entityMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, fontPickerMeta, formMeta, formatFileSize, formatHex, glidingHighlightMeta, gradientArtMeta, hsvToRgb, hueColor, inlineStatusMeta, inputMeta, isTransparent, kbdMeta, labelMeta, lineChartMeta, markerMeta, marqueeMeta, mediaImageMeta, mediaTileMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, overflowMenuMeta, pageHeaderMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, phoneMeta, pieChartMeta, pillMeta, pillVariants, popoverMeta, progressMeta, progressTrackVariants, radarChartMeta, radialChartMeta, radioCardMeta, radioMeta, relativeTimeMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, searchInputMeta, sectionHeaderMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, selectMeta, separatorMeta, settingsMenuMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, sidebarMeta, skeletonMeta, sliderMeta, sparklineMeta, spinnerMeta, startViewTransition, statusBarMeta, statusDotMeta, statusDotVariants, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, treeIndent, treeMeta, useAnchoredDensity, useComboboxFilteredItems, useCommandMenuShortcut, useCopy, useDensity, useDisclosurePicker, useInView, useMotion, usePanelWidth, usePresentation, useReducedMotion, useScrollActivity, useSidebar, useToast, useToastManager, useTypeTier, videoMeta, workspaceMeta };
|
package/dist/index.js
CHANGED
|
@@ -160,7 +160,7 @@ import { PresentationProvider, usePresentation } from "./lib/presentation.js";
|
|
|
160
160
|
import { useScrollActivity } from "./lib/use-scroll-activity.js";
|
|
161
161
|
import { useCopy } from "./lib/use-copy.js";
|
|
162
162
|
import { STATUS_TONES, STATUS_TONE_ICON } from "./lib/status-tone.js";
|
|
163
|
-
var SABLE_UI_VERSION = "0.8.
|
|
163
|
+
var SABLE_UI_VERSION = "0.8.2";
|
|
164
164
|
export {
|
|
165
165
|
ART_TOKENS,
|
|
166
166
|
CHART_SERIES_COUNT,
|
|
@@ -16,6 +16,7 @@ Use DataTable when a data array and column definition should render a sortable,
|
|
|
16
16
|
- Do: Provide stable rowKey values for dynamic data so selection and sorting do not jump.
|
|
17
17
|
- Do: Use sortable only on columns where ordering is obvious and useful.
|
|
18
18
|
- Do: Pair server-side datasets with Pagination and pass already-sorted/paged rows.
|
|
19
|
+
- Do: Move to TanStack Table, rendered with Sable's Table parts, once the table needs server-side sorting or filtering, column visibility or resizing, grouping, or virtualised rows. DataTable stays dependency-free and does not grow into a data grid; `sable docs tables-with-tanstack` maps one onto the other.
|
|
19
20
|
- Don't: Make every row clickable and also fill it with competing buttons. Choose a clear row interaction model.
|
|
20
21
|
- Don't: Use DataTable for heterogeneous cards or timeline content. A list or card layout will scan better.
|
|
21
22
|
|
|
@@ -4,6 +4,12 @@ Sable is on npm as `@sableui/react`. The installed version's `registry.json`
|
|
|
4
4
|
consume block has the exact recipe. Requirements: React 19, React DOM 19, and
|
|
5
5
|
Tailwind CSS 4 integrated into your build.
|
|
6
6
|
|
|
7
|
+
Sable works in any React 19 app and depends on no router, data or form
|
|
8
|
+
library. Starting from a blank slate, its suggested stack is React with
|
|
9
|
+
TypeScript on Vite, TanStack Router, TanStack Query, TanStack Form for forms with
|
|
10
|
+
validation logic, and TanStack Table once a table outgrows `DataTable`
|
|
11
|
+
(`sable docs tables-with-tanstack`). An existing app keeps its own.
|
|
12
|
+
|
|
7
13
|
## Install
|
|
8
14
|
|
|
9
15
|
```bash
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Task: tables with TanStack Table
|
|
2
|
+
|
|
3
|
+
`DataTable` is Sable's own table: a column list and a data array, with
|
|
4
|
+
client-side sorting, row click, checkbox selection and an empty state, and no
|
|
5
|
+
dependency. Use it until the table needs what it doesn't do:
|
|
6
|
+
|
|
7
|
+
- server-side sorting, filtering or paging over a large data set;
|
|
8
|
+
- column visibility, ordering, resizing or pinning;
|
|
9
|
+
- grouping, expanding rows, faceted filters;
|
|
10
|
+
- thousands of rows that need virtualising.
|
|
11
|
+
|
|
12
|
+
Then use **TanStack Table** (`@tanstack/react-table`) for the table's logic and
|
|
13
|
+
render it with Sable's `Table` parts, so it still looks and behaves like every
|
|
14
|
+
other Sable table: the same row hover, selected fill, edge insets in a flush
|
|
15
|
+
`Card`, and numeric columns. Sable doesn't depend on TanStack; this is the
|
|
16
|
+
pairing it suggests, and any headless table library renders the same way.
|
|
17
|
+
|
|
18
|
+
## The pairing
|
|
19
|
+
|
|
20
|
+
TanStack owns state (sorting, selection, pagination); Sable owns markup. Map them
|
|
21
|
+
one to one:
|
|
22
|
+
|
|
23
|
+
| TanStack | Sable |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `getHeaderGroups()` → header row | `TableHeader` › `TableRow` › `TableHead` |
|
|
26
|
+
| `getRowModel().rows` | `TableBody` › `TableRow` › `TableCell` |
|
|
27
|
+
| `row.getIsSelected()` | `data-selected` on the `TableRow` (the selected fill) |
|
|
28
|
+
| `column.getToggleSortingHandler()` | a ghost `Button size="xs"` in the head, `aria-sort` on the `TableHead` |
|
|
29
|
+
| selection column | `Checkbox` per row (named after the row) and in the head (`indeterminate` when some are checked) |
|
|
30
|
+
| `getPageCount()`, `setPageIndex()` | `Pagination` (1-based) |
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import {
|
|
34
|
+
type ColumnDef,
|
|
35
|
+
type RowSelectionState,
|
|
36
|
+
type SortingState,
|
|
37
|
+
flexRender,
|
|
38
|
+
getCoreRowModel,
|
|
39
|
+
getPaginationRowModel,
|
|
40
|
+
getSortedRowModel,
|
|
41
|
+
useReactTable,
|
|
42
|
+
} from "@tanstack/react-table"
|
|
43
|
+
import { Button, Checkbox, Icon, Pagination, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
44
|
+
import { useState } from "react"
|
|
45
|
+
|
|
46
|
+
type Ticket = { id: string; subject: string; customer: string; updated: Date }
|
|
47
|
+
|
|
48
|
+
const columns: ColumnDef<Ticket>[] = [
|
|
49
|
+
{
|
|
50
|
+
id: "select",
|
|
51
|
+
enableSorting: false,
|
|
52
|
+
header: ({ table }) => (
|
|
53
|
+
<Checkbox
|
|
54
|
+
aria-label={`Select all ${table.getRowModel().rows.length} tickets shown`}
|
|
55
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
56
|
+
indeterminate={table.getIsSomePageRowsSelected()}
|
|
57
|
+
onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
|
|
58
|
+
/>
|
|
59
|
+
),
|
|
60
|
+
cell: ({ row }) => (
|
|
61
|
+
<Checkbox
|
|
62
|
+
aria-label={`Select ${row.original.subject}`}
|
|
63
|
+
checked={row.getIsSelected()}
|
|
64
|
+
onCheckedChange={(checked) => row.toggleSelected(checked)}
|
|
65
|
+
/>
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
{ accessorKey: "subject", header: "Subject" },
|
|
69
|
+
{ accessorKey: "customer", header: "Customer" },
|
|
70
|
+
{ accessorKey: "updated", header: "Updated", cell: ({ getValue }) => getValue<Date>().toLocaleDateString() },
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
export function TicketTable({ data }: { data: Ticket[] }) {
|
|
74
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
75
|
+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
76
|
+
const table = useReactTable({
|
|
77
|
+
data,
|
|
78
|
+
columns,
|
|
79
|
+
getRowId: (ticket) => ticket.id,
|
|
80
|
+
state: { sorting, rowSelection },
|
|
81
|
+
onSortingChange: setSorting,
|
|
82
|
+
onRowSelectionChange: setRowSelection,
|
|
83
|
+
getCoreRowModel: getCoreRowModel(),
|
|
84
|
+
getSortedRowModel: getSortedRowModel(),
|
|
85
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
<Table>
|
|
91
|
+
<TableHeader>
|
|
92
|
+
{table.getHeaderGroups().map((group) => (
|
|
93
|
+
<TableRow key={group.id}>
|
|
94
|
+
{group.headers.map((header) => {
|
|
95
|
+
const sorted = header.column.getIsSorted()
|
|
96
|
+
const label = flexRender(header.column.columnDef.header, header.getContext())
|
|
97
|
+
return (
|
|
98
|
+
<TableHead
|
|
99
|
+
key={header.id}
|
|
100
|
+
className={header.column.id === "select" ? "w-0" : undefined}
|
|
101
|
+
aria-sort={sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : undefined}
|
|
102
|
+
>
|
|
103
|
+
{header.column.getCanSort() ? (
|
|
104
|
+
<Button variant="ghost" size="xs" bleed="start" onClick={header.column.getToggleSortingHandler()}>
|
|
105
|
+
{label}
|
|
106
|
+
<Icon name={sorted === "desc" ? "chevron-down" : "chevron-up"} className={sorted ? undefined : "opacity-60"} />
|
|
107
|
+
</Button>
|
|
108
|
+
) : (
|
|
109
|
+
label
|
|
110
|
+
)}
|
|
111
|
+
</TableHead>
|
|
112
|
+
)
|
|
113
|
+
})}
|
|
114
|
+
</TableRow>
|
|
115
|
+
))}
|
|
116
|
+
</TableHeader>
|
|
117
|
+
<TableBody>
|
|
118
|
+
{table.getRowModel().rows.map((row) => (
|
|
119
|
+
<TableRow key={row.id} data-selected={row.getIsSelected() ? "" : undefined}>
|
|
120
|
+
{row.getVisibleCells().map((cell) => (
|
|
121
|
+
<TableCell key={cell.id} className={cell.column.id === "select" ? "w-0" : undefined}>
|
|
122
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
123
|
+
</TableCell>
|
|
124
|
+
))}
|
|
125
|
+
</TableRow>
|
|
126
|
+
))}
|
|
127
|
+
</TableBody>
|
|
128
|
+
</Table>
|
|
129
|
+
<Pagination
|
|
130
|
+
page={table.getState().pagination.pageIndex + 1}
|
|
131
|
+
total={table.getPageCount()}
|
|
132
|
+
onPageChange={(page) => table.setPageIndex(page - 1)}
|
|
133
|
+
/>
|
|
134
|
+
</>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Inside a card, put the `Table` in `<CardContent flush>`, as with `DataTable`.
|
|
140
|
+
Mark a numeric column with `numeric` on its `TableHead` and `TableCell`s.
|
|
141
|
+
|
|
142
|
+
## Server-side data
|
|
143
|
+
|
|
144
|
+
Set `manualSorting`, `manualPagination` (and `manualFiltering`) with
|
|
145
|
+
`pageCount` or `rowCount`, and fetch with the table's state as the query key.
|
|
146
|
+
In a blank-slate app that is TanStack Query:
|
|
147
|
+
`useQuery({ queryKey: ["tickets", sorting, pagination], queryFn })`, passing
|
|
148
|
+
`data ?? []` to the table. Show `Skeleton` rows while the first page loads and
|
|
149
|
+
keep the previous page on screen while the next one does.
|
|
150
|
+
|
|
151
|
+
## Selection and bulk actions
|
|
152
|
+
|
|
153
|
+
Selection belongs to the rows shown: clear `rowSelection` when the filter or
|
|
154
|
+
view changes the data set. Above the table, render the bulk actions only while
|
|
155
|
+
something is selected, and keep a `role="status"` element mounted from the start
|
|
156
|
+
that says "3 of 25 selected", so a screen reader hears each change. The
|
|
157
|
+
`DataTable` docs show the same bar (`sable component DataTable --examples`).
|
package/llms-full.txt
CHANGED
|
@@ -1791,6 +1791,12 @@ Sable is on npm as `@sableui/react`. The installed version's `registry.json`
|
|
|
1791
1791
|
consume block has the exact recipe. Requirements: React 19, React DOM 19, and
|
|
1792
1792
|
Tailwind CSS 4 integrated into your build.
|
|
1793
1793
|
|
|
1794
|
+
Sable works in any React 19 app and depends on no router, data or form
|
|
1795
|
+
library. Starting from a blank slate, its suggested stack is React with
|
|
1796
|
+
TypeScript on Vite, TanStack Router, TanStack Query, TanStack Form for forms with
|
|
1797
|
+
validation logic, and TanStack Table once a table outgrows `DataTable`
|
|
1798
|
+
(`sable docs tables-with-tanstack`). An existing app keeps its own.
|
|
1799
|
+
|
|
1794
1800
|
## Install
|
|
1795
1801
|
|
|
1796
1802
|
```bash
|
|
@@ -2318,6 +2324,166 @@ lesson only within the contexts the observation supports.
|
|
|
2318
2324
|
|
|
2319
2325
|
---
|
|
2320
2326
|
|
|
2327
|
+
# Task: tables with TanStack Table
|
|
2328
|
+
|
|
2329
|
+
`DataTable` is Sable's own table: a column list and a data array, with
|
|
2330
|
+
client-side sorting, row click, checkbox selection and an empty state, and no
|
|
2331
|
+
dependency. Use it until the table needs what it doesn't do:
|
|
2332
|
+
|
|
2333
|
+
- server-side sorting, filtering or paging over a large data set;
|
|
2334
|
+
- column visibility, ordering, resizing or pinning;
|
|
2335
|
+
- grouping, expanding rows, faceted filters;
|
|
2336
|
+
- thousands of rows that need virtualising.
|
|
2337
|
+
|
|
2338
|
+
Then use **TanStack Table** (`@tanstack/react-table`) for the table's logic and
|
|
2339
|
+
render it with Sable's `Table` parts, so it still looks and behaves like every
|
|
2340
|
+
other Sable table: the same row hover, selected fill, edge insets in a flush
|
|
2341
|
+
`Card`, and numeric columns. Sable doesn't depend on TanStack; this is the
|
|
2342
|
+
pairing it suggests, and any headless table library renders the same way.
|
|
2343
|
+
|
|
2344
|
+
## The pairing
|
|
2345
|
+
|
|
2346
|
+
TanStack owns state (sorting, selection, pagination); Sable owns markup. Map them
|
|
2347
|
+
one to one:
|
|
2348
|
+
|
|
2349
|
+
| TanStack | Sable |
|
|
2350
|
+
|---|---|
|
|
2351
|
+
| `getHeaderGroups()` → header row | `TableHeader` › `TableRow` › `TableHead` |
|
|
2352
|
+
| `getRowModel().rows` | `TableBody` › `TableRow` › `TableCell` |
|
|
2353
|
+
| `row.getIsSelected()` | `data-selected` on the `TableRow` (the selected fill) |
|
|
2354
|
+
| `column.getToggleSortingHandler()` | a ghost `Button size="xs"` in the head, `aria-sort` on the `TableHead` |
|
|
2355
|
+
| selection column | `Checkbox` per row (named after the row) and in the head (`indeterminate` when some are checked) |
|
|
2356
|
+
| `getPageCount()`, `setPageIndex()` | `Pagination` (1-based) |
|
|
2357
|
+
|
|
2358
|
+
```tsx
|
|
2359
|
+
import {
|
|
2360
|
+
type ColumnDef,
|
|
2361
|
+
type RowSelectionState,
|
|
2362
|
+
type SortingState,
|
|
2363
|
+
flexRender,
|
|
2364
|
+
getCoreRowModel,
|
|
2365
|
+
getPaginationRowModel,
|
|
2366
|
+
getSortedRowModel,
|
|
2367
|
+
useReactTable,
|
|
2368
|
+
} from "@tanstack/react-table"
|
|
2369
|
+
import { Button, Checkbox, Icon, Pagination, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
2370
|
+
import { useState } from "react"
|
|
2371
|
+
|
|
2372
|
+
type Ticket = { id: string; subject: string; customer: string; updated: Date }
|
|
2373
|
+
|
|
2374
|
+
const columns: ColumnDef<Ticket>[] = [
|
|
2375
|
+
{
|
|
2376
|
+
id: "select",
|
|
2377
|
+
enableSorting: false,
|
|
2378
|
+
header: ({ table }) => (
|
|
2379
|
+
<Checkbox
|
|
2380
|
+
aria-label={`Select all ${table.getRowModel().rows.length} tickets shown`}
|
|
2381
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
2382
|
+
indeterminate={table.getIsSomePageRowsSelected()}
|
|
2383
|
+
onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
|
|
2384
|
+
/>
|
|
2385
|
+
),
|
|
2386
|
+
cell: ({ row }) => (
|
|
2387
|
+
<Checkbox
|
|
2388
|
+
aria-label={`Select ${row.original.subject}`}
|
|
2389
|
+
checked={row.getIsSelected()}
|
|
2390
|
+
onCheckedChange={(checked) => row.toggleSelected(checked)}
|
|
2391
|
+
/>
|
|
2392
|
+
),
|
|
2393
|
+
},
|
|
2394
|
+
{ accessorKey: "subject", header: "Subject" },
|
|
2395
|
+
{ accessorKey: "customer", header: "Customer" },
|
|
2396
|
+
{ accessorKey: "updated", header: "Updated", cell: ({ getValue }) => getValue<Date>().toLocaleDateString() },
|
|
2397
|
+
]
|
|
2398
|
+
|
|
2399
|
+
export function TicketTable({ data }: { data: Ticket[] }) {
|
|
2400
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
2401
|
+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
2402
|
+
const table = useReactTable({
|
|
2403
|
+
data,
|
|
2404
|
+
columns,
|
|
2405
|
+
getRowId: (ticket) => ticket.id,
|
|
2406
|
+
state: { sorting, rowSelection },
|
|
2407
|
+
onSortingChange: setSorting,
|
|
2408
|
+
onRowSelectionChange: setRowSelection,
|
|
2409
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2410
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2411
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2412
|
+
})
|
|
2413
|
+
|
|
2414
|
+
return (
|
|
2415
|
+
<>
|
|
2416
|
+
<Table>
|
|
2417
|
+
<TableHeader>
|
|
2418
|
+
{table.getHeaderGroups().map((group) => (
|
|
2419
|
+
<TableRow key={group.id}>
|
|
2420
|
+
{group.headers.map((header) => {
|
|
2421
|
+
const sorted = header.column.getIsSorted()
|
|
2422
|
+
const label = flexRender(header.column.columnDef.header, header.getContext())
|
|
2423
|
+
return (
|
|
2424
|
+
<TableHead
|
|
2425
|
+
key={header.id}
|
|
2426
|
+
className={header.column.id === "select" ? "w-0" : undefined}
|
|
2427
|
+
aria-sort={sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : undefined}
|
|
2428
|
+
>
|
|
2429
|
+
{header.column.getCanSort() ? (
|
|
2430
|
+
<Button variant="ghost" size="xs" bleed="start" onClick={header.column.getToggleSortingHandler()}>
|
|
2431
|
+
{label}
|
|
2432
|
+
<Icon name={sorted === "desc" ? "chevron-down" : "chevron-up"} className={sorted ? undefined : "opacity-60"} />
|
|
2433
|
+
</Button>
|
|
2434
|
+
) : (
|
|
2435
|
+
label
|
|
2436
|
+
)}
|
|
2437
|
+
</TableHead>
|
|
2438
|
+
)
|
|
2439
|
+
})}
|
|
2440
|
+
</TableRow>
|
|
2441
|
+
))}
|
|
2442
|
+
</TableHeader>
|
|
2443
|
+
<TableBody>
|
|
2444
|
+
{table.getRowModel().rows.map((row) => (
|
|
2445
|
+
<TableRow key={row.id} data-selected={row.getIsSelected() ? "" : undefined}>
|
|
2446
|
+
{row.getVisibleCells().map((cell) => (
|
|
2447
|
+
<TableCell key={cell.id} className={cell.column.id === "select" ? "w-0" : undefined}>
|
|
2448
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
2449
|
+
</TableCell>
|
|
2450
|
+
))}
|
|
2451
|
+
</TableRow>
|
|
2452
|
+
))}
|
|
2453
|
+
</TableBody>
|
|
2454
|
+
</Table>
|
|
2455
|
+
<Pagination
|
|
2456
|
+
page={table.getState().pagination.pageIndex + 1}
|
|
2457
|
+
total={table.getPageCount()}
|
|
2458
|
+
onPageChange={(page) => table.setPageIndex(page - 1)}
|
|
2459
|
+
/>
|
|
2460
|
+
</>
|
|
2461
|
+
)
|
|
2462
|
+
}
|
|
2463
|
+
```
|
|
2464
|
+
|
|
2465
|
+
Inside a card, put the `Table` in `<CardContent flush>`, as with `DataTable`.
|
|
2466
|
+
Mark a numeric column with `numeric` on its `TableHead` and `TableCell`s.
|
|
2467
|
+
|
|
2468
|
+
## Server-side data
|
|
2469
|
+
|
|
2470
|
+
Set `manualSorting`, `manualPagination` (and `manualFiltering`) with
|
|
2471
|
+
`pageCount` or `rowCount`, and fetch with the table's state as the query key.
|
|
2472
|
+
In a blank-slate app that is TanStack Query:
|
|
2473
|
+
`useQuery({ queryKey: ["tickets", sorting, pagination], queryFn })`, passing
|
|
2474
|
+
`data ?? []` to the table. Show `Skeleton` rows while the first page loads and
|
|
2475
|
+
keep the previous page on screen while the next one does.
|
|
2476
|
+
|
|
2477
|
+
## Selection and bulk actions
|
|
2478
|
+
|
|
2479
|
+
Selection belongs to the rows shown: clear `rowSelection` when the filter or
|
|
2480
|
+
view changes the data set. Above the table, render the bulk actions only while
|
|
2481
|
+
something is selected, and keep a `role="status"` element mounted from the start
|
|
2482
|
+
that says "3 of 25 selected", so a screen reader hears each change. The
|
|
2483
|
+
`DataTable` docs show the same bar (`sable component DataTable --examples`).
|
|
2484
|
+
|
|
2485
|
+
---
|
|
2486
|
+
|
|
2321
2487
|
# Task: use a portable preset
|
|
2322
2488
|
|
|
2323
2489
|
Bundled presets are maintained with Sable. Personal presets are app-owned JSON
|
|
@@ -12731,6 +12897,7 @@ Use DataTable when a data array and column definition should render a sortable,
|
|
|
12731
12897
|
- Do: Provide stable rowKey values for dynamic data so selection and sorting do not jump.
|
|
12732
12898
|
- Do: Use sortable only on columns where ordering is obvious and useful.
|
|
12733
12899
|
- Do: Pair server-side datasets with Pagination and pass already-sorted/paged rows.
|
|
12900
|
+
- Do: Move to TanStack Table, rendered with Sable's Table parts, once the table needs server-side sorting or filtering, column visibility or resizing, grouping, or virtualised rows. DataTable stays dependency-free and does not grow into a data grid; `sable docs tables-with-tanstack` maps one onto the other.
|
|
12734
12901
|
- Don't: Make every row clickable and also fill it with competing buttons. Choose a clear row interaction model.
|
|
12735
12902
|
- Don't: Use DataTable for heterogeneous cards or timeline content. A list or card layout will scan better.
|
|
12736
12903
|
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://sable.design/registry.schema.json",
|
|
3
3
|
"name": "@sableui/react",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.2",
|
|
5
5
|
"description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
|
|
6
6
|
"engine": {
|
|
7
7
|
"behavior": "@base-ui/react",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@import \"@sableui/react/styles.css\";",
|
|
29
29
|
"@source \"<node_modules>/@sableui/react/dist/**/*.{js,mjs}\";"
|
|
30
30
|
],
|
|
31
|
+
"stack": "Sable depends on no router, data or form library; an existing app keeps its own, and links take any router's Link through render. For a blank-slate app the suggested stack is Vite + TypeScript, TanStack Router, TanStack Query, TanStack Form for forms with validation logic, and TanStack Table once a table outgrows DataTable (sable docs tables-with-tanstack).",
|
|
31
32
|
"cssSources": "That @source ships every component's classes (about 32 KB gzipped). Once the app is built, `sable sources --write src/sable-sources.css` lists just the modules it imports; @import that file in place of the @source. sable check fails while it is stale.",
|
|
32
33
|
"fonts": {
|
|
33
34
|
"sans": "Figtree",
|
|
@@ -7471,6 +7472,10 @@
|
|
|
7471
7472
|
"guidance": "do",
|
|
7472
7473
|
"description": "Pair server-side datasets with Pagination and pass already-sorted/paged rows."
|
|
7473
7474
|
},
|
|
7475
|
+
{
|
|
7476
|
+
"guidance": "do",
|
|
7477
|
+
"description": "Move to TanStack Table, rendered with Sable's Table parts, once the table needs server-side sorting or filtering, column visibility or resizing, grouping, or virtualised rows. DataTable stays dependency-free and does not grow into a data grid; `sable docs tables-with-tanstack` maps one onto the other."
|
|
7478
|
+
},
|
|
7474
7479
|
{
|
|
7475
7480
|
"guidance": "dont",
|
|
7476
7481
|
"description": "Make every row clickable and also fill it with competing buttons. Choose a clear row interaction model."
|
package/skills/sable/SKILL.md
CHANGED
|
@@ -93,6 +93,15 @@ and registry `consume` block. Install from npm with the project's package
|
|
|
93
93
|
manager, for example `pnpm add @sableui/react`; no registry setup or token is
|
|
94
94
|
needed. Do not substitute an unrelated package with a similar name.
|
|
95
95
|
|
|
96
|
+
A project that already has a router, data layer or form library keeps it: Sable
|
|
97
|
+
depends on none, and its links, menus and nav take any router's link through
|
|
98
|
+
`render`. For a blank-slate app, start from Sable's suggested stack: React with
|
|
99
|
+
TypeScript on Vite, **TanStack Router** for routes and links, **TanStack Query**
|
|
100
|
+
for server data, **TanStack Form** when a form needs validation logic, and
|
|
101
|
+
**TanStack Table** once a table outgrows `DataTable` (`sable docs
|
|
102
|
+
tables-with-tanstack`). Say so in the brief, and use something else only when the
|
|
103
|
+
user or the product asks for it.
|
|
104
|
+
|
|
96
105
|
Complete setup before implementing UI:
|
|
97
106
|
|
|
98
107
|
- Match the installed package's React, React DOM, Tailwind, and Node requirements.
|