@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 +253 -1
- package/dist/index.cjs +343 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +340 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +338 -0
- package/package.json +1 -1
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
|
+
- **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
|