@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.
@@ -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.16.0",
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",