@bigtablet/design-system 3.16.0 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +2222 -1347
- package/dist/index.d.ts +1028 -197
- package/dist/index.js +1983 -782
- package/dist/styles/layout/_index.scss +18 -0
- package/dist/styles/typography/_index.scss +23 -0
- package/dist/vanilla/bigtablet.min.css +1 -1
- package/dist/vanilla/bigtablet.min.js +3 -3
- package/docs/AGENT_GUIDE.md +591 -0
- package/package.json +3 -2
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# Bigtablet Design System - Agent Guide
|
|
2
|
+
|
|
3
|
+
> Prompt-ready reference for AI coding agents building UIs with `@bigtablet/design-system@^3.0.0`. Load this file as context before generating any component code.
|
|
4
|
+
|
|
5
|
+
**Version:** 3.0.0
|
|
6
|
+
**React:** 19+ required
|
|
7
|
+
**Bundle:** Pure React (`@bigtablet/design-system`) or Vanilla JS (`@bigtablet/design-system/vanilla`)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Philosophy
|
|
12
|
+
|
|
13
|
+
- **Token-first.** Never hardcode colors, spacing, radius, or shadows. Always reference tokens.
|
|
14
|
+
- **Dark mode is mandatory.** Every UI must work in both themes. Tokens auto-flip via `[data-theme="dark"]` or `prefers-color-scheme`.
|
|
15
|
+
- **Components compose.** Build pages from `Container > Section > Stack/Grid > primitives`. Don't reach for raw `<div>` styling.
|
|
16
|
+
- **Accessibility is non-negotiable.** Buttons need labels, modals trap focus, interactive elements have `:focus-visible` rings.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Public surface — what you may rely on
|
|
21
|
+
|
|
22
|
+
| Surface | Stable? | Notes |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Exported components and their props | **Yes** | TypeScript types are the contract. A removed or renamed prop is a major |
|
|
25
|
+
| SCSS tokens (`@bigtablet/design-system/scss/token`) | **Yes** | Added in minors, removed only in majors |
|
|
26
|
+
| CSS variables (`--bt-*`) from `style.css` | **Yes** | The Vanilla bundle defines a subset — see the Spacing section |
|
|
27
|
+
| Vanilla `bt-*` classes (`/vanilla`) | **Yes** | That bundle's whole API is its classes |
|
|
28
|
+
| **React DOM structure and `component_part` classes** | **No** | `.modal_panel`, `.textarea_container`, `.text_field_input` … internal. They move in patches |
|
|
29
|
+
|
|
30
|
+
### Do not style DS internals from your app
|
|
31
|
+
|
|
32
|
+
Reaching into a React component's DOM — overriding `.textarea_container`, shaving a
|
|
33
|
+
radius, wrapping the component to add a focus ring — breaks silently. There is no type
|
|
34
|
+
error when a class is renamed; only the screen changes. Three of these were live in one
|
|
35
|
+
consumer app at once (Bigtablet/bigtablet-design-system#544).
|
|
36
|
+
|
|
37
|
+
**If you have to reach inside, the component is missing a prop.** File an issue with the
|
|
38
|
+
markup you had to write. `Textarea`'s `toolbar` slot exists because a formatting bar
|
|
39
|
+
needed the border, radius, and `:focus-within` that only the internal container had.
|
|
40
|
+
|
|
41
|
+
### Checking what a version gives you
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# props your installed version actually has
|
|
45
|
+
grep -A30 "interface TextareaProps" node_modules/@bigtablet/design-system/dist/index.d.ts
|
|
46
|
+
|
|
47
|
+
# what changed, and which lines need a visual check
|
|
48
|
+
# entries starting with "(렌더 변경)" alter rendering with no API change
|
|
49
|
+
cat node_modules/@bigtablet/design-system/CHANGELOG.md # or the GitHub releases page
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Install & Bootstrap
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add @bigtablet/design-system react@^19 react-dom@^19 lucide-react
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// app entry - providers wrap the tree once
|
|
62
|
+
import {
|
|
63
|
+
ThemeProvider,
|
|
64
|
+
AlertProvider,
|
|
65
|
+
ToastProvider,
|
|
66
|
+
} from "@bigtablet/design-system";
|
|
67
|
+
import "@bigtablet/design-system/style.css";
|
|
68
|
+
|
|
69
|
+
export default function RootLayout({ children }) {
|
|
70
|
+
return (
|
|
71
|
+
<ThemeProvider defaultMode="system">
|
|
72
|
+
<AlertProvider>
|
|
73
|
+
<ToastProvider>{children}</ToastProvider>
|
|
74
|
+
</AlertProvider>
|
|
75
|
+
</ThemeProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Providers needed:
|
|
81
|
+
- `ThemeProvider` - required for runtime dark mode toggle via `useTheme()`. If you only need OS-preference-based dark mode, you can omit it (CSS handles it).
|
|
82
|
+
- `AlertProvider` - required for `useAlert()` confirmation dialogs.
|
|
83
|
+
- `ToastProvider` - required for `useToast()` snackbar notifications.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Design Tokens
|
|
88
|
+
|
|
89
|
+
Always reference tokens - never inline a hex value.
|
|
90
|
+
|
|
91
|
+
Two surfaces, and they are **not** interchangeable:
|
|
92
|
+
- **SCSS tokens** (`@use "src/styles/token" as token;` → `token.$spacing_16`) - the full set. Use these in component `style.scss`.
|
|
93
|
+
- **CSS custom properties** - read them with `var()` in consumer CSS. Spacing / radius / typography CSS vars exist only in the Vanilla bundle. Full contract incl. runtime behaviour: [THEMING.md](./THEMING.md#레이아웃런타임-계약-변수).
|
|
94
|
+
|
|
95
|
+
<!-- css-var-claim: 아래 한 줄이 React entry 의 변수 계열 전부를 주장한다. scripts/check-css-vars.sh 가 이 줄만 검사하므로 예시 변수명을 이 줄에 두지 말 것 - 예시가 계열을 채워 검사가 무력화된다. -->
|
|
96
|
+
The React entry's `style.css` emits only `--bt-color-*`, `--bt-elevation-*`, `--bt-focus-*`, `--bt-sidebar-*`, `--bt-bottom-nav-*`, `--bt-bottom-inset*`, `--bt-scrollbar-width`, `--bt-z-*`.
|
|
97
|
+
|
|
98
|
+
### Color tokens
|
|
99
|
+
|
|
100
|
+
| Token | Light | Dark | When to use |
|
|
101
|
+
|-------|-------|------|-------------|
|
|
102
|
+
| `--bt-color-bg-solid` | white | navy_900 | Page/card background |
|
|
103
|
+
| `--bt-color-bg-solid-dim` | neutral_50 | navy_800 | Elevated surface (panels, dimmed sections) |
|
|
104
|
+
| `--bt-color-text-heading` | neutral_900 | white | Primary headings, body emphasis |
|
|
105
|
+
| `--bt-color-text-body` | neutral_700 | navy_200 | Body text, descriptions |
|
|
106
|
+
| `--bt-color-text-caption` | neutral_500 | neutral_400 | Captions, helper text, placeholders |
|
|
107
|
+
| `--bt-color-border-default` | neutral_200 | navy_700 | Card borders, dividers |
|
|
108
|
+
| `--bt-color-border-hover` | neutral_400 | navy_400 | Hover state borders |
|
|
109
|
+
| `--bt-color-brand-primary` | #121212 | #121212 (same) | Button "filled" bg (intentional, both themes) |
|
|
110
|
+
| `--bt-color-brand-on-primary` | white | white (same) | Text on `brand-primary` |
|
|
111
|
+
| `--bt-color-accent-default` | #121212 | white | Indicators that flip in dark (checked checkboxes, active progress) |
|
|
112
|
+
| `--bt-color-accent-on-surface` | white | #121212 | Text/icon on top of `accent-default` |
|
|
113
|
+
| `--bt-color-status-success` | green | green (same) | Success states |
|
|
114
|
+
| `--bt-color-status-warning` | amber | amber (same) | Warning states |
|
|
115
|
+
| `--bt-color-status-error` | red | red (same) | Error states, destructive actions |
|
|
116
|
+
| `--bt-color-status-info` | blue | blue (same) | Informational states |
|
|
117
|
+
| `--bt-color-state-hover-on-light` | alpha black 5% | alpha white 8% | Hover overlay on light/elevated surface |
|
|
118
|
+
| `--bt-color-state-focus-on-light` | alpha black 8% | alpha white 12% | Focus overlay |
|
|
119
|
+
| `--bt-color-state-pressed-on-light` | alpha black 12% | alpha white 12% | Pressed overlay |
|
|
120
|
+
|
|
121
|
+
**Key distinction:** `brand-primary` stays dark in both themes (for Button filled). `accent-default` FLIPS to white in dark mode (for indicators that need contrast against page bg). Pick the right one for the context.
|
|
122
|
+
|
|
123
|
+
### Spacing
|
|
124
|
+
|
|
125
|
+
SCSS: `$spacing_0` … `$spacing_128`. Scale: 0, 1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 64, 96, 128.
|
|
126
|
+
|
|
127
|
+
`$spacing_64` / `_96` / `_128` are layout rhythm - screen-level block padding (`Section`), not padding inside a component. `$spacing_10` exists because dense controls (pagination, nav-bar, tooltip) share a `6px 10px` pair.
|
|
128
|
+
|
|
129
|
+
CSS vars exist **only in the Vanilla bundle**, and only for `--bt-spacing-4` / `-8` / `-12` / `-16` / `-20` / `-24` / `-32` / `-40` / `-48`. There is no CSS var for 0, 1, 2, 3, 6, 10, 64, 96 or 128, and the React entry's `style.css` emits none of them. In React/Next code use the SCSS token.
|
|
130
|
+
|
|
131
|
+
### Radius
|
|
132
|
+
|
|
133
|
+
SCSS: `$radius_none` (0), `$radius_xs` (4px), `$radius_sm` (6px), `$radius_md` (8px), `$radius_lg` (12px), `$radius_xl` (16px), `$radius_full` (9999px).
|
|
134
|
+
|
|
135
|
+
CSS vars exist **only in the Vanilla bundle**, and only for a subset: `--bt-radius-sm` / `-md` / `-lg` / `-full`. There is no `--bt-radius-xs` or `-xl`.
|
|
136
|
+
|
|
137
|
+
### Elevation
|
|
138
|
+
|
|
139
|
+
`--bt-elevation-level1` through `-level5`. Light = subtle dark drops. Dark = stronger blacks with larger spread for visibility on navy.
|
|
140
|
+
|
|
141
|
+
### Motion
|
|
142
|
+
|
|
143
|
+
```scss
|
|
144
|
+
$transition_fast // 0.1s ease-in-out - color/border micro-changes
|
|
145
|
+
$transition_base // 0.2s ease-in-out - bg/transform normal interactions
|
|
146
|
+
$transition_slow // 0.3s ease-in-out - panel expansion
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Easing pair for entrance/exit:
|
|
150
|
+
- `$easing_enter` - `cubic-bezier(0.16, 1, 0.3, 1)` (out-expo)
|
|
151
|
+
- `$easing_exit` - `cubic-bezier(0.4, 0, 1, 1)` (ease-in)
|
|
152
|
+
|
|
153
|
+
Composite shorthands `$transition_enter_*` / `$transition_exit_*` already include easing - do NOT add another easing or CSS parse fails.
|
|
154
|
+
|
|
155
|
+
### Inline style usage
|
|
156
|
+
|
|
157
|
+
**Prefer a `style.scss` rule with SCSS tokens.** It is the only place where every token - colour *and* spacing - is available by name:
|
|
158
|
+
|
|
159
|
+
```scss
|
|
160
|
+
// ✓ Best - both colour and spacing come from tokens
|
|
161
|
+
.my-panel {
|
|
162
|
+
background: token.$color_bg_solid_dim;
|
|
163
|
+
padding: token.$spacing_16;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Reach for an inline style only when the value is genuinely dynamic (computed at runtime). Then:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// ✓ OK - --bt-color-* IS emitted by the React entry's style.css
|
|
171
|
+
<div style={{ background: "var(--bt-color-bg-solid-dim)" }} />
|
|
172
|
+
|
|
173
|
+
// ✗ Never - hardcoded hex breaks dark mode
|
|
174
|
+
<div style={{ background: "#F2F5F8" }} />
|
|
175
|
+
|
|
176
|
+
// ✗ Never - --bt-spacing-* is Vanilla-only, resolves to nothing in a React app
|
|
177
|
+
<div style={{ padding: "var(--bt-spacing-16)" }} />
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Spacing has no CSS var to reference on the React entry, so an inline `padding` has to be a bare number (`padding: 16`). That is a last resort - it is an untokenised literal. If you find yourself writing one, move the rule into `style.scss` and use `token.$spacing_16` instead.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Dark Mode Rules
|
|
185
|
+
|
|
186
|
+
1. **Trigger:** `[data-theme="dark"]` attribute on `<html>` (via `ThemeProvider`) OR system `prefers-color-scheme: dark`. Both work automatically.
|
|
187
|
+
|
|
188
|
+
2. **Use adaptive tokens for theme-aware UI.** Most tokens auto-flip. Hardcoded hex breaks dark mode.
|
|
189
|
+
|
|
190
|
+
3. **Indicator color rule:**
|
|
191
|
+
- If element must be **black in light AND dark** (e.g., filled button bg) → `brand-primary`
|
|
192
|
+
- If element should be **black in light, white in dark** (e.g., checkbox checked bg) → `accent-default`
|
|
193
|
+
- Pair `accent-default` with `accent-on-surface` for the foreground (auto-inverts)
|
|
194
|
+
|
|
195
|
+
4. **Panel elevation in dark mode:**
|
|
196
|
+
- Page bg: `bg-solid` (navy_900)
|
|
197
|
+
- Elevated panel (dropdown, menu, modal): use the conditional pattern below
|
|
198
|
+
|
|
199
|
+
```scss
|
|
200
|
+
.my-panel {
|
|
201
|
+
background: var(--bt-color-bg-solid); // white in light
|
|
202
|
+
|
|
203
|
+
[data-theme="dark"] & {
|
|
204
|
+
background: var(--bt-color-bg-solid-dim); // navy_800 (lighter than canvas)
|
|
205
|
+
}
|
|
206
|
+
@media (prefers-color-scheme: dark) {
|
|
207
|
+
:root:not([data-theme]) & {
|
|
208
|
+
background: var(--bt-color-bg-solid-dim);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
5. **Hardcoded whites/darks are allowed for intentional fixed surfaces:**
|
|
215
|
+
- Navy gradient avatars (decorative, brand-colored)
|
|
216
|
+
- Hero `_overlay_navy` (always dark gradient)
|
|
217
|
+
- Tooltip body (`accent-strong` token already handles theme)
|
|
218
|
+
- Destructive red modal buttons
|
|
219
|
+
|
|
220
|
+
Everything else: tokens.
|
|
221
|
+
|
|
222
|
+
6. **`color-scheme: dark`** is already set on `[data-theme="dark"]` root - browser native UI (scrollbars, form controls) adapts automatically.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Component Catalog
|
|
227
|
+
|
|
228
|
+
Organized by category. **Always** import from the package root (`@bigtablet/design-system`), never deep paths.
|
|
229
|
+
|
|
230
|
+
### Forms
|
|
231
|
+
|
|
232
|
+
| Component | Purpose | Key props |
|
|
233
|
+
|-----------|---------|-----------|
|
|
234
|
+
| `Button` | Primary action button. | `variant` (filled/outline/tonal/text), `size` (sm/md/lg/xl), `danger`, `radius`, `leadingIcon`, `trailingIcon`, `fullWidth` |
|
|
235
|
+
| `IconButton` | Icon-only button. | `variant` (standard/filled/tonal/outlined), `size` (sm/md), `icon`, `aria-label` (required) |
|
|
236
|
+
| `TextField` | Single-line text input with label. | `label`, `placeholder`, `supportingText`, `error`, `size`, `leadingIcon`/`trailingIcon` (decorative only - `aria-hidden`), `leadingAction`/`trailingAction` (focusable content), `showPasswordToggle` + `passwordToggleLabels` (app-injected i18n), `clearable`, `onValueChange`, `imeStrategy` (delayed/immediate - use `immediate` for live search w/ Korean IME) |
|
|
237
|
+
| `Textarea` | Multi-line text input. | `label`, `placeholder`, `supportingText`, `error`, `size`, `rows`, `minRows`/`maxRows` (auto-grow), `maxLength` + `showCounter`, `resize` (none/vertical/both), `onChangeAction`, `imeStrategy`. Same tokens/visuals as TextField. |
|
|
238
|
+
| `Checkbox` | Boolean selection. | `checked`, `indeterminate`, `disabled`, `error`, `label` |
|
|
239
|
+
| `Radio` | Single choice. | `value`, `checked`, `name`, `size`, `label`. Standalone, or auto-wired inside `RadioGroup`. |
|
|
240
|
+
| `RadioGroup` | Groups `Radio`s via Context. | `value`/`defaultValue`/`onValueChange`, `name` (auto), `label`, `supportingText`, `error`, `size`, `orientation` (vertical/horizontal), `disabled`. Children = `Radio value=...`. |
|
|
241
|
+
| `Toggle` | On/off switch. | `checked`, `size` (sm/md), `disabled` |
|
|
242
|
+
| `Dropdown` | Value-selection menu. | `options`, `value`, `onChange`, `label`, `supportingText`, `placeholder`, `size`. Block-level - fills parent. |
|
|
243
|
+
| `DatePicker` | Year/month/day select. | `value`, `onChange`, `mode` (year-month/year-month-day), `min`, `max`, `fullWidth` |
|
|
244
|
+
| `FileInput` | File upload. | `variant` (button/preview), `multiple`, `accept`, `onFiles`, `previewSize` (for preview variant), `disabled` |
|
|
245
|
+
| `OTPInput` | Single-digit code boxes. | `length`, `value`, `onChange`, `autoFocus`, `error` |
|
|
246
|
+
|
|
247
|
+
### Display
|
|
248
|
+
|
|
249
|
+
| Component | Purpose | Key props |
|
|
250
|
+
|-----------|---------|-----------|
|
|
251
|
+
| `Card` | Generic container (header/body/footer composition). | `heading`, `variant` (default/accent/glass/outlined - glass=frosted blur over colored bg, outlined=transparent+border), `interactive` (hover-lift for clickable cards), `footer` + `footerAlign` (start/between/end), `bordered`, `shadow` (none/sm/md/lg), `padding` (none/sm/md/lg) |
|
|
252
|
+
| `MediaCard` | Image + content card. | `heading`, `eyebrow`, `description`, `media` (URL), `clickable`, `shadow` |
|
|
253
|
+
| `Hero` | Page-top hero section. | `title`, `subtitle`, `eyebrow`, `backgroundImage`, `overlay` (dark/light/navy), `height` (sm/md/lg/full), `align`, `textColor` (auto/inverse/default), `primaryAction`, `secondaryAction` |
|
|
254
|
+
| `Avatar` | User profile circle. | `name` (initials fallback), `src`, `size` (sm/md/lg), `shape` (circle/square) |
|
|
255
|
+
| `Badge` | Number/status pill. | `shape` (dot/count/label), `variant` (accent/neutral/info/success/warning/error), `appearance` (solid/soft - soft = tint bg + dark text, both WCAG AA), `count` |
|
|
256
|
+
| `Chip` | Tag/category pill. | `type` (interactive/static), `tone` (default/accent/info/success/warning/error - static only), `size` (sm/md), `selected`, `removable`, `leadingIcon` |
|
|
257
|
+
| `ListItem` | Single row in a list. | `label`, `overline`, `supportingText`, `metadata` (all accept string **or ReactNode** - inline `<strong>`/`<a>`/`Badge`), `leadingElement`, `trailingElement`, `alignment` (auto-detects OneLine → middle), `onClick`, `selected` |
|
|
258
|
+
| `Table` | Data table. | `columns`, `data`, `keyExtractor`, `size` (sm/md/lg), `isLoading`, `stickyHeader`, `onRowClick`, `emptyMessage`. Clickable rows get keyboard support automatically. |
|
|
259
|
+
| `Divider` | Horizontal/vertical line. | `orientation` |
|
|
260
|
+
| `Icon` | Lucide icon wrapper. | `icon` (lucide-react component), `size`, `strokeWidth`, `aria-label` |
|
|
261
|
+
| `Accordion` | Expandable disclosure. | `items` array with `id`/`trigger`/`content`. Hover bg works in any open state. |
|
|
262
|
+
|
|
263
|
+
### Feedback
|
|
264
|
+
|
|
265
|
+
| Component | Purpose | Key props |
|
|
266
|
+
|-----------|---------|-----------|
|
|
267
|
+
| `Alert` (via `useAlert`) | Confirm/alert dialog. | `showAlert({ title, message, variant, showCancel, destructive, onConfirm, onCancel })` |
|
|
268
|
+
| `Toast` (via `useToast`) | Transient notification. | `toast.success("...")` / `.error(...)` / `.warning(...)` / `.info(...)` / `.message(...)`. Second arg = duration ms. |
|
|
269
|
+
| `Spinner` | Inline loading indicator. | `size` (px), `ariaLabel`. Vercel-style 12-bar fade. |
|
|
270
|
+
| `TopLoading` | Top-of-page progress bar. | `progress` (0-100, or undefined for indeterminate), `height`, `color`, `isLoading`, `ariaLabel` |
|
|
271
|
+
| `LinearProgress` | Step progress with dots. | `totalSteps`, `currentStep`, `aria-label`. Renders N+1 checkpoints. |
|
|
272
|
+
| `Skeleton` | Loading placeholder. | `variant` (text/title/avatar/rect), `width`, `height`, `radius` |
|
|
273
|
+
| `EmptyState` | "Nothing here" block. | `illustration`, `title`, `description`, `action`, `size` (sm/md/lg). Vertically centers when parent is flex column. |
|
|
274
|
+
| `ErrorState` | Error block (boundary / load failure). | `title` (default "문제가 발생했습니다"), `description`, `icon` (default warning, `null` to hide), `action` (retry button), `variant` (page = full-area fallback / widget = inline compact). Uses `status-error` token, `role="alert"`. |
|
|
275
|
+
|
|
276
|
+
### Navigation
|
|
277
|
+
|
|
278
|
+
| Component | Purpose | Key props |
|
|
279
|
+
|-----------|---------|-----------|
|
|
280
|
+
| `Tabs` | Compound tab pattern. | Wrap `Tab` items in `TabList`; render content via `TabPanel`. `defaultValue` (uncontrolled), `value`/`onValueChange` (controlled). Variants `line` (default) / `fills`. |
|
|
281
|
+
| `Sidebar` | Admin left nav. | `header`, `headerCollapsed` (collapse crossfade), `footer`, `collapsed`, `collapsible`, `collapsedWidth`, `mode` (auto/static - auto transforms to bottom bar <600px). Children = `SidebarSection` + `SidebarItem`. |
|
|
282
|
+
| `BottomNav` | Mobile bottom nav bar. | 2-5 `BottomNavItem` (`icon`, `label`, `active`, `badge`, `as`/`href`). `position: fixed; bottom: 0` + iOS safe-area. Use `BottomNavSpacer` at page end to avoid content overlap. mobile-first flat nav. |
|
|
283
|
+
| `NavBar` | Top nav. | `brand`, `actions`, `variant` (default/transparent/accent), `layout` (contained/fluid). Children = `NavLink`. Sliding active indicator built in. |
|
|
284
|
+
| `Breadcrumb` | Page path nav. | `items` array (`label`, `href`, `current`). |
|
|
285
|
+
| `Menu` | Action menu (context/kebab). | `trigger` element, `items` (key/label/icon/onSelect/destructive/disabled), `align` (start/end). Trigger components MUST forward props (`<button {...props}>`). |
|
|
286
|
+
| `Pagination` | Page number nav. | `page`, `pageCount`, `onChange`, `siblingCount`. Cursor pointer baked in. |
|
|
287
|
+
|
|
288
|
+
### Overlay
|
|
289
|
+
|
|
290
|
+
| Component | Purpose | Key props |
|
|
291
|
+
|-----------|---------|-----------|
|
|
292
|
+
| `Modal` | Centered dialog. | `open`, `onClose`, `title`, `description`, `footer`, `footerAlign` (end/between/start), `showCloseIcon` (default true), `width`, `closeOnOverlay`. X close icon top-right by default. |
|
|
293
|
+
| `Tooltip` | Hover info. | `content`, `placement` (top/bottom/left/right), `delay`, `disabled`. Children = single trigger element. Long text wraps with `text-align: center`, max-width 240px. |
|
|
294
|
+
| `Popover` | Click-triggered non-modal panel for arbitrary interactive content (form/explanation/actions). | `trigger` element, `content` (ReactNode), `placement` (top/bottom/left/right, default bottom), `open`/`defaultOpen`/`onOpenChange` (controlled/uncontrolled), `aria-label`/`aria-labelledby`. `role="dialog"`. Focus moves into panel on open; `Esc` closes + returns focus to trigger. Use `Menu` for action lists, `Tooltip` for hover info. Trigger MUST forward props (`<button {...props}>`). |
|
|
295
|
+
|
|
296
|
+
### Layout
|
|
297
|
+
|
|
298
|
+
| Component | Purpose | Key props |
|
|
299
|
+
|-----------|---------|-----------|
|
|
300
|
+
| `Container` | Centered max-width wrapper. | `size` (sm/md/lg/xl/full), `padding` |
|
|
301
|
+
| `Section` | Page section with vertical rhythm. | `spacing` (xs/sm/md/lg/xl), `bg` (default/dim/accent/navy/transparent) |
|
|
302
|
+
| `Stack` | 1D flex layout. | `direction` (horizontal/vertical), `gap`, `align`, `justify`, `wrap` |
|
|
303
|
+
| `Grid` | CSS Grid layout. | `cols` (number or "auto"), `gap`, `minColWidth` (when `cols="auto"`) |
|
|
304
|
+
|
|
305
|
+
### Foundation
|
|
306
|
+
|
|
307
|
+
- `ThemeProvider` - wraps app for runtime theme control. `defaultMode` (light/dark/system).
|
|
308
|
+
- `useTheme()` - returns `{ mode, setMode, resolvedMode }`.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Animation
|
|
313
|
+
|
|
314
|
+
**All entrance/exit animations on overlays use `react-spring`.** Don't reach for CSS keyframes.
|
|
315
|
+
|
|
316
|
+
### Built-in motion (just use the components)
|
|
317
|
+
- Modal, Alert: overlay fade + panel scale-translate spring
|
|
318
|
+
- Dropdown, Menu, Tooltip, Popover: pop-in spring
|
|
319
|
+
- Toast: slide-in spring with onExitComplete unmount
|
|
320
|
+
- Accordion: grid-template-rows 0fr → 1fr (CSS, height-auto-safe)
|
|
321
|
+
- Sidebar logo: crossfade between collapsed/expanded layers
|
|
322
|
+
- NavBar/Tabs active: sliding indicator
|
|
323
|
+
|
|
324
|
+
### Custom motion utility
|
|
325
|
+
For your own interactive components:
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
import { useSpringPresence, animated } from "@bigtablet/design-system";
|
|
329
|
+
|
|
330
|
+
function MyPopover({ open, onClose }) {
|
|
331
|
+
const [shouldRender, setShouldRender] = useState(open);
|
|
332
|
+
useEffect(() => { if (open) setShouldRender(true); }, [open]);
|
|
333
|
+
|
|
334
|
+
const style = useSpringPresence({
|
|
335
|
+
visible: open,
|
|
336
|
+
from: "translateY(-4px)",
|
|
337
|
+
onExitComplete: () => setShouldRender(false), // unmount after exit
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (!shouldRender) return null;
|
|
341
|
+
return <animated.div style={style}>...</animated.div>;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Reduced motion:** every component respects `prefers-reduced-motion: reduce`. Spring respects it automatically; CSS animations have explicit overrides.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Common Patterns
|
|
350
|
+
|
|
351
|
+
### Form
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
<Stack gap={16}>
|
|
355
|
+
<TextField label="Email" placeholder="you@example.com" supportingText="We'll never share." />
|
|
356
|
+
<Dropdown
|
|
357
|
+
label="Role"
|
|
358
|
+
options={[
|
|
359
|
+
{ value: "admin", label: "Admin" },
|
|
360
|
+
{ value: "editor", label: "Editor" },
|
|
361
|
+
]}
|
|
362
|
+
value={role}
|
|
363
|
+
onChange={setRole}
|
|
364
|
+
/>
|
|
365
|
+
<Stack direction="horizontal" gap={8} justify="end">
|
|
366
|
+
<Button variant="outline">Cancel</Button>
|
|
367
|
+
<Button variant="filled">Save</Button>
|
|
368
|
+
</Stack>
|
|
369
|
+
</Stack>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Confirmation with destructive action
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
const { showAlert } = useAlert();
|
|
376
|
+
|
|
377
|
+
<Button
|
|
378
|
+
variant="outline"
|
|
379
|
+
danger
|
|
380
|
+
onClick={() =>
|
|
381
|
+
showAlert({
|
|
382
|
+
title: "Delete project?",
|
|
383
|
+
message: "This cannot be undone.",
|
|
384
|
+
showCancel: true,
|
|
385
|
+
destructive: true,
|
|
386
|
+
confirmText: "Delete",
|
|
387
|
+
onConfirm: handleDelete,
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
>
|
|
391
|
+
Delete
|
|
392
|
+
</Button>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Toast feedback
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
const toast = useToast();
|
|
399
|
+
|
|
400
|
+
toast.success("Saved");
|
|
401
|
+
toast.error("Network error", 6000); // custom 6s duration
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Dashboard layout
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
<div style={{ display: "flex", minHeight: "100vh", background: "var(--bt-color-bg-solid-dim)" }}>
|
|
408
|
+
<Sidebar
|
|
409
|
+
header={<img src="/logo.png" alt="Brand" height={28} />}
|
|
410
|
+
headerCollapsed={<img src="/favicon.png" alt="" width={28} height={28} />}
|
|
411
|
+
footer={<UserProfile />}
|
|
412
|
+
>
|
|
413
|
+
<SidebarSection label="메인">
|
|
414
|
+
<SidebarItem icon={<Home size={20} />} active>Home</SidebarItem>
|
|
415
|
+
<SidebarItem icon={<Receipt size={20} />}>Orders</SidebarItem>
|
|
416
|
+
</SidebarSection>
|
|
417
|
+
</Sidebar>
|
|
418
|
+
|
|
419
|
+
<div style={{ flex: 1, padding: 32, overflowY: "auto" }}>
|
|
420
|
+
<Container size="xl">
|
|
421
|
+
<Stack gap={24}>
|
|
422
|
+
<h1 style={{ color: "var(--bt-color-text-heading)" }}>Dashboard</h1>
|
|
423
|
+
<Grid cols={2} gap={16}>
|
|
424
|
+
<StatCard />
|
|
425
|
+
<StatCard />
|
|
426
|
+
</Grid>
|
|
427
|
+
</Stack>
|
|
428
|
+
</Container>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Marketing landing
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
<Hero
|
|
437
|
+
title="Run smarter stores"
|
|
438
|
+
subtitle="Orders, inventory, staff - one platform."
|
|
439
|
+
backgroundImage="https://..."
|
|
440
|
+
overlay="dark"
|
|
441
|
+
height="lg"
|
|
442
|
+
align="center"
|
|
443
|
+
primaryAction={{ label: "Start free", onClick: ... }}
|
|
444
|
+
secondaryAction={{ label: "See demo", onClick: ... }}
|
|
445
|
+
/>
|
|
446
|
+
|
|
447
|
+
<Section spacing="lg" bg="default">
|
|
448
|
+
<Container size="xl">
|
|
449
|
+
<Stack gap={32}>
|
|
450
|
+
<Stack gap={8} align="center">
|
|
451
|
+
<Chip type="static" tone="accent" label="Features" />
|
|
452
|
+
<h2 style={{ color: "var(--bt-color-text-heading)" }}>Why Bigtablet?</h2>
|
|
453
|
+
</Stack>
|
|
454
|
+
<Grid cols="auto" minColWidth="280px" gap={24}>
|
|
455
|
+
{features.map(f => <MediaCard key={f.id} {...f} />)}
|
|
456
|
+
</Grid>
|
|
457
|
+
</Stack>
|
|
458
|
+
</Container>
|
|
459
|
+
</Section>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Anti-Patterns
|
|
465
|
+
|
|
466
|
+
| ❌ Don't | ✅ Do |
|
|
467
|
+
|---------|-------|
|
|
468
|
+
| `style={{ color: "#888" }}` | `style={{ color: "var(--bt-color-text-caption)" }}` |
|
|
469
|
+
| `background: "#fff"` for elevated panels | Conditional `bg-solid` light / `bg-solid-dim` dark |
|
|
470
|
+
| `background: brand-primary` for an active toggle/indicator | `accent-default` (auto flips for dark mode visibility) |
|
|
471
|
+
| `color: "#fff"` over a token-flipping bg | `accent-on-surface` (flips opposite to `accent-default`) |
|
|
472
|
+
| Raw `<button>` for form actions | `<Button>` component |
|
|
473
|
+
| Custom modal with CSS keyframes | `<Modal>` (spring built in) |
|
|
474
|
+
| Build dropdown from scratch | `<Dropdown>` |
|
|
475
|
+
| `transition: all 0.2s ease` | Specific properties + motion tokens |
|
|
476
|
+
| `animation: ... infinite` outside loading indicators | Spring (`useSpringPresence`) for entrance/exit |
|
|
477
|
+
| Skip `aria-label` on icon-only triggers | `<IconButton icon={...} aria-label="..." />` |
|
|
478
|
+
| Hardcode logo color white inside a Sidebar | Sidebar uses light bg by default - use `text-heading` for text |
|
|
479
|
+
| Use Tag component | Removed in v3.0 - use `<Chip type="static" tone="..." />` |
|
|
480
|
+
| Use Select component | Removed in v3.0 - use `<Dropdown>` |
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Storybook Reference
|
|
485
|
+
|
|
486
|
+
Stories are organized:
|
|
487
|
+
- **Getting Started** - Installation, Introduction
|
|
488
|
+
- **Cookbook** - Composition recipes (Form / Layout / Feedback / Data patterns)
|
|
489
|
+
- **Examples** - Full page patterns (Admin Dashboard, Marketing landing)
|
|
490
|
+
- **Foundation** - Token visualization (colors, spacing, typography, etc.)
|
|
491
|
+
- **Components/{Category}/{Component}** - Each component's variants
|
|
492
|
+
|
|
493
|
+
Run locally: `pnpm storybook` (port 6006).
|
|
494
|
+
|
|
495
|
+
**Note on Storybook Docs view:** Interactive states (hover, focus, animations) only show in Canvas view (individual story). Docs view renders static snapshots, so Spinner shows frozen, Tooltip won't appear, etc.
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Vanilla JS Bundle
|
|
500
|
+
|
|
501
|
+
For non-React contexts (Thymeleaf, JSP, PHP, Django):
|
|
502
|
+
|
|
503
|
+
```html
|
|
504
|
+
<link rel="stylesheet" href="https://unpkg.com/@bigtablet/design-system/dist/vanilla/bigtablet.min.css">
|
|
505
|
+
<script src="https://unpkg.com/@bigtablet/design-system/dist/vanilla/bigtablet.min.js"></script>
|
|
506
|
+
|
|
507
|
+
<button class="bt-button bt-button--md bt-button--filled">Filled</button>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Class naming: `.bt-{component}` + `--{modifier}` + `.is-{state}` (BEM-like).
|
|
511
|
+
|
|
512
|
+
Components available: Button, TextField, Checkbox, Radio, Toggle, Dropdown, Modal, Card, Spinner, Pagination, DatePicker, FileInput.
|
|
513
|
+
|
|
514
|
+
Class names and JS option names mirror the React API exactly - there are no deprecated aliases. See [MIGRATION.md](./MIGRATION.md#v380-vanilla-패키지-정리) for the v3.8.0 old → new map.
|
|
515
|
+
|
|
516
|
+
JS API (auto-init on DOMContentLoaded, or manual):
|
|
517
|
+
```js
|
|
518
|
+
const dropdown = Bigtablet.Dropdown("#my-dropdown", { options, onValueChange });
|
|
519
|
+
// React 와 동일한 이름의 multiple / searchable 도 지원 (data-multiple / data-searchable 로도 가능)
|
|
520
|
+
Bigtablet.Dropdown("#multi", { multiple: true, searchable: true, selectedSummary: (n) => `${n}개 선택` });
|
|
521
|
+
const modal = Bigtablet.Modal("#my-modal", { onOpen, onClose });
|
|
522
|
+
Bigtablet.Alert({ title, message, showCancel: true, onConfirm });
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Dark mode: same `[data-theme="dark"]` attribute works. CSS custom properties expose all tokens with `--bt-` prefix.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Constraints for Code Generation
|
|
530
|
+
|
|
531
|
+
When asked to generate UI:
|
|
532
|
+
|
|
533
|
+
1. **Always** wrap your output in the appropriate provider chain if the app entry isn't shown.
|
|
534
|
+
2. **Always** import components from `@bigtablet/design-system` root.
|
|
535
|
+
3. **Always** use tokens (`var(--bt-color-*)`) for any inline style or custom SCSS.
|
|
536
|
+
4. **Always** check both light and dark modes in your mental model. If a color choice fails in either theme, swap to a flipping token.
|
|
537
|
+
5. **Never** generate a custom Button/Modal/Dropdown/Tooltip when the DS provides one.
|
|
538
|
+
6. **Never** use `useLayoutEffect` for non-DOM-measurement work (Next.js SSR warning). Default to `useEffect`.
|
|
539
|
+
7. **For interactive content**, ensure: `aria-label`/`aria-labelledby`, keyboard support (Enter/Space for buttons, Arrow keys for menus), and `:focus-visible` styling.
|
|
540
|
+
8. **For images**, set `alt` (empty `""` if purely decorative).
|
|
541
|
+
9. **Korean / English text**: both supported. Use `word-break: keep-all` if you set custom long-text styles in Korean contexts.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## Migration from v2.x
|
|
546
|
+
|
|
547
|
+
- `Select` → `Dropdown` (`SelectOption` → `DropdownOption`)
|
|
548
|
+
- `Tag` → `<Chip type="static" tone="..." />`
|
|
549
|
+
- `Chip` import path changed (`general/chip` → `display/chip`) - root import unaffected
|
|
550
|
+
- `Dropdown` `fullWidth` prop is now a no-op (always full width)
|
|
551
|
+
- `Icon` API: `<Icon name="search" />` → `<Icon icon={Search} />` (pass lucide-react component)
|
|
552
|
+
- Vanilla `--bt-color-primary` is now reserved for Button `--primary` only. Use `--bt-color-accent` for indicators.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## Quick Decision Tree
|
|
557
|
+
|
|
558
|
+
**Need a button?** → `<Button>` (variant: filled for primary, outline for secondary, text for tertiary, danger for destructive)
|
|
559
|
+
|
|
560
|
+
**Need a text input?** → `<TextField>`. For value selection from a list → `<Dropdown>`.
|
|
561
|
+
|
|
562
|
+
**Need a dialog?** → `<Modal>` for arbitrary content. `useAlert()` for simple confirm/alert.
|
|
563
|
+
|
|
564
|
+
**Need a notification?** → `useToast()`. For inline alerts within page → `<Alert>` component.
|
|
565
|
+
|
|
566
|
+
**Need to show loading?** → `<Spinner>` inline, `<TopLoading>` for page-level, `<Skeleton>` for content placeholders, `<LinearProgress>` for step-based progress.
|
|
567
|
+
|
|
568
|
+
**Need a layout?** → `Container > Section > Stack/Grid`. Don't manually do flexbox/grid CSS unless inside a Stack/Grid child.
|
|
569
|
+
|
|
570
|
+
**Need a tooltip?** → `<Tooltip content="..." placement="top"><TriggerElement /></Tooltip>`.
|
|
571
|
+
|
|
572
|
+
**Need a click-triggered panel with interactive content (filter form, profile card, inline actions)?** → `<Popover trigger={<Button>…</Button>} content={…} aria-label="…" />`. For a plain action list use `<Menu>`; for hover-only info use `<Tooltip>`; for a blocking center dialog use `<Modal>`.
|
|
573
|
+
|
|
574
|
+
**Need a sidebar nav?** → `<Sidebar>` with `<SidebarSection>` + `<SidebarItem>`. Include `headerCollapsed` for collapse animation.
|
|
575
|
+
|
|
576
|
+
**Need a data table?** → `<Table>`. Pass `onRowClick` for interactive rows (keyboard support included).
|
|
577
|
+
|
|
578
|
+
**Building a form?** → Stack vertical with gap=16. Group buttons in a horizontal Stack with `justify="end"` at the bottom.
|
|
579
|
+
|
|
580
|
+
**Building a marketing page?** → Hero + Section(s) with Container + Grid for feature cards.
|
|
581
|
+
|
|
582
|
+
**Building an admin dashboard?** → Sidebar layout + main content in `<Container size="xl">`. Stats in `<Grid cols={4}>`. Tables, charts, lists below.
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Final Reminders
|
|
587
|
+
|
|
588
|
+
- Read the [Components](./COMPONENTS.md) doc for full props API per component.
|
|
589
|
+
- Storybook is the source of truth for visual behavior - `pnpm storybook` and explore.
|
|
590
|
+
- When in doubt, **use tokens, use components, use providers**. Don't reinvent.
|
|
591
|
+
- Dark mode bugs are the most common failure mode - test both themes mentally before shipping code.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigtablet/design-system",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Bigtablet Design System UI Components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"!dist/vanilla/bigtablet.js",
|
|
30
30
|
"!dist/vanilla/examples",
|
|
31
31
|
"README.md",
|
|
32
|
-
"LICENSE"
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"docs/AGENT_GUIDE.md"
|
|
33
34
|
],
|
|
34
35
|
"sideEffects": [
|
|
35
36
|
"**/*.css",
|