@brokenrubik/ui 0.0.0-stage → 0.1.1
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 +264 -2
- package/dist/components/accordion.d.ts +13 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +25 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert-dialog.d.ts +21 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.js +46 -0
- package/dist/components/alert-dialog.js.map +1 -0
- package/dist/components/alert.d.ts +24 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js +42 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +27 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +53 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +40 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +18 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +39 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +13 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +68 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +15 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +31 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/checkbox.d.ts +6 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +12 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/command.d.ts +24 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.js +41 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +18 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +23 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +45 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +34 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +61 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/empty-state.d.ts +13 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +25 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/field.d.ts +24 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.js +34 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +18 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +14 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.js +31 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +6 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +11 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/pagination.d.ts +18 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +33 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +12 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +29 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +17 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +26 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +7 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +14 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/select.d.ts +20 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +46 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +6 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +11 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +19 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.js +47 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +9 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sonner.d.ts +13 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +40 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +11 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +24 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +5 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +10 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toggle-group.d.ts +11 -0
- package/dist/components/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group.js +18 -0
- package/dist/components/toggle-group.js.map +1 -0
- package/dist/components/tooltip.d.ts +13 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +24 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tone.d.ts +8 -0
- package/dist/lib/tone.d.ts.map +1 -0
- package/dist/lib/tone.js +14 -0
- package/dist/lib/tone.js.map +1 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles/base.css +37 -0
- package/dist/styles/fonts.css +16 -0
- package/dist/styles/styles.css +14 -0
- package/dist/styles/theme.css +170 -0
- package/dist/styles/tokens.css +228 -0
- package/docs/design-handoff.md +112 -0
- package/docs/figma-vs-tailwind.md +135 -0
- package/docs/visual-testing.md +156 -0
- package/package.json +97 -4
- package/src/components/accordion.tsx +66 -0
- package/src/components/alert-dialog.tsx +139 -0
- package/src/components/alert.tsx +87 -0
- package/src/components/avatar.tsx +105 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/button.tsx +70 -0
- package/src/components/calendar.tsx +131 -0
- package/src/components/card.tsx +72 -0
- package/src/components/checkbox.tsx +31 -0
- package/src/components/command.tsx +149 -0
- package/src/components/date-picker.tsx +86 -0
- package/src/components/dialog.tsx +126 -0
- package/src/components/dropdown-menu.tsx +225 -0
- package/src/components/empty-state.tsx +63 -0
- package/src/components/field.tsx +79 -0
- package/src/components/input.tsx +49 -0
- package/src/components/kbd.tsx +48 -0
- package/src/components/label.tsx +22 -0
- package/src/components/pagination.tsx +94 -0
- package/src/components/popover.tsx +65 -0
- package/src/components/progress.tsx +52 -0
- package/src/components/radio-group.tsx +42 -0
- package/src/components/select.tsx +167 -0
- package/src/components/separator.tsx +29 -0
- package/src/components/sheet.tsx +112 -0
- package/src/components/skeleton.tsx +16 -0
- package/src/components/sonner.tsx +58 -0
- package/src/components/switch.tsx +27 -0
- package/src/components/tabs.tsx +56 -0
- package/src/components/textarea.tsx +17 -0
- package/src/components/toggle-group.tsx +42 -0
- package/src/components/tooltip.tsx +55 -0
- package/src/index.ts +35 -0
- package/src/lib/tone.ts +16 -0
- package/src/lib/utils.ts +46 -0
- package/src/styles/base.css +37 -0
- package/src/styles/fonts.css +16 -0
- package/src/styles/styles.css +14 -0
- package/src/styles/theme.css +170 -0
- package/src/styles/tokens.css +228 -0
package/README.md
CHANGED
|
@@ -1,3 +1,265 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @brokenrubik/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The **BR Admin Library** (Figma) as a React component package: shadcn-style components and a
|
|
4
|
+
Tailwind CSS v4 theme with the exact Figma values. Every BrokenRubik app (Proma, Timma, Mara,
|
|
5
|
+
Folio, DocuBuilder and the next one) installs this package and gets the same look. Each app can
|
|
6
|
+
still change or extend it.
|
|
7
|
+
|
|
8
|
+
- **Docs site with live examples and code:** https://brokenrubik.github.io/ui/
|
|
9
|
+
- **Source of truth:** Figma file `ONZyUryZr526mtbex2HVPk` (BR Admin Library), pages
|
|
10
|
+
*Foundations* (`2004:2`) and *Components · Base* (`2004:3`).
|
|
11
|
+
- **What is different from Tailwind and shadcn:** [docs/figma-vs-tailwind.md](docs/figma-vs-tailwind.md)
|
|
12
|
+
- **Stack:** React 18 or 19, Tailwind CSS v4, Radix UI, class-variance-authority,
|
|
13
|
+
tailwind-merge, lucide-react, cmdk, sonner, react-day-picker, IBM Plex fonts (included). ESM only. Every component that
|
|
14
|
+
needs the browser has `'use client'`, so it works in the Next.js App Router.
|
|
15
|
+
|
|
16
|
+
Contents: [Install](#install) · [Set up](#set-up-once-per-app) ·
|
|
17
|
+
[How the Tailwind config works](#how-the-tailwind-config-works) · [Use](#use) ·
|
|
18
|
+
[Extend and override](#extend-and-override) · [Adopt in an existing app](#adopt-in-an-existing-app) ·
|
|
19
|
+
[Rules](#rules-that-prevent-bugs) · [Contribute](#contribute)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @brokenrubik/ui # or: npm install @brokenrubik/ui
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Your app must already have `react`, `react-dom` and `tailwindcss` 4.x (peer dependencies). The
|
|
28
|
+
package installs all other libraries itself (Radix, sonner, cmdk, lucide-react…) and the IBM
|
|
29
|
+
Plex font files.
|
|
30
|
+
|
|
31
|
+
## Set up (once per app)
|
|
32
|
+
|
|
33
|
+
### 1. CSS
|
|
34
|
+
|
|
35
|
+
Put these two lines at the top of the app's main stylesheet (`globals.css`, `index.css`):
|
|
36
|
+
|
|
37
|
+
```css
|
|
38
|
+
@import "tailwindcss";
|
|
39
|
+
@import "@brokenrubik/ui/styles.css";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The order is important: `tailwindcss` first, then the package. Nothing else is necessary. The
|
|
43
|
+
next section tells what these two lines do.
|
|
44
|
+
|
|
45
|
+
### 2. Fonts (IBM Plex Sans and IBM Plex Mono)
|
|
46
|
+
|
|
47
|
+
You do not need to do anything. `styles.css` loads IBM Plex Sans (400, 500, 600) and IBM Plex
|
|
48
|
+
Mono (400, 500) from the package. Your bundler (Next.js, Vite) copies the font files into the
|
|
49
|
+
build. The browser downloads only the weights and the character sets that the page uses.
|
|
50
|
+
|
|
51
|
+
If the app already loads the fonts, remove that code (`next/font` IBM Plex, `@fontsource`
|
|
52
|
+
imports, a Google Fonts `<link>`), so that the fonts do not load two times.
|
|
53
|
+
|
|
54
|
+
To load the fonts in a different way (for example `next/font`, to get self-hosting with
|
|
55
|
+
preload), set the CSS variables `--font-plex-sans` and `--font-plex-mono` to your font
|
|
56
|
+
families. The package fonts are then never downloaded:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Next.js: app/layout.tsx (optional)
|
|
60
|
+
import { IBM_Plex_Sans, IBM_Plex_Mono } from 'next/font/google';
|
|
61
|
+
|
|
62
|
+
const sans = IBM_Plex_Sans({ subsets: ['latin'], weight: ['400', '500', '600'], variable: '--font-plex-sans' });
|
|
63
|
+
const mono = IBM_Plex_Mono({ subsets: ['latin'], weight: ['400', '500'], variable: '--font-plex-mono' });
|
|
64
|
+
|
|
65
|
+
<html lang="en" className={`${sans.variable} ${mono.variable}`}>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Dark mode and the light sidebar
|
|
69
|
+
|
|
70
|
+
- Dark theme: put the class `dark` on `<html>`. The `dark:` variant follows this class, not
|
|
71
|
+
the operating system setting.
|
|
72
|
+
- "Sidebar claro" (light sidebar, in the light and the dark theme): put the class
|
|
73
|
+
`sidebar-light` on `<html>`. With `dark` too, you get the Figma mode "Dark · Sidebar claro".
|
|
74
|
+
- On the sidebar element, put the class `sidebar-surface`. Then the components inside the
|
|
75
|
+
sidebar use the sidebar colors (light text on navy).
|
|
76
|
+
|
|
77
|
+
Set these classes before the first paint (an inline script in `<head>`), or the page flashes.
|
|
78
|
+
|
|
79
|
+
### 4. Providers
|
|
80
|
+
|
|
81
|
+
Add these once, in the app root:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Toaster, TooltipProvider } from '@brokenrubik/ui';
|
|
85
|
+
|
|
86
|
+
<TooltipProvider>
|
|
87
|
+
{children}
|
|
88
|
+
<Toaster theme={isDark ? 'dark' : 'light'} />
|
|
89
|
+
</TooltipProvider>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## How the Tailwind config works
|
|
93
|
+
|
|
94
|
+
Tailwind v4 has **no `tailwind.config.js`**. The configuration is CSS, and
|
|
95
|
+
`@brokenrubik/ui/styles.css` is the shared configuration. When your app's Tailwind compiles
|
|
96
|
+
`globals.css`, it reads the package CSS as if you wrote it in your own file. There is one
|
|
97
|
+
CSS output for the app, with your classes and the package classes together.
|
|
98
|
+
|
|
99
|
+
`styles.css` contains five parts:
|
|
100
|
+
|
|
101
|
+
| Part | File | What it does |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| Fonts | `fonts.css` | `@font-face` rules for IBM Plex Sans and IBM Plex Mono (files from the `@fontsource` packages, which the package installs). |
|
|
104
|
+
| Tokens | `tokens.css` | CSS variables with the Figma values: `--primary: #9547ff`, `--radius: .5rem`… for light (`:root`), dark (`.dark`), `.sidebar-light` and `.sidebar-surface`. Plain CSS, no Tailwind. |
|
|
105
|
+
| Theme | `theme.css` | Tells Tailwind about the tokens. `@theme` makes `bg-primary`, `text-muted-foreground`, `rounded-lg`, `shadow-md`, `text-body`… `@custom-variant dark` makes `dark:` follow the `.dark` class. `@source "../"` tells Tailwind to scan the package files for class names. |
|
|
106
|
+
| Base | `base.css` | Page defaults: IBM Plex Sans, 13 px body text (Figma Body/Default), border color. |
|
|
107
|
+
| Animations | `tw-animate-css` | Open and close animations for menus, dialogs and sheets. |
|
|
108
|
+
|
|
109
|
+
Three things follow from this:
|
|
110
|
+
|
|
111
|
+
1. **Tailwind must see the package classes.** The `@source` line in `theme.css` does this
|
|
112
|
+
automatically. If the components show without styles, the import is missing or is above
|
|
113
|
+
`@import "tailwindcss"`.
|
|
114
|
+
2. **Your CSS after the import wins.** A later declaration of the same variable replaces the
|
|
115
|
+
earlier one. This is how you extend and override (next section).
|
|
116
|
+
3. **Your own classes and the package classes use the same scale.** `bg-primary` in your code is
|
|
117
|
+
the same violet as in the Button. `text-body` in your code is the same 13/20 text as in the
|
|
118
|
+
Input.
|
|
119
|
+
|
|
120
|
+
If an app needs less, import the parts:
|
|
121
|
+
|
|
122
|
+
```css
|
|
123
|
+
@import "tailwindcss";
|
|
124
|
+
@import "@brokenrubik/ui/tokens.css"; /* variables only */
|
|
125
|
+
@import "@brokenrubik/ui/theme.css"; /* Tailwind mapping + @source */
|
|
126
|
+
@import "tw-animate-css"; /* needed by the overlays */
|
|
127
|
+
/* no base.css: the app keeps its own body styles */
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`tokens.css` alone works without Tailwind (for example in a plain CSS app or in app-shell).
|
|
131
|
+
|
|
132
|
+
Tailwind v3 apps cannot use this package.
|
|
133
|
+
|
|
134
|
+
## Use
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { Badge, Button, Field, FieldDescription, FieldLabel, Input, toast } from '@brokenrubik/ui';
|
|
138
|
+
// or one file: import { Button } from '@brokenrubik/ui/components/button';
|
|
139
|
+
|
|
140
|
+
<Button onClick={() => toast.success('Saved')}>Save changes</Button>
|
|
141
|
+
<Button variant="outline" size="sm">Cancel</Button>
|
|
142
|
+
<Badge tone="success">Done</Badge>
|
|
143
|
+
<Field>
|
|
144
|
+
<FieldLabel htmlFor="name">Project name</FieldLabel>
|
|
145
|
+
<Input id="name" />
|
|
146
|
+
<FieldDescription>Shown on the sidebar and in links.</FieldDescription>
|
|
147
|
+
</Field>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The components have the shadcn/ui API (the same parts, `asChild`, `data-slot`), so shadcn
|
|
151
|
+
examples work. The [docs site](https://brokenrubik.github.io/ui/) shows every
|
|
152
|
+
component with its code.
|
|
153
|
+
|
|
154
|
+
| Figma | Code |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| Button, Icon Button | `Button` — `variant`: default · secondary · outline · ghost · destructive · link; `size`: default · sm · icon · icon-sm |
|
|
157
|
+
| Badge | `Badge` — `tone` (or `variant`): neutral · primary · success · warning · destructive · highlight · info |
|
|
158
|
+
| Avatar | `Avatar` (`size` 20 · 28), `AvatarImage`, `AvatarFallback` (`tone`); `avatarToneFor(name)` |
|
|
159
|
+
| Key Chip | `Kbd` — `variant`: highlight · muted; `size` 20 · 24 |
|
|
160
|
+
| Input, Textarea, Select, Date Picker | `Input` (`startIcon`), `Textarea`, `Select*`, `DatePicker` |
|
|
161
|
+
| Form Field | `Field` (`invalid`, `disabled`), `FieldLabel`, `FieldDescription`, `FieldError` |
|
|
162
|
+
| Checkbox, Radio, Switch | `Checkbox`, `RadioGroup` + `RadioGroupItem`, `Switch` |
|
|
163
|
+
| Tab, Toggle Group | `Tabs*` (underline), `ToggleGroup` + `ToggleGroupItem` (segmented) |
|
|
164
|
+
| Menu Item, Dropdown Menu | `DropdownMenu*` (item `variant="destructive"`) |
|
|
165
|
+
| Tooltip, Popover | `Tooltip*`, `Popover*` (`PopoverHeader`, `PopoverTitle`, `PopoverDescription`) |
|
|
166
|
+
| Dialog (Default, Destructive) | `Dialog*`, `AlertDialog*` (`AlertDialogAction variant="destructive"`) |
|
|
167
|
+
| Sheet, Command | `Sheet*` (`side`), `Command*`, `CommandDialog` |
|
|
168
|
+
| Alert, Toast | `Alert` (`tone`, default icon per tone), `Toaster` + `toast()` |
|
|
169
|
+
| Card, Empty State | `Card*`, `EmptyState*` |
|
|
170
|
+
| Progress, Skeleton, Separator | `Progress` (`tone`), `Skeleton`, `Separator` |
|
|
171
|
+
| Calendar, Pagination, Accordion | `Calendar`, `Pagination*`, `Accordion*` |
|
|
172
|
+
|
|
173
|
+
**`tone` and `variant`.** Figma calls the color of Badge, Alert, Progress and Avatar "Tone";
|
|
174
|
+
shadcn calls it `variant`. These components accept both names. `variant` also accepts the
|
|
175
|
+
shadcn values: `<Badge variant="secondary">` is the neutral badge, and `<Alert variant="default">`
|
|
176
|
+
is the info alert. If you set both props, `tone` wins.
|
|
177
|
+
|
|
178
|
+
## Extend and override
|
|
179
|
+
|
|
180
|
+
Put your changes **after** the package import. From the smallest change to the largest:
|
|
181
|
+
|
|
182
|
+
1. **Change a token.** Every component follows.
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
@import "tailwindcss";
|
|
186
|
+
@import "@brokenrubik/ui/styles.css";
|
|
187
|
+
|
|
188
|
+
:root { --radius: 0.375rem; } /* all corners, from one value */
|
|
189
|
+
.dark { --primary: #a78bfa; }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
2. **Add tokens and utilities** in your own `@theme` block.
|
|
193
|
+
|
|
194
|
+
```css
|
|
195
|
+
@theme {
|
|
196
|
+
--color-brand-timma: #c2581c; /* gives bg-brand-timma, text-brand-timma, border-brand-timma… */
|
|
197
|
+
--text-hero: 40px; /* gives text-hero */
|
|
198
|
+
--text-hero--line-height: 48px;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
If you add a `text-*` size, also make a `cn` that knows it (see the rule about `cn` below):
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// lib/utils.ts
|
|
206
|
+
import { createCn } from '@brokenrubik/ui';
|
|
207
|
+
export const cn = createCn({ extend: { theme: { text: ['hero'] } } });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
3. **Change one use** with `className`. The last class wins: `<Button className="h-9 px-4">`.
|
|
211
|
+
|
|
212
|
+
4. **Use the styles on another element.** Each component exports its recipe
|
|
213
|
+
(`buttonVariants`, `badgeVariants`, `alertVariants`, `kbdVariants`…) and the shared class
|
|
214
|
+
strings (`controlClassName`, `menuItemClassName`, `menuContentClassName`, `overlayClassName`).
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
<Link className={buttonVariants({ variant: 'outline', size: 'sm' })} href="/">Home</Link>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
5. **A new variant for one app.** Wrap the package component in the app:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
// components/ui/button.tsx (in the app)
|
|
224
|
+
import { Button as BaseButton, cn, type ButtonProps } from '@brokenrubik/ui';
|
|
225
|
+
|
|
226
|
+
export function Button({ variant, className, ...props }: Omit<ButtonProps, 'variant'> & { variant?: ButtonProps['variant'] | 'brand' }) {
|
|
227
|
+
if (variant === 'brand') return <BaseButton className={cn('bg-brand-timma text-white hover:bg-brand-timma/90', className)} {...props} />;
|
|
228
|
+
return <BaseButton variant={variant} className={className} {...props} />;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
When a second app needs the same variant, add it to Figma and to this package.
|
|
233
|
+
|
|
234
|
+
## Adopt in an existing app
|
|
235
|
+
|
|
236
|
+
1. `pnpm add @brokenrubik/ui`.
|
|
237
|
+
2. In `globals.css`, remove the token blocks (`:root`, `.dark`, `.sidebar-light`,
|
|
238
|
+
`.sidebar-surface`, `@theme inline`) and add `@import "@brokenrubik/ui/styles.css";` after
|
|
239
|
+
`@import "tailwindcss";`. Keep only the tokens that belong to the app, after the import.
|
|
240
|
+
3. Remove the app's IBM Plex setup (`next/font` IBM Plex in `layout.tsx`, `@fontsource`
|
|
241
|
+
imports and packages, Google Fonts `<link>`). The package loads the fonts.
|
|
242
|
+
4. Replace `cn` in `lib/utils.ts` with `export { cn } from '@brokenrubik/ui';`.
|
|
243
|
+
5. Replace the `components/ui/*` imports with `@brokenrubik/ui`, one component at a time. Keep
|
|
244
|
+
an app file only for a real app variant (step 5 above).
|
|
245
|
+
6. Replace `import { toast } from 'sonner'` with `import { toast } from '@brokenrubik/ui'`.
|
|
246
|
+
7. Check light, dark, light sidebar and phone width against Figma.
|
|
247
|
+
|
|
248
|
+
The differences that each app has today: [docs/figma-vs-tailwind.md › Drift](docs/figma-vs-tailwind.md#7-drift-found-in-the-apps-2026-11-09).
|
|
249
|
+
|
|
250
|
+
## Rules that prevent bugs
|
|
251
|
+
|
|
252
|
+
- **Use the package `cn`.** The Figma text styles are classes such as `text-body`. A plain
|
|
253
|
+
shadcn `cn` thinks that `text-body` is a color and removes it when you also write a color
|
|
254
|
+
(`cn('text-body', 'text-muted-foreground')`). The package `cn` knows the names.
|
|
255
|
+
- **Import `toast` from `@brokenrubik/ui`.** A `toast` from another copy of `sonner` does not
|
|
256
|
+
reach the package `<Toaster />`.
|
|
257
|
+
- **Never use `primary` for text.** Use `text-primary-strong`. `primary` is for fills.
|
|
258
|
+
- **Lime (`highlight`) means "you are here" only.** Do not use it for status.
|
|
259
|
+
|
|
260
|
+
## Contribute
|
|
261
|
+
|
|
262
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md): set up, add or change a component, the visual test
|
|
263
|
+
against Figma, and releases. AI agents: read [AGENTS.md](AGENTS.md).
|
|
264
|
+
|
|
265
|
+
Releases and their notes: [GitHub releases](https://github.com/BrokenRubik/ui/releases). Requests for the Figma file: [docs/design-handoff.md](docs/design-handoff.md).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui';
|
|
3
|
+
/**
|
|
4
|
+
* Figma: Components · Base › Accordion Item (2085:277). Bottom border; trigger 12 px
|
|
5
|
+
* vertical padding, Body/Default Medium, muted chevron; content Body/Default muted, 12 px
|
|
6
|
+
* bottom padding.
|
|
7
|
+
*/
|
|
8
|
+
declare function Accordion(props: React.ComponentProps<typeof AccordionPrimitive.Root>): React.JSX.Element;
|
|
9
|
+
declare function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element;
|
|
10
|
+
declare function AccordionTrigger({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger>): React.JSX.Element;
|
|
11
|
+
declare function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>): React.JSX.Element;
|
|
12
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
13
|
+
//# sourceMappingURL=accordion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accordion.d.ts","sourceRoot":"","sources":["../../src/components/accordion.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAK3D;;;;GAIG;AACH,iBAAS,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,qBAE7E;AAED,iBAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,qBAQnG;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,qBAgBzD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,qBAUzD;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui';
|
|
5
|
+
import { ChevronDownIcon } from 'lucide-react';
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Figma: Components · Base › Accordion Item (2085:277). Bottom border; trigger 12 px
|
|
9
|
+
* vertical padding, Body/Default Medium, muted chevron; content Body/Default muted, 12 px
|
|
10
|
+
* bottom padding.
|
|
11
|
+
*/
|
|
12
|
+
function Accordion(props) {
|
|
13
|
+
return _jsx(AccordionPrimitive.Root, { "data-slot": "accordion", ...props });
|
|
14
|
+
}
|
|
15
|
+
function AccordionItem({ className, ...props }) {
|
|
16
|
+
return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn('border-b border-border', className), ...props }));
|
|
17
|
+
}
|
|
18
|
+
function AccordionTrigger({ className, children, ...props }) {
|
|
19
|
+
return (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn('flex flex-1 items-center justify-between gap-4 py-3 text-left text-body font-medium text-foreground transition-all outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180', className), ...props, children: [children, _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200" })] }) }));
|
|
20
|
+
}
|
|
21
|
+
function AccordionContent({ className, children, ...props }) {
|
|
22
|
+
return (_jsx(AccordionPrimitive.Content, { "data-slot": "accordion-content", className: "overflow-hidden text-body text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down", ...props, children: _jsx("div", { className: cn('pb-3', className), children: children }) }));
|
|
23
|
+
}
|
|
24
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
25
|
+
//# sourceMappingURL=accordion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../src/components/accordion.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAA2D;IAC5E,OAAO,KAAC,kBAAkB,CAAC,IAAI,iBAAW,WAAW,KAAK,KAAK,GAAI,CAAC;AACtE,CAAC;AAED,SAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAwD;IAClG,OAAO,CACL,KAAC,kBAAkB,CAAC,IAAI,iBACZ,gBAAgB,EAC1B,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC,KAC9C,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACgD;IACxD,OAAO,CACL,KAAC,kBAAkB,CAAC,MAAM,IAAC,SAAS,EAAC,MAAM,YACzC,MAAC,kBAAkB,CAAC,OAAO,iBACf,mBAAmB,EAC7B,SAAS,EAAE,EAAE,CACX,sRAAsR,EACtR,SAAS,CACV,KACG,KAAK,aAER,QAAQ,EACT,KAAC,eAAe,IAAC,SAAS,EAAC,6FAA6F,GAAG,IAChG,GACH,CAC7B,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACgD;IACxD,OAAO,CACL,KAAC,kBAAkB,CAAC,OAAO,iBACf,mBAAmB,EAC7B,SAAS,EAAC,mIAAmI,KACzI,KAAK,YAET,cAAK,SAAS,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,YAAG,QAAQ,GAAO,GAC5B,CAC9B,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
|
|
3
|
+
import type { VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { buttonVariants } from './button.js';
|
|
5
|
+
/**
|
|
6
|
+
* Figma: Components · Base › Dialog, Type=Destructive (2084:160): the confirm dialog.
|
|
7
|
+
* Same chrome as Dialog, no close button; Cancel (outline) + action (default or destructive).
|
|
8
|
+
*/
|
|
9
|
+
declare function AlertDialog(props: React.ComponentProps<typeof AlertDialogPrimitive.Root>): React.JSX.Element;
|
|
10
|
+
declare function AlertDialogTrigger(props: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>): React.JSX.Element;
|
|
11
|
+
declare function AlertDialogPortal(props: React.ComponentProps<typeof AlertDialogPrimitive.Portal>): React.JSX.Element;
|
|
12
|
+
declare function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>): React.JSX.Element;
|
|
13
|
+
declare function AlertDialogContent({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Content>): React.JSX.Element;
|
|
14
|
+
declare function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
15
|
+
declare function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
16
|
+
declare function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>): React.JSX.Element;
|
|
17
|
+
declare function AlertDialogDescription({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Description>): React.JSX.Element;
|
|
18
|
+
declare function AlertDialogAction({ className, variant, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action> & Pick<VariantProps<typeof buttonVariants>, 'variant'>): React.JSX.Element;
|
|
19
|
+
declare function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>): React.JSX.Element;
|
|
20
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, };
|
|
21
|
+
//# sourceMappingURL=alert-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../../src/components/alert-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;;GAGG;AACH,iBAAS,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,IAAI,CAAC,qBAEjF;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,OAAO,CAAC,qBAE3F;AAED,iBAAS,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,MAAM,CAAC,qBAEzF;AAED,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,OAAO,CAAC,qBAQ3D;AAED,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,OAAO,CAAC,qBAc3D;AAED,iBAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAE9E;AAED,iBAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQ9E;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,KAAK,CAAC,qBAQzD;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,WAAW,CAAC,qBAQ/D;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,MAAM,CAAC,GACzD,IAAI,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,EAAE,SAAS,CAAC,qBAQrD;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,MAAM,CAAC,qBAQ1D;AAED,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,GACnB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
|
|
5
|
+
import { cn } from '../lib/utils.js';
|
|
6
|
+
import { buttonVariants } from './button.js';
|
|
7
|
+
import { overlayClassName } from './dialog.js';
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Dialog, Type=Destructive (2084:160): the confirm dialog.
|
|
10
|
+
* Same chrome as Dialog, no close button; Cancel (outline) + action (default or destructive).
|
|
11
|
+
*/
|
|
12
|
+
function AlertDialog(props) {
|
|
13
|
+
return _jsx(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
|
|
14
|
+
}
|
|
15
|
+
function AlertDialogTrigger(props) {
|
|
16
|
+
return _jsx(AlertDialogPrimitive.Trigger, { "data-slot": "alert-dialog-trigger", ...props });
|
|
17
|
+
}
|
|
18
|
+
function AlertDialogPortal(props) {
|
|
19
|
+
return _jsx(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props });
|
|
20
|
+
}
|
|
21
|
+
function AlertDialogOverlay({ className, ...props }) {
|
|
22
|
+
return (_jsx(AlertDialogPrimitive.Overlay, { "data-slot": "alert-dialog-overlay", className: cn(overlayClassName, className), ...props }));
|
|
23
|
+
}
|
|
24
|
+
function AlertDialogContent({ className, ...props }) {
|
|
25
|
+
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, { "data-slot": "alert-dialog-content", className: cn('fixed top-1/2 left-1/2 z-50 flex w-[calc(100%-2rem)] max-w-[480px] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 rounded-xl border border-border bg-background p-6 text-body text-foreground shadow-lg outline-none data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95', className), ...props })] }));
|
|
26
|
+
}
|
|
27
|
+
function AlertDialogHeader({ className, ...props }) {
|
|
28
|
+
return _jsx("div", { "data-slot": "alert-dialog-header", className: cn('flex flex-col gap-1.5', className), ...props });
|
|
29
|
+
}
|
|
30
|
+
function AlertDialogFooter({ className, ...props }) {
|
|
31
|
+
return (_jsx("div", { "data-slot": "alert-dialog-footer", className: cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className), ...props }));
|
|
32
|
+
}
|
|
33
|
+
function AlertDialogTitle({ className, ...props }) {
|
|
34
|
+
return (_jsx(AlertDialogPrimitive.Title, { "data-slot": "alert-dialog-title", className: cn('text-page-title', className), ...props }));
|
|
35
|
+
}
|
|
36
|
+
function AlertDialogDescription({ className, ...props }) {
|
|
37
|
+
return (_jsx(AlertDialogPrimitive.Description, { "data-slot": "alert-dialog-description", className: cn('text-body text-muted-foreground', className), ...props }));
|
|
38
|
+
}
|
|
39
|
+
function AlertDialogAction({ className, variant = 'default', ...props }) {
|
|
40
|
+
return (_jsx(AlertDialogPrimitive.Action, { "data-slot": "alert-dialog-action", className: cn(buttonVariants({ variant }), className), ...props }));
|
|
41
|
+
}
|
|
42
|
+
function AlertDialogCancel({ className, ...props }) {
|
|
43
|
+
return (_jsx(AlertDialogPrimitive.Cancel, { "data-slot": "alert-dialog-cancel", className: cn(buttonVariants({ variant: 'outline' }), className), ...props }));
|
|
44
|
+
}
|
|
45
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, };
|
|
46
|
+
//# sourceMappingURL=alert-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","sourceRoot":"","sources":["../../src/components/alert-dialog.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAG/D,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AACH,SAAS,WAAW,CAAC,KAA6D;IAChF,OAAO,KAAC,oBAAoB,CAAC,IAAI,iBAAW,cAAc,KAAK,KAAK,GAAI,CAAC;AAC3E,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAgE;IAC1F,OAAO,KAAC,oBAAoB,CAAC,OAAO,iBAAW,sBAAsB,KAAK,KAAK,GAAI,CAAC;AACtF,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA+D;IACxF,OAAO,KAAC,oBAAoB,CAAC,MAAM,iBAAW,qBAAqB,KAAK,KAAK,GAAI,CAAC;AACpF,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACkD;IAC1D,OAAO,CACL,KAAC,oBAAoB,CAAC,OAAO,iBACjB,sBAAsB,EAChC,SAAS,EAAE,EAAE,CAAC,gBAAgB,EAAE,SAAS,CAAC,KACtC,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACkD;IAC1D,OAAO,CACL,MAAC,iBAAiB,eAChB,KAAC,kBAAkB,KAAG,EACtB,KAAC,oBAAoB,CAAC,OAAO,iBACjB,sBAAsB,EAChC,SAAS,EAAE,EAAE,CACX,6YAA6Y,EAC7Y,SAAS,CACV,KACG,KAAK,GACT,IACgB,CACrB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IAC7E,OAAO,2BAAe,qBAAqB,EAAC,SAAS,EAAE,EAAE,CAAC,uBAAuB,EAAE,SAAS,CAAC,KAAM,KAAK,GAAI,CAAC;AAC/G,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IAC7E,OAAO,CACL,2BACY,qBAAqB,EAC/B,SAAS,EAAE,EAAE,CAAC,wDAAwD,EAAE,SAAS,CAAC,KAC9E,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACgD;IACxD,OAAO,CACL,KAAC,oBAAoB,CAAC,KAAK,iBACf,oBAAoB,EAC9B,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,KACvC,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACsD;IAC9D,OAAO,CACL,KAAC,oBAAoB,CAAC,WAAW,iBACrB,0BAA0B,EACpC,SAAS,EAAE,EAAE,CAAC,iCAAiC,EAAE,SAAS,CAAC,KACvD,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,OAAO,GAAG,SAAS,EACnB,GAAG,KAAK,EAE4C;IACpD,OAAO,CACL,KAAC,oBAAoB,CAAC,MAAM,iBAChB,qBAAqB,EAC/B,SAAS,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,CAAC,KACjD,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,GAAG,KAAK,EACiD;IACzD,OAAO,CACL,KAAC,oBAAoB,CAAC,MAAM,iBAChB,qBAAqB,EAC/B,SAAS,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,CAAC,KAC5D,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,GACnB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
/**
|
|
4
|
+
* Figma: Components · Base › Alert (2085:172). 8 px radius, 12/16 px padding, 12 px gap,
|
|
5
|
+
* 16 px icon. info = bordered on background (violet icon, muted text); the states are a
|
|
6
|
+
* tint with the -strong shade for icon, title and text.
|
|
7
|
+
*/
|
|
8
|
+
declare const alertVariants: (props?: ({
|
|
9
|
+
tone?: "destructive" | "info" | "success" | "warning" | null | undefined;
|
|
10
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
|
+
type AlertTone = NonNullable<VariantProps<typeof alertVariants>['tone']>;
|
|
12
|
+
type AlertProps = React.ComponentProps<'div'> & {
|
|
13
|
+
/** Figma "Tone". Default `info`. */
|
|
14
|
+
tone?: AlertTone | null;
|
|
15
|
+
/** shadcn name for `tone`. Also accepts `default` (→ `info`). */
|
|
16
|
+
variant?: AlertTone | 'default' | null;
|
|
17
|
+
/** The icon. Default: the tone's lucide icon from Figma. `null` = no icon. */
|
|
18
|
+
icon?: React.ReactNode;
|
|
19
|
+
};
|
|
20
|
+
declare function Alert({ className, tone: toneProp, variant, icon, children, ...props }: AlertProps): React.JSX.Element;
|
|
21
|
+
declare function AlertTitle({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
22
|
+
declare function AlertDescription({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
23
|
+
export { Alert, AlertTitle, AlertDescription, alertVariants, type AlertProps, type AlertTone };
|
|
24
|
+
//# sourceMappingURL=alert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert.d.ts","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAMlE;;;;GAIG;AACH,QAAA,MAAM,aAAa;;8EAalB,CAAC;AASF,KAAK,SAAS,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAEzE,KAAK,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC9C,oCAAoC;IACpC,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,iEAAiE;IACjE,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB,CAAC;AAEF,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,qBAgB1F;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQvE;AAED,iBAAS,gBAAgB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAW7E;AAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { CircleAlertIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon } from 'lucide-react';
|
|
5
|
+
import { resolveTone } from '../lib/tone.js';
|
|
6
|
+
import { cn } from '../lib/utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Figma: Components · Base › Alert (2085:172). 8 px radius, 12/16 px padding, 12 px gap,
|
|
9
|
+
* 16 px icon. info = bordered on background (violet icon, muted text); the states are a
|
|
10
|
+
* tint with the -strong shade for icon, title and text.
|
|
11
|
+
*/
|
|
12
|
+
const alertVariants = cva("relative grid w-full grid-cols-[auto_1fr] items-start gap-x-3 gap-y-0.5 rounded-lg px-4 py-3 text-body [&>svg]:size-4", {
|
|
13
|
+
variants: {
|
|
14
|
+
tone: {
|
|
15
|
+
info: 'border border-border bg-background text-foreground [&>svg]:text-primary-strong *:data-[slot=alert-description]:text-muted-foreground',
|
|
16
|
+
success: 'bg-success-tint text-success-strong',
|
|
17
|
+
warning: 'bg-warning-tint text-warning-strong',
|
|
18
|
+
destructive: 'bg-destructive-tint text-destructive-strong',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: { tone: 'info' },
|
|
22
|
+
});
|
|
23
|
+
const defaultIcons = {
|
|
24
|
+
info: InfoIcon,
|
|
25
|
+
success: CircleCheckIcon,
|
|
26
|
+
warning: TriangleAlertIcon,
|
|
27
|
+
destructive: CircleAlertIcon,
|
|
28
|
+
};
|
|
29
|
+
function Alert({ className, tone: toneProp, variant, icon, children, ...props }) {
|
|
30
|
+
const tone = resolveTone(toneProp, variant, { default: 'info' }, 'info');
|
|
31
|
+
const DefaultIcon = defaultIcons[tone];
|
|
32
|
+
const shown = icon === undefined ? _jsx(DefaultIcon, { "aria-hidden": true }) : icon;
|
|
33
|
+
return (_jsxs("div", { "data-slot": "alert", "data-tone": tone, role: "alert", className: cn(alertVariants({ tone }), !shown && 'grid-cols-1', className), ...props, children: [shown, children] }));
|
|
34
|
+
}
|
|
35
|
+
function AlertTitle({ className, ...props }) {
|
|
36
|
+
return (_jsx("div", { "data-slot": "alert-title", className: cn('col-start-2 font-medium in-[[data-slot=alert].grid-cols-1]:col-start-1', className), ...props }));
|
|
37
|
+
}
|
|
38
|
+
function AlertDescription({ className, ...props }) {
|
|
39
|
+
return (_jsx("div", { "data-slot": "alert-description", className: cn('col-start-2 text-body-sm in-[[data-slot=alert].grid-cols-1]:col-start-1 [&_p]:leading-relaxed', className), ...props }));
|
|
40
|
+
}
|
|
41
|
+
export { Alert, AlertTitle, AlertDescription, alertVariants };
|
|
42
|
+
//# sourceMappingURL=alert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert.js","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAE7F,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC;;;;GAIG;AACH,MAAM,aAAa,GAAG,GAAG,CACvB,uHAAuH,EACvH;IACE,QAAQ,EAAE;QACR,IAAI,EAAE;YACJ,IAAI,EAAE,sIAAsI;YAC5I,OAAO,EAAE,qCAAqC;YAC9C,OAAO,EAAE,qCAAqC;YAC9C,WAAW,EAAE,6CAA6C;SAC3D;KACF;IACD,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;CAClC,CACF,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,iBAAiB;IAC1B,WAAW,EAAE,eAAe;CACpB,CAAC;AAaX,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAc;IACzF,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;IACzE,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAC,WAAW,0BAAe,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,OAAO,CACL,4BACY,OAAO,eACN,IAAI,EACf,IAAI,EAAC,OAAO,EACZ,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,aAAa,EAAE,SAAS,CAAC,KACtE,KAAK,aAER,KAAK,EACL,QAAQ,IACL,CACP,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IACtE,OAAO,CACL,2BACY,aAAa,EACvB,SAAS,EAAE,EAAE,CAAC,wEAAwE,EAAE,SAAS,CAAC,KAC9F,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IAC5E,OAAO,CACL,2BACY,mBAAmB,EAC7B,SAAS,EAAE,EAAE,CACX,+FAA+F,EAC/F,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAmC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { Avatar as AvatarPrimitive } from 'radix-ui';
|
|
4
|
+
/**
|
|
5
|
+
* Figma: Components · Base › Avatar (2006:62). 20 or 28 px, full radius. Initials use a
|
|
6
|
+
* soft tint per person with the -strong shade, never a solid saturated fill.
|
|
7
|
+
*/
|
|
8
|
+
declare const avatarVariants: (props?: ({
|
|
9
|
+
size?: 20 | 28 | null | undefined;
|
|
10
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
|
+
declare const avatarFallbackVariants: (props?: ({
|
|
12
|
+
tone?: "amber" | "coral" | "emerald" | "purple" | "red" | "slate" | null | undefined;
|
|
13
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
14
|
+
type AvatarTone = NonNullable<VariantProps<typeof avatarFallbackVariants>['tone']>;
|
|
15
|
+
/** A stable tone for a person (same name → same tint), for lists without a stored tone. */
|
|
16
|
+
declare function avatarToneFor(key: string): AvatarTone;
|
|
17
|
+
declare function Avatar({ className, size, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & VariantProps<typeof avatarVariants>): React.JSX.Element;
|
|
18
|
+
declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element;
|
|
19
|
+
type AvatarFallbackProps = React.ComponentProps<typeof AvatarPrimitive.Fallback> & {
|
|
20
|
+
/** Figma "Tone". Default `slate`. `avatarToneFor(name)` picks a stable one per person. */
|
|
21
|
+
tone?: AvatarTone | null;
|
|
22
|
+
/** Same as `tone` (shadcn-style name). */
|
|
23
|
+
variant?: AvatarTone | null;
|
|
24
|
+
};
|
|
25
|
+
declare function AvatarFallback({ className, tone: toneProp, variant, ...props }: AvatarFallbackProps): React.JSX.Element;
|
|
26
|
+
export { Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarFallbackVariants, avatarToneFor, type AvatarTone, type AvatarFallbackProps, };
|
|
27
|
+
//# sourceMappingURL=avatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../src/components/avatar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAKrD;;;GAGG;AACH,QAAA,MAAM,cAAc;;8EAQlB,CAAC;AAEH,QAAA,MAAM,sBAAsB;;8EAe3B,CAAC;AAEF,KAAK,UAAU,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAInF,2FAA2F;AAC3F,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAI9C;AAED,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,IAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,qBASzF;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQ/F;AAED,KAAK,mBAAmB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,QAAQ,CAAC,GAAG;IACjF,0FAA0F;IAC1F,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,iBAAS,cAAc,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,qBAU5F;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,KAAK,UAAU,EACf,KAAK,mBAAmB,GACzB,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
import { Avatar as AvatarPrimitive } from 'radix-ui';
|
|
6
|
+
import { resolveTone } from '../lib/tone.js';
|
|
7
|
+
import { cn } from '../lib/utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Figma: Components · Base › Avatar (2006:62). 20 or 28 px, full radius. Initials use a
|
|
10
|
+
* soft tint per person with the -strong shade, never a solid saturated fill.
|
|
11
|
+
*/
|
|
12
|
+
const avatarVariants = cva('group/avatar relative flex shrink-0 overflow-hidden rounded-full', {
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
20: 'size-5',
|
|
16
|
+
28: 'size-7',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: { size: 28 },
|
|
20
|
+
});
|
|
21
|
+
const avatarFallbackVariants = cva('flex size-full items-center justify-center rounded-full text-initials group-data-[size=20]/avatar:text-initials-sm', {
|
|
22
|
+
variants: {
|
|
23
|
+
tone: {
|
|
24
|
+
purple: 'bg-primary-tint text-primary-strong',
|
|
25
|
+
amber: 'bg-warning-tint text-warning-strong',
|
|
26
|
+
red: 'bg-destructive-tint text-destructive-strong',
|
|
27
|
+
emerald: 'bg-success-tint text-success-strong',
|
|
28
|
+
coral: 'bg-secondary text-secondary-foreground',
|
|
29
|
+
slate: 'bg-secondary text-secondary-foreground',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: { tone: 'slate' },
|
|
33
|
+
});
|
|
34
|
+
const avatarTones = ['purple', 'amber', 'red', 'emerald', 'coral', 'slate'];
|
|
35
|
+
/** A stable tone for a person (same name → same tint), for lists without a stored tone. */
|
|
36
|
+
function avatarToneFor(key) {
|
|
37
|
+
let hash = 0;
|
|
38
|
+
for (let i = 0; i < key.length; i++)
|
|
39
|
+
hash = (hash * 31 + key.charCodeAt(i)) | 0;
|
|
40
|
+
return avatarTones[Math.abs(hash) % avatarTones.length] ?? 'slate';
|
|
41
|
+
}
|
|
42
|
+
function Avatar({ className, size = 28, ...props }) {
|
|
43
|
+
return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn(avatarVariants({ size }), className), ...props }));
|
|
44
|
+
}
|
|
45
|
+
function AvatarImage({ className, ...props }) {
|
|
46
|
+
return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn('aspect-square size-full object-cover', className), ...props }));
|
|
47
|
+
}
|
|
48
|
+
function AvatarFallback({ className, tone: toneProp, variant, ...props }) {
|
|
49
|
+
const tone = resolveTone(toneProp, variant, {}, 'slate');
|
|
50
|
+
return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", "data-tone": tone, className: cn(avatarFallbackVariants({ tone }), className), ...props }));
|
|
51
|
+
}
|
|
52
|
+
export { Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarFallbackVariants, avatarToneFor, };
|
|
53
|
+
//# sourceMappingURL=avatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.js","sourceRoot":"","sources":["../../src/components/avatar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC;;;GAGG;AACH,MAAM,cAAc,GAAG,GAAG,CAAC,kEAAkE,EAAE;IAC7F,QAAQ,EAAE;QACR,IAAI,EAAE;YACJ,EAAE,EAAE,QAAQ;YACZ,EAAE,EAAE,QAAQ;SACb;KACF;IACD,eAAe,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,GAAG,CAChC,oHAAoH,EACpH;IACE,QAAQ,EAAE;QACR,IAAI,EAAE;YACJ,MAAM,EAAE,qCAAqC;YAC7C,KAAK,EAAE,qCAAqC;YAC5C,GAAG,EAAE,6CAA6C;YAClD,OAAO,EAAE,qCAAqC;YAC9C,KAAK,EAAE,wCAAwC;YAC/C,KAAK,EAAE,wCAAwC;SAChD;KACF;IACD,eAAe,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;CACnC,CACF,CAAC;AAIF,MAAM,WAAW,GAA0B,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAEnG,2FAA2F;AAC3F,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChF,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC;AACrE,CAAC;AAED,SAAS,MAAM,CAAC,EACd,SAAS,EACT,IAAI,GAAG,EAAE,EACT,GAAG,KAAK,EACgF;IACxF,OAAO,CACL,KAAC,eAAe,CAAC,IAAI,iBACT,QAAQ,eACP,IAAI,EACf,SAAS,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,KAC9C,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAsD;IAC9F,OAAO,CACL,KAAC,eAAe,CAAC,KAAK,iBACV,cAAc,EACxB,SAAS,EAAE,EAAE,CAAC,sCAAsC,EAAE,SAAS,CAAC,KAC5D,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AASD,SAAS,cAAc,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAuB;IAC3F,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACzD,OAAO,CACL,KAAC,eAAe,CAAC,QAAQ,iBACb,iBAAiB,eAChB,IAAI,EACf,SAAS,EAAE,EAAE,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,KACtD,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,aAAa,GAGd,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
/**
|
|
4
|
+
* Figma: Components · Base › Badge (2006:37). 20 px high, 6 px radius, Label/Caption
|
|
5
|
+
* Medium. Every tone is a soft tint with the -strong text shade ("one color, one job").
|
|
6
|
+
* Only status fields get a tone: categorical tags (country, owner…) stay neutral.
|
|
7
|
+
*/
|
|
8
|
+
declare const badgeVariants: (props?: ({
|
|
9
|
+
tone?: "destructive" | "highlight" | "info" | "neutral" | "primary" | "success" | "warning" | null | undefined;
|
|
10
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
|
+
type BadgeTone = NonNullable<VariantProps<typeof badgeVariants>['tone']>;
|
|
12
|
+
type BadgeProps = React.ComponentProps<'span'> & {
|
|
13
|
+
/** Figma "Tone". Default `neutral`. */
|
|
14
|
+
tone?: BadgeTone | null;
|
|
15
|
+
/** shadcn name for `tone`. Also accepts `default`, `secondary`, `outline` (→ `neutral`). */
|
|
16
|
+
variant?: BadgeTone | 'default' | 'secondary' | 'outline' | null;
|
|
17
|
+
asChild?: boolean;
|
|
18
|
+
};
|
|
19
|
+
declare function Badge({ className, tone, variant, asChild, ...props }: BadgeProps): React.JSX.Element;
|
|
20
|
+
export { Badge, badgeVariants, type BadgeProps, type BadgeTone };
|
|
21
|
+
//# sourceMappingURL=badge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../src/components/badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAMlE;;;;GAIG;AACH,QAAA,MAAM,aAAa;;8EAkBlB,CAAC;AAEF,KAAK,SAAS,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AASzE,KAAK,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG;IAC/C,uCAAuC;IACvC,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,OAAe,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,qBAWjF;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,CAAC"}
|