@bubble-design-system/ui 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Every visual decision — tone, color, brand, gray family, radius, density, typo
18
18
  >
19
19
  ```
20
20
 
21
- - **21 components** — Button, Input, Textarea, Checkbox, Radio, Switch, Select, Badge, Avatar, Divider, Modal, Toast, Tooltip, Tabs, Alert, DropdownMenu, Skeleton, Card, StatusPill, Segmented (plus Container + Grid layout primitives). Each wraps an [`@base-ui/react`](https://base-ui.com/) primitive where one exists — accessible by construction, styled with a single shipped stylesheet.
21
+ - **25 components** — Button, Input, Textarea, Checkbox, Radio, Switch, Select, Badge, Avatar, Divider, Modal, Toast, Tooltip, Tabs, Alert, DropdownMenu, Skeleton, Card, StatusPill, Segmented, Popover, DataTable, CommandPalette, Chat (plus Container + Grid layout primitives). Each wraps an [`@base-ui/react`](https://base-ui.com/) primitive where one exists — accessible by construction, styled with a single shipped stylesheet.
22
22
  - **A 3-layer, multi-theme token system** spanning color (light/dark · 3 gray families · 6 brand palettes including teal), **3 tones** (vivid · pastel · soft — soft is the signature look), radius (4 scales), density (3 scales), typography (2 font pairs), layered shadows, and motion.
23
23
  - **Live theme switching** via seven `data-*` attributes on any ancestor. Every CSS rule reads `var(--…)` at use-site, so swapping `data-tone="vivid"` for `data-tone="soft"` reflows the UI without re-rendering or rebuilding.
24
24
  - **No build dependency in consumer apps.** One CSS import. No PostCSS, no Tailwind, no preprocessor required.
@@ -37,8 +37,38 @@ The five rules every decision in Bubble traces back to:
37
37
 
38
38
  ---
39
39
 
40
+ ## Common UI Patterns
41
+
42
+ Before writing custom CSS against `var(--color-bg-*)`, `var(--radius-*)`, or `var(--shadow-*)` to build a surface, pill, menu, or banner — check this table. There is very likely already a component for it.
43
+
44
+ | You're building... | Use | Not... |
45
+ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
46
+ | A floating card / panel / content surface | [`Card`](#card) (`variant="elevated"` or `"muted"`) | a `<div>` styled with `--color-bg-secondary` + `--radius-lg` + `--shadow-md` |
47
+ | A chat message thread (bubbles, avatars, timestamps, read receipts, reactions, compose bar) | [`Chat`](#chat) — `ChatThread` / `ChatMessage` / `ChatCompose` | a custom flex layout styled from raw tokens, with hand-rolled grouping and scroll logic |
48
+ | A notification / toast / status banner | [`Toast`](#toast) + `useToast()` | a custom `toast-container` + `setTimeout` store |
49
+ | A status indicator (online, connected, error dot) | [`StatusPill`](#statuspill) + `StatusPill.Indicator` | a `::after` pseudo-element colored with `--color-bg-success-strong` |
50
+ | A small tag, count, or label pill | [`Badge`](#badge) | a custom pill `<div>` with `--radius-full` |
51
+ | A dropdown / context menu / select-from-list | [`DropdownMenu`](#dropdownmenu) | an absolutely-positioned custom `<div>` list |
52
+ | A command palette / "/" slash-command menu / ⌘K launcher | [`CommandPalette`](#commandpalette) + `useCommandPalette()` | a custom filtered dropdown with arrow-key handling |
53
+ | A popover anchored to a trigger (info panel, form, menu) | [`Popover`](#popover) | a custom absolutely-positioned `<div>` + manual outside-click handling |
54
+ | A hover tooltip | [`Tooltip`](#tooltip) | the native `title` attribute or a custom hover `<div>` |
55
+ | A loading placeholder | [`Skeleton`](#skeleton) | a custom `@keyframes pulse` `<div>` |
56
+ | A view switcher / segmented toggle | [`Segmented`](#segmented) | a custom button group with manual active-state CSS |
57
+ | A confirm dialog / modal | [`Modal`](#modal) | a custom backdrop + centered `<div>` |
58
+ | A responsive page section / 12-col grid | [`Container`](#container--grid) + [`Grid`](#container--grid) | custom `max-width` + flex/grid CSS |
59
+ | A tabbed interface | [`Tabs`](#tabs) | a custom button row + conditional render |
60
+ | A sortable / searchable / paginated table | [`DataTable`](#datatable) | a custom `<table>` + manual sort/filter/page state |
61
+ | A horizontal or vertical rule | [`Divider`](#divider) | `border-top: 1px solid var(--color-border-*)` |
62
+ | A user/avatar icon with fallback initials | [`Avatar`](#avatar) | a circular `<div>` + `<img>` with manual error handling |
63
+ | Form fields (text, multiline, checkbox, radio, switch, select) | [`Input`](#input) / [`Textarea`](#textarea) / [`Checkbox`](#checkbox) / [`Radio` / `RadioGroup`](#radio--radiogroup) / [`Switch`](#switch) / [`Select`](#select) | native elements styled from scratch |
64
+
65
+ **If nothing in this table fits**, that's a real gap — reach for tokens directly, and consider [opening an issue](https://github.com/mushroomgead/bubble-design-system/issues) so the pattern can become component #26.
66
+
67
+ ---
68
+
40
69
  ## Table of contents
41
70
 
71
+ - [Common UI Patterns](#common-ui-patterns)
42
72
  - [Tech stack](#tech-stack)
43
73
  - [Installation](#installation)
44
74
  - [Setup](#setup)
@@ -207,13 +237,20 @@ import {
207
237
  Badge,
208
238
  Button,
209
239
  Card,
240
+ ChatCompose,
241
+ ChatDateDivider,
242
+ ChatMessage,
243
+ ChatThread,
210
244
  Checkbox,
245
+ CommandPalette,
211
246
  Container,
247
+ DataTable,
212
248
  Divider,
213
249
  DropdownMenu,
214
250
  Grid,
215
251
  Input,
216
252
  Modal,
253
+ Popover,
217
254
  Radio,
218
255
  RadioGroup,
219
256
  Segmented,
@@ -225,6 +262,7 @@ import {
225
262
  Textarea,
226
263
  Toast,
227
264
  Tooltip,
265
+ useCommandPalette,
228
266
  useToast,
229
267
  cn,
230
268
  } from "@bubble-design-system/ui";
@@ -374,6 +412,77 @@ Compound component for floating-pill surface content.
374
412
 
375
413
  ---
376
414
 
415
+ ### Chat
416
+
417
+ Four standalone components for assembling a chat thread — no Base UI primitive needed, just plain markup. `ChatThread` is the scroll container, `ChatDateDivider` renders a centered separator label, `ChatMessage` is a single message bubble (with grouping, avatar, meta, reactions, and delivery status), and `ChatCompose` is an auto-growing input bar.
418
+
419
+ | Component | Description |
420
+ | ----------------- | ---------------------------------------------------------------- |
421
+ | `ChatThread` | Scrollable message-list container. |
422
+ | `ChatDateDivider` | Centered separator label (e.g. "Today"). |
423
+ | `ChatMessage` | A single message bubble — grouping/avatar/meta/reactions/status. |
424
+ | `ChatCompose` | Auto-growing textarea + send button, controlled or uncontrolled. |
425
+
426
+ **`ChatMessage` props:**
427
+
428
+ | Prop | Type | Default | Description |
429
+ | ----------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
430
+ | `side` | `"sent" \| "received"` | `"received"` | Which side of the thread the bubble renders on. |
431
+ | `position` | `"solo" \| "first" \| "middle" \| "last"` | `"solo"` | Position within a consecutive group — controls bubble-corner rounding and which messages show meta/avatar/status. |
432
+ | `avatar` | `ReactNode` | — | Avatar slot, shown for `received` messages at `solo`/`last`. |
433
+ | `name` | `string` | — | Sender name, shown at `solo`/`first` for received messages. |
434
+ | `time` | `string` | — | Timestamp, shown at `solo`/`first`. |
435
+ | `status` | `"sending" \| "sent" \| "delivered" \| "read"` | — | Delivery-status row, shown for `sent` messages at `solo`/`last`. |
436
+ | `reactions` | `{ emoji: string; count?: number; mine?: boolean }[]` | — | Reaction pills rendered under the bubble. |
437
+ | `className` | `string` | — | Extra classes. |
438
+
439
+ **`ChatCompose` props:**
440
+
441
+ | Prop | Type | Default | Description |
442
+ | ------------- | ----------------------------------------------- | -------------------- | ---------------------------------------------------------------- |
443
+ | `value` | `string` | — | Controlled value. Omit for uncontrolled (internal state). |
444
+ | `onChange` | `(e: ChangeEvent<HTMLTextAreaElement>) => void` | — | Change handler (controlled mode). |
445
+ | `onSend` | `(value: string) => void` | — | Called with the trimmed text on send (Enter or the send button). |
446
+ | `placeholder` | `string` | `"Write a message…"` | Textarea placeholder. |
447
+ | `avatar` | `ReactNode` | — | Avatar slot rendered before the input. |
448
+ | `disabled` | `boolean` | `false` | Disables the textarea and send button. |
449
+ | `className` | `string` | — | Extra classes. |
450
+
451
+ ```tsx
452
+ <ChatThread>
453
+ <ChatDateDivider>Today</ChatDateDivider>
454
+
455
+ <ChatMessage
456
+ side="received"
457
+ name="Lena Torres"
458
+ time="9:41 AM"
459
+ avatar={
460
+ <Avatar>
461
+ <Avatar.Fallback>LT</Avatar.Fallback>
462
+ </Avatar>
463
+ }
464
+ reactions={[{ emoji: "👍", count: 2, mine: true }]}
465
+ >
466
+ Hey — got a minute to look at the new Card variants?
467
+ </ChatMessage>
468
+
469
+ <ChatMessage side="sent" status="read">
470
+ Sure, pulling them up now.
471
+ </ChatMessage>
472
+ </ChatThread>
473
+
474
+ <ChatCompose
475
+ avatar={
476
+ <Avatar>
477
+ <Avatar.Fallback>AK</Avatar.Fallback>
478
+ </Avatar>
479
+ }
480
+ onSend={(text) => sendMessage(text)}
481
+ />
482
+ ```
483
+
484
+ ---
485
+
377
486
  ### Checkbox
378
487
 
379
488
  Wraps `@base-ui/react/checkbox`. Supports checked, unchecked, and indeterminate states with built-in SVG indicators.
@@ -392,6 +501,50 @@ Wraps `@base-ui/react/checkbox`. Supports checked, unchecked, and indeterminate
392
501
 
393
502
  ---
394
503
 
504
+ ### CommandPalette
505
+
506
+ Built on `@base-ui/react/dialog`. A fuzzy-searchable, grouped command list with arrow-key navigation and Enter-to-select — the "/" or ⌘K menu pattern. Pair it with `useCommandPalette()`, a small hook that owns `open` state and registers a global `⌘K` / `Ctrl+K` listener.
507
+
508
+ **`CommandPalette` props:**
509
+
510
+ | Prop | Type | Default | Description |
511
+ | -------------- | ------------------------------------ | ------------------ | -------------------------------------------------------------------------------- |
512
+ | `open` | `boolean` | — | Whether the palette is open. |
513
+ | `onOpenChange` | `(open: boolean) => void` | — | Called when the palette should open or close. |
514
+ | `groups` | `CommandPaletteGroup[]` | `[]` | Grouped, filterable command items. |
515
+ | `placeholder` | `string` | `"Type to search"` | Search input placeholder. |
516
+ | `onSelect` | `(item: CommandPaletteItem) => void` | — | Called when an item is chosen (Enter or click), after the item's own `onSelect`. |
517
+ | `className` | `string` | — | Extra classes on the popup. |
518
+
519
+ **`CommandPaletteGroup`** — `{ label?: string; items: CommandPaletteItem[] }`
520
+
521
+ **`CommandPaletteItem`** — `{ id: string; label: string; description?: string; icon?: ReactNode; shortcut?: string; keywords?: string[]; onSelect?: () => void }`
522
+
523
+ ```tsx
524
+ const palette = useCommandPalette(); // owns `open` + ⌘K/Ctrl+K toggle
525
+
526
+ <CommandPalette
527
+ open={palette.open}
528
+ onOpenChange={palette.setOpen}
529
+ groups={[
530
+ {
531
+ label: "Navigation",
532
+ items: [
533
+ { id: "home", label: "Go home", shortcut: "G H" },
534
+ { id: "settings", label: "Settings", keywords: ["preferences"] },
535
+ ],
536
+ },
537
+ {
538
+ label: "Actions",
539
+ items: [{ id: "new", label: "New item", onSelect: () => createItem() }],
540
+ },
541
+ ]}
542
+ onSelect={(item) => console.log("selected", item.id)}
543
+ />;
544
+ ```
545
+
546
+ ---
547
+
395
548
  ### Container + Grid
396
549
 
397
550
  Layout primitives. `Container` centers content and applies page margins. `Grid` is a 12-column grid; `Grid.Col` spans columns with optional responsive overrides.
@@ -421,6 +574,55 @@ Layout primitives. `Container` centers content and applies page margins. `Grid`
421
574
 
422
575
  ---
423
576
 
577
+ ### DataTable
578
+
579
+ Generic, client-side `DataTable<T extends { id: string | number }>` — search across all columns, sortable columns, row selection (header select-all + per-row, reusing `Checkbox`), and pagination.
580
+
581
+ **Props:**
582
+
583
+ | Prop | Type | Default | Description |
584
+ | ------------ | ---------------------- | ------- | ------------------------------------------------------- |
585
+ | `columns` | `DataTableColumn<T>[]` | — | Column definitions, rendered in order. |
586
+ | `data` | `T[]` | — | Rows. Each row must have an `id: string \| number`. |
587
+ | `selectable` | `boolean` | `true` | Shows the select-all / per-row checkboxes. |
588
+ | `searchable` | `boolean` | `true` | Shows the search input; filters across all column keys. |
589
+ | `pageSize` | `number` | `10` | Rows per page. |
590
+ | `actions` | `ReactNode` | — | Right-aligned toolbar content (e.g. a "New" button). |
591
+ | `className` | `string` | — | Extra classes. |
592
+
593
+ **`DataTableColumn<T>`:**
594
+
595
+ | Field | Type | Description |
596
+ | ---------------- | --------------------------------------- | ---------------------------------------------- |
597
+ | `key` | `string` | Property name read off each row. |
598
+ | `label` | `string` | Column header text. |
599
+ | `sortable` | `boolean` | Default `true` — click the header to sort. |
600
+ | `render` | `(value: unknown, row: T) => ReactNode` | Custom cell renderer. |
601
+ | `width` | `string` | CSS width for the `<th>` / `<td>`. |
602
+ | `muted` / `mono` | `boolean` | Styles the cell text as secondary / monospace. |
603
+
604
+ ```tsx
605
+ <DataTable
606
+ columns={[
607
+ { key: "name", label: "Name" },
608
+ { key: "email", label: "Email", muted: true },
609
+ {
610
+ key: "status",
611
+ label: "Status",
612
+ render: (value) => (
613
+ <StatusPill intent={value === "active" ? "success" : "neutral"}>
614
+ {String(value)}
615
+ </StatusPill>
616
+ ),
617
+ },
618
+ ]}
619
+ data={users}
620
+ actions={<Button size="sm">Invite</Button>}
621
+ />
622
+ ```
623
+
624
+ ---
625
+
424
626
  ### Divider
425
627
 
426
628
  Horizontal or vertical separator with a semantic role from `@base-ui/react/separator`.
@@ -545,6 +747,53 @@ Compound component built on `@base-ui/react/dialog`. Renders into a portal, with
545
747
 
546
748
  ---
547
749
 
750
+ ### Popover
751
+
752
+ Compound component built on `@base-ui/react/popover`. Like `Tooltip`, but for richer content (forms, lists, multi-element panels) that stays open until dismissed.
753
+
754
+ | Sub-component | Description |
755
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------ |
756
+ | `Popover.Root` | State container. |
757
+ | `Popover.Trigger` | The element that opens the popover. |
758
+ | `Popover.Content` | The portalled popup — pre-wires Portal → Positioner → Popup, plus an optional arrow. |
759
+ | `Popover.Title` / `Popover.Description` | Accessible heading / description, wired to ARIA via Base UI. |
760
+ | `Popover.Close` | Closes the popover when clicked. |
761
+ | `Popover.Header` / `Popover.Body` / `Popover.Footer` | Layout slots for `Content`'s children. |
762
+
763
+ **`Popover.Content` props:**
764
+
765
+ | Prop | Type | Default | Description |
766
+ | ------------ | ---------------------------------------- | ---------- | ---------------------------------------- |
767
+ | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Preferred side. |
768
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the side. |
769
+ | `sideOffset` | `number` | `10` | Distance from the trigger. |
770
+ | `showArrow` | `boolean` | `true` | Renders a caret pointing at the trigger. |
771
+ | `className` | `string` | — | Extra classes on the popup. |
772
+
773
+ ```tsx
774
+ <Popover.Root>
775
+ <Popover.Trigger render={<Button variant="secondary">Filters</Button>} />
776
+ <Popover.Content>
777
+ <Popover.Header>
778
+ <Popover.Title>Column visibility</Popover.Title>
779
+ </Popover.Header>
780
+ <Popover.Body>…</Popover.Body>
781
+ <Popover.Footer>
782
+ <Popover.Close
783
+ render={
784
+ <Button size="sm" variant="ghost">
785
+ Reset
786
+ </Button>
787
+ }
788
+ />
789
+ <Button size="sm">Apply</Button>
790
+ </Popover.Footer>
791
+ </Popover.Content>
792
+ </Popover.Root>
793
+ ```
794
+
795
+ ---
796
+
548
797
  ### Radio / RadioGroup
549
798
 
550
799
  Wraps `@base-ui/react/radio` and `@base-ui/react/radio-group`.
@@ -825,6 +1074,9 @@ cn("my-card", isActive && "my-card--active", className);
825
1074
 
826
1075
  ## Design tokens
827
1076
 
1077
+ > **About to write `var(--color-bg-*)` / `var(--radius-*)` / `var(--shadow-*)` on a hand-authored `<div>`?**
1078
+ > Tokens are how you _theme_ Bubble's components and the escape hatch for genuine gaps — they are not a second, parallel "build it yourself" API. Check [Common UI Patterns](#common-ui-patterns) first; the surface/pill/menu/banner you're about to build very likely already exists as a themed, accessible component.
1079
+
828
1080
  All tokens are CSS custom properties defined in `src/tokens.css`. Reference them directly in your CSS with `var(--…)`. The semantic tokens (those prefixed `--color-bg-*`, `--color-text-*`, `--color-border-*`) re-resolve automatically when an ancestor `data-*` attribute changes.
829
1081
 
830
1082
  ### Color tokens