@1337exp/taoui 1.11.1 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,545 +1,545 @@
1
- # Tao UI
2
-
3
- **English** · [Русский](README.ru.md)
4
-
5
- A Vue 3 component library for Nuxt/Vue apps, driven by design tokens:
6
- change a handful of CSS variables and the whole kit follows.
7
-
8
- ## Components
9
-
10
- ### Basics
11
-
12
- - **TaoBlock** — container with padding and radius
13
- - **TaoButton** — `primary` / `secondary` / `ghost` / `text` / `danger`. `tone` tints ghost and text (danger, success, warning, info)
14
- - **TaoCard** — card with cover / header / footer slots. `#cover` can be a photo or `TaoCarousel`. `flush` is a tile (no padding/shadow, fills the cell); `#overlay` is a scrim on the cover
15
- - **TaoCopy** — clipboard icon. After a click, a “Copied” tooltip; if the trigger is gone, the tooltip is too
16
- - **TaoInput** — text field with v-model, validation, and `#prefix` / `#suffix` slots (icon or text inside the frame)
17
- - **TaoInputGroup** — outer glue: `#before` | field | `#after` (protocol, unit, button)
18
- - **TaoModal** — modal with animation and slots
19
- - **TaoDrawer** — side panel (filters, settings). Esc, backdrop click, focus trap — same as the modal
20
- - **TaoSpoiler** — disclosure. `v-model` and `aria-expanded`. Neighbours do not close each other
21
- - **TaoSpoilerGroup** — accordion: `v-model` is the open panel key (`null` = all closed); spoilers take `name`. Without `v-model` the group keeps that key itself
22
- - **TaoTabs** — tabbed sections
23
- - **TaoTooltip** — hover hint. `top` / `bottom` / `left` / `right`, flips at the viewport edge
24
- - **TaoPopover** — click panel with any slot content, Esc and outside click. Not a tooltip, not an action menu
25
-
26
- ### Layout
27
-
28
- - **TaoContainer** — max-width (wide / slim / ultra-slim) and centering
29
- - **TaoFlex** — flexbox wrapper: justify / align / direction / wrap / gap
30
- - **TaoSpace** — even gap between children
31
- - **TaoGrid** / **TaoGridItem** — tiles: `min` is a catalog (auto-fill), `cols` is bento. Item `col` / `row` span; below ~560px span resets. Not masonry
32
- - **TaoDivider** — `solid` (plain border) / `dashed` / `colored` (accent) / `gap`. Optional `text` sits in the middle of any of them; `fade` softens the line ends; `size` is for `gap` (small / medium / large)
33
- - **TaoSplit** — two panes and a gutter. `row` / `column` like Flex. `v-model` is the start size in `%` or `px`; `resizable="false"` locks the gutter. Not Divider.
34
- - **TaoFieldset** — grouped frame: `legend` sits on the top border, `align` is start / center / end
35
- - **TaoAnimatedBorder** — rainbow border wrapper (decorative, not tied to the theme)
36
-
37
- ### Forms
38
-
39
- - **TaoCheckbox** — checkbox with v-model, pre/post slots, and `indeterminate`
40
- - **TaoSwitch** — boolean toggle (`role="switch"`)
41
- - **TaoRadio** / **TaoRadioGroup** — classic radios: longer labels, can stack vertically
42
- - **TaoSegmented** / **TaoSegmentedOption** — compact exclusive choice among 2–5 short options. Not tabs
43
- - **TaoSelect** — dropdown with keyboard, same chrome as Input. Clear via “Clear” in the list or Delete
44
- - **TaoCombobox** — Select with a field: type to filter, `allow-create` writes a custom value into `v-model`, `create` lets the parent append the list
45
- - **TaoDate** — a single day: `YYYY-MM-DD`, no time, no timezone. Calendar popup like Select
46
- - **TaoDateRange** — a period: `v-model` is `{ start, end }` or `null`. First click is start, second is end (they swap if end is earlier). An incomplete pick is not written to the model
47
- - **TaoFormField** — shared label / hint / error for form controls
48
- - **TaoInputNumber** — number with min/max/step and ± on the right. Empty value is `null`
49
- - **TaoQuantity** — cart quantity: `− | field | +`, integers, stock; minus at the minimum can remove the row
50
- - **TaoTextarea** — autosizing multiline. `maxlength` caps input, `count` shows “entered / max”
51
- - **TaoPinCode** — N cells. By default a click clears the cell and everything to the right (`clear-on="focus"`); `clear-on="input"` replaces the digit only when typing. The last cell blurs and emits `complete`
52
- - **TaoSlider** — mouse, touch, and arrows. Optional precise input on right-click. `size="small"` is a thin track for seek / volume
53
- - **TaoFileDrop** — drop zone (drag & drop + click). File names listed under the zone, `v-model` is `File[]`. Clearing the whole batch is the parent’s job (`clear-request`)
54
-
55
- ### Data display
56
-
57
- - **TaoTable** — simple table: columns, empty, loading, sort via `v-model:sort`. `head` tints the header, `lines` draws row rules (both on by default). `striped` starts on the first row; `striped="even"` starts on the second. Not a datagrid.
58
- - **TaoPagination** — pages with ellipsis. Clicking “…” jumps `jump` pages (5 by default)
59
- - **TaoEmpty** — empty list, no results, no access
60
- - **TaoList** / **TaoListItem** — list row: icon, title, caption, hover action. Not Select, not a table
61
- - **TaoStages** — stage list: `wait` / `work` / `ok` / `bad`. Not a linear stepper
62
- - **TaoSkeleton** — loading placeholder (text / title / circle / rect)
63
- - **TaoCounter** — flipping digits (not an input)
64
- - **TaoCarousel** — strip: full slide with `autoplay`, card with `peek`, row with `per-view`. Arrows via `controls` or `#prev` / `#next`, dots via `dots`
65
- - **TaoAvatar** — photo or initials, sizes `small` / `medium` / `large`. Status `dot` and `count`
66
- - **TaoTag** — badge with tones. `#prefix` (avatar), `closable` dismisses the chip, `flat` drops the border
67
- - **TaoAlert** — inline page/form banner (success / error / warning / info)
68
- - **TaoProgress** — static bar (`progress` in %). Percent centered on top, or `showPercentage="right"`; `#left` / `#right` slots are captions, then the percent sits in parentheses on the right. `false` hides it. Width is `minWidth` / `maxWidth`, not the value domain
69
- - **TaoLoader** — four-dot spinner
70
- - **TaoImage** — `<img>` wrapper: fade-in and placeholder. `lazy` turns on native `loading="lazy"`
71
- - **TaoIcon** — icon-font wrapper (see Icons below)
72
-
73
- ### Navigation
74
-
75
- - **TaoLink** — `<NuxtLink>` in Nuxt, `<RouterLink>` with vue-router, otherwise a plain `<a>` — no extra setup
76
- - **TaoBreadcrumb** — crumbs: the last item is the current page
77
- - **TaoDropdownMenu** — dropdown that flips at the viewport edge
78
- - **TaoScrollTop** — floating “back to top” with a ripple. Appears on reverse scroll after `boundary`
79
-
80
- ### Feedback
81
-
82
- - **toast()** — fluent toasts: `toast().success().message('Saved')`
83
- - **confirm()** — overlay question: the same fluent API plus `await`. Not a toast — it waits for an answer and does not dismiss itself.
84
- - **TaoToastViewport** / **TaoConfirmHost** — hosts; if they are missing from the tree, the first call mounts them on `body`
85
-
86
- ## Conventions
87
-
88
- Visual tones: `success` | `danger` | `warning` | `info` | `neutral`.
89
- `error` on Alert, Tag, and `toast()` is an alias of `danger`. Button already uses `danger`.
90
- Stages describe a process, not a tone: `wait` / `work` / `ok` / `bad` (`danger` is accepted as `bad`).
91
-
92
- Control sizes: `small` | `medium` | `large`.
93
- Avatar also accepts short `s` / `m` / `l`. Divider `gap` used to call the large size `big` — that is an alias of `large`.
94
- Container lives on another axis: `wide` / `slim` / `ultra-slim`.
95
-
96
- Font size scale (primitives): `xs` 12 … `xl` 18 for controls; `2xl` 24, `3xl` 32, `4xl` 36 for titles and prose.
97
- There is no heading component — use `h1 { font-size: var(--tao-font-size-3xl) }`.
98
-
99
- ## Icons
100
-
101
- `TaoIcon` does not ship its own set — it renders `icon-<name>`
102
- for an external icon font (IcoMoon / Fontello and the like). Connect a font
103
- with classes such as `.icon-arrow-up { ... }` in the host app.
104
-
105
- ## Install
106
-
107
- ```bash
108
- npm install @1337exp/taoui
109
- ```
110
-
111
- From a local folder:
112
-
113
- ```bash
114
- npm install ../path-to-tao-ui
115
- ```
116
-
117
- ## Usage
118
-
119
- ### Nuxt 3
120
-
121
- Create `plugins/tao-ui.ts`:
122
-
123
- ```typescript
124
- import { defineNuxtPlugin } from '#app'
125
- import TaoUI from '@1337exp/taoui'
126
-
127
- export default defineNuxtPlugin((nuxtApp) => {
128
- nuxtApp.vueApp.use(TaoUI)
129
- })
130
- ```
131
-
132
- Import styles in `app.vue` or a layout:
133
-
134
- ```vue
135
- <script setup>
136
- import '@1337exp/taoui/styles.css'
137
- </script>
138
- ```
139
-
140
- ### Vue 3
141
-
142
- ```javascript
143
- // main.js
144
- import { createApp } from 'vue'
145
- import App from './App.vue'
146
- import TaoUI from '@1337exp/taoui'
147
- import '@1337exp/taoui/styles.css'
148
-
149
- const app = createApp(App)
150
- app.use(TaoUI)
151
- app.mount('#app')
152
- ```
153
-
154
- ### Individual components
155
-
156
- ```vue
157
- <script setup>
158
- import { TaoButton, TaoInput, TaoModal } from '@1337exp/taoui'
159
- import '@1337exp/taoui/styles.css'
160
- </script>
161
-
162
- <template>
163
- <TaoButton variant="primary">Press me</TaoButton>
164
- <TaoInput v-model="text" label="Your text" />
165
- <TaoModal v-model="isOpen" title="Title">
166
- Modal body
167
- </TaoModal>
168
- </template>
169
- ```
170
-
171
- ## Split
172
-
173
- `TaoSplit` is two panes and a gutter. It is not `TaoDivider` (a line) and not an n-pane layout: three columns are two nested splits.
174
-
175
- `direction` matches Flex: `row` (start | end) or `column` (start above end). Slots are `#start` / `#end`.
176
-
177
- `v-model` is the start pane size. `unit` is `%` (default — the ratio survives a container resize) or `px` (a sidebar; the end pane takes the rest). With `px`, pass an explicit size — the default `50` is 50px. `min` / `max` apply to start; `min-end` is the other pane's floor; all three use the same unit. `resizable="false"` keeps the gutter but locks it.
178
-
179
- The split fills the parent (`width` / `height: 100%`). Give the parent a bounded size on the split axis — a `column` split without a height has no `%` to compute. Panes use `min-width` / `min-height: 0` and `overflow: hidden`, so content cannot block shrinking. Scroll belongs on an inner wrapper (`overflow: auto; height: 100%; min-height: 0`). A nested `TaoSplit` as a direct child of a pane stretches on its own.
180
-
181
- ```vue
182
- <TaoSplit v-model="side" unit="px" :min="160" :max="480" :min-end="240">
183
- <template #start>Nav</template>
184
- <template #end>Main</template>
185
- </TaoSplit>
186
- ```
187
-
188
- ## Toast
189
-
190
- The chain runs in the same tick; `setTimeout(0)` actually shows the toast.
191
-
192
- ```javascript
193
- import { toast } from '@1337exp/taoui'
194
-
195
- toast().success().message('Saved')
196
- toast().error().title('Network').timeout(5000).message('No connection')
197
- toast.success('Saved')
198
- toast.warning('Draft', { timeout: 1000 })
199
- ```
200
-
201
- Default position is `topCenter`. Globally:
202
-
203
- ```javascript
204
- toast.defaults({ position: 'bottomRight' })
205
- toast.byBottomRight()
206
- ```
207
-
208
- There is no overlay `question` in this kit — that is confirm, not toast. Buttons on a toast, if needed:
209
-
210
- ```javascript
211
- toast().info().message('Delete the file?').action('Delete', onRemove)
212
- ```
213
-
214
- You can skip the viewport: the first call creates it. For explicit control (tests, a custom layout):
215
-
216
- ```vue
217
- <TaoToastViewport />
218
- ```
219
-
220
- ## Confirm
221
-
222
- An overlay question in the center of the screen. This is not a toast: no progress bar, no auto-close, `await`, Esc and overlay click = cancel.
223
-
224
- ```javascript
225
- import { confirm } from '@1337exp/taoui'
226
-
227
- if (await confirm().title('Delete the file?').message('This cannot be undone').danger()) {
228
- remove()
229
- }
230
-
231
- const ok = await confirm('Leave without saving?', {
232
- ok: 'Leave',
233
- cancel: 'Stay',
234
- })
235
- ```
236
-
237
- Queue: a second `confirm()` while the first is open will wait. Default labels are “OK” / “Cancel”:
238
-
239
- ```javascript
240
- confirm.defaults({ ok: 'Yes', cancel: 'No' })
241
- ```
242
-
243
- ## Forms
244
-
245
- `TaoFormField` attaches a shared label / hint / error and forwards `id` to the nested control.
246
-
247
- ```vue
248
- <TaoFormField label="City" hint="Optional">
249
- <TaoSelect v-model="city" :options="cities" />
250
- </TaoFormField>
251
-
252
- <TaoSwitch v-model="dark" label="Dark theme" />
253
-
254
- <TaoRadioGroup v-model="plan" legend="Plan">
255
- <TaoRadio value="free" label="Free" />
256
- <TaoRadio value="pro" label="Pro" />
257
- </TaoRadioGroup>
258
-
259
- <TaoSegmented v-model="period" legend="Period">
260
- <TaoSegmentedOption value="week">Week</TaoSegmentedOption>
261
- <TaoSegmentedOption value="month">Month</TaoSegmentedOption>
262
- <TaoSegmentedOption value="year">Year</TaoSegmentedOption>
263
- </TaoSegmented>
264
-
265
- <TaoAlert type="warning" title="Draft">Save before you leave.</TaoAlert>
266
- ```
267
-
268
- `TaoSegmented` is a short exclusive choice with every option visible. It is not page navigation — that is `TaoTabs`. Longer labels or more than 5–6 items belong in `TaoRadioGroup` or `TaoSelect`.
269
-
270
- `TaoSelect` opens from the keyboard (arrows, Enter, Esc); the list flips at the viewport edge. Delete or “Clear” at the bottom of the list resets the value to `null`.
271
-
272
- `TaoCombobox` is the same menu with a field. Typing filters options. `allow-create` writes the typed string into `v-model` when there is no exact match. The list does not mutate itself: listen to `create` and append an option from the parent.
273
-
274
- `TaoFormField` also wraps `TaoTextarea`, `TaoCheckbox`, `TaoSwitch`, `TaoRadioGroup`, and `TaoSegmented` — id, hint, and error are forwarded for you.
275
-
276
- `TaoTag` can be a chip: `#prefix` on the left (usually `TaoAvatar`), `closable` draws a cross and emits `close`. The close click does not bubble. `flat` drops the border and keeps the fill.
277
-
278
- ```vue
279
- <TaoTag type="neutral" closable @close="remove">
280
- <template #prefix>
281
- <TaoAvatar name="Anna Kozlova" size="small" />
282
- </template>
283
- Anna Kozlova
284
- </TaoTag>
285
- ```
286
-
287
- ## Table and pagination
288
-
289
- The table renders what you pass in: it does not slice or sort the data. That works for both server-side and client-side paging — the page is cut outside.
290
-
291
- ```vue
292
- <TaoTable
293
- :columns="columns"
294
- :rows="pageRows"
295
- v-model:sort="sort"
296
- empty-text="No records yet"
297
- >
298
- <template #cell-status="{ row }">
299
- <TaoTag :type="row.status === 'active' ? 'success' : 'neutral'">
300
- {{ row.status }}
301
- </TaoTag>
302
- </template>
303
- </TaoTable>
304
-
305
- <TaoPagination v-model:page="page" :total="rows.length" :page-size="8" />
306
- ```
307
-
308
- ```javascript
309
- const columns = [
310
- { key: 'name', label: 'Name', sortable: true },
311
- { key: 'city', label: 'City' },
312
- { key: 'status', label: 'Status' },
313
- ]
314
- ```
315
-
316
- The `cell-<key>` slot replaces a cell. `loading` shows an overlay; an empty list shows text or the `#empty` slot. Clicking a `sortable` header cycles `asc → desc → off`.
317
-
318
- ## Empty states and loading
319
-
320
- ```vue
321
- <TaoEmpty title="Nothing found">
322
- Change the filter or reset search.
323
- <template #action>
324
- <TaoButton variant="secondary">Reset</TaoButton>
325
- </template>
326
- </TaoEmpty>
327
-
328
- <TaoSkeleton variant="title" />
329
- <TaoSkeleton :lines="3" />
330
- <TaoSkeleton variant="circle" />
331
-
332
- <TaoStages :items="[
333
- { key: 'pay', label: 'Payment', status: 'ok' },
334
- { key: 'pack', label: 'Packing', status: 'work' },
335
- { key: 'ship', label: 'Shipping', status: 'wait' },
336
- ]" />
337
-
338
- <TaoList>
339
- <TaoListItem
340
- title="Ann Kozlova"
341
- description="Design · Moscow"
342
- clickable
343
- :selected="picked === 'ann'"
344
- @click="picked = 'ann'"
345
- >
346
- <template #prefix>
347
- <TaoAvatar name="Ann Kozlova" size="small" />
348
- </template>
349
- </TaoListItem>
350
- </TaoList>
351
- ```
352
-
353
- ```vue
354
- <TaoInput v-model="q" placeholder="Search…">
355
- <template #prefix>⌕</template>
356
- </TaoInput>
357
-
358
- <TaoInputGroup>
359
- <template #before>https://</template>
360
- <TaoInput v-model="host" placeholder="example.com" />
361
- <template #after>
362
- <TaoButton>Check</TaoButton>
363
- </template>
364
- </TaoInputGroup>
365
-
366
- <form @submit.prevent="onSave">
367
- <TaoFormField label="Email">
368
- <TaoInput v-model="email" type="email" />
369
- </TaoFormField>
370
- <TaoButton type="submit">Save</TaoButton>
371
- </form>
372
-
373
- <TaoFormField label="Quantity">
374
- <TaoInputNumber v-model="qty" :min="1" :max="99" />
375
- </TaoFormField>
376
-
377
- <TaoQuantity v-model="qty" :max="12" @dec="onDec" />
378
-
379
- <TaoFormField label="Delivery day" hint="The model is always YYYY-MM-DD">
380
- <TaoDate v-model="day" min="2026-09-01" max="2026-09-30" />
381
- </TaoFormField>
382
-
383
- <TaoFormField label="Stay">
384
- <TaoDateRange v-model="stay" min="2026-09-01" max="2026-09-30" />
385
- </TaoFormField>
386
-
387
- <TaoCounter :value="score" :max-digits="6" />
388
-
389
- <TaoCarousel :autoplay="4000" loop dots>
390
- <article v-for="item in banners" :key="item.id" class="hero">
391
- <strong>{{ item.title }}</strong>
392
- <span>{{ item.text }}</span>
393
- </article>
394
- </TaoCarousel>
395
- <TaoCarousel :peek="72" :controls="false" dots>…</TaoCarousel>
396
- <TaoCarousel v-model="slide" :per-view="5" :peek="28">
397
- <template #prev></template>
398
- <template #next="{ go, disabled }">
399
- <button type="button" :disabled="disabled" @click="go">next</button>
400
- </template>
401
- <article v-for="item in products" :key="item.id">…</article>
402
- </TaoCarousel>
403
- ```
404
-
405
- `TaoDrawer` is a side sheet, not a centered modal:
406
-
407
- ```vue
408
- <TaoDrawer v-model="open" title="Filters">
409
- ...
410
- </TaoDrawer>
411
- ```
412
-
413
- ## Design tokens and theming
414
-
415
- Library styles are three layers. Components **never** touch raw colors or sizes —
416
- only semantic variables. That is the theming mechanism: override 20–30 variables
417
- and every component follows, with no edits inside the components themselves.
418
-
419
- ```
420
- primitives.css → semantic.css → themes/*.css (optional)
421
- raw colors public theming ready-made themes on top
422
- and scales, API: this is of semantic — light is
423
- leave them alone what you change the example
424
- ```
425
-
426
- ### Layers
427
-
428
- | File | Role | Touch it? |
429
- |---|---|---|
430
- | `primitives.css` | Raw palette, space / radius / shadow scales (`--tao-orange-400`, `--tao-space-3`…) | Usually no |
431
- | `semantic.css` | Default (dark) theme tokens: `--tao-color-accent`, `--tao-color-surface`, `--tao-color-danger`, etc. | **Yes — this is the theming API** |
432
- | `themes/light.css` | Alternate theme via `[data-tao-theme="light"]` | Yes, as a sample |
433
- | `base.css` | Global reset (scrollbar, links, box-sizing) | Rarely |
434
-
435
- All four come in through `@1337exp/taoui/styles.css`.
436
-
437
- ### Your own theme
438
-
439
- A theme is just semantic tokens under your selector.
440
- Nothing in the components needs to change:
441
-
442
- ```css
443
- /* my-brand-theme.css */
444
- [data-tao-theme="my-brand"] {
445
- --tao-color-accent: #6c5ce7;
446
- --tao-color-accent-hover: #5a4bd1;
447
- --tao-color-bg: #0f0f1a;
448
- --tao-color-surface: #0f0f1a;
449
- --tao-color-surface-raised: #1a1a2e;
450
- --tao-color-text: #d6d6e8;
451
- --tao-color-text-strong: #ffffff;
452
- /* remaining tokens: see semantic.css */
453
- }
454
- ```
455
-
456
- ```vue
457
- <!-- app.vue -->
458
- <template>
459
- <div data-tao-theme="my-brand">
460
- <NuxtPage />
461
- </div>
462
- </template>
463
- ```
464
-
465
- Or switch the theme on `<html>` so modals/tooltips teleported to `body` pick it up:
466
-
467
- ```javascript
468
- document.documentElement.setAttribute('data-tao-theme', 'light')
469
- ```
470
-
471
- ### Token groups
472
-
473
- - `--tao-color-accent*` — brand/accent (primary buttons, active tabs, links)
474
- - `--tao-color-on-accent` — text on the accent (`--tao-color-text-on-accent` is an alias)
475
- - `--tao-color-selection`, `--tao-color-selection-text` — text selection (`::selection`)
476
- - `--tao-color-surface*` — container backgrounds (default / raised / sunken / hover)
477
- - `--tao-color-border*` — borders
478
- - `--tao-color-text*` — text (default / strong / subtle / muted / disabled)
479
- - `--tao-color-danger*`, `--tao-color-success*`, `--tao-color-warning*`, `--tao-color-info*` — status colors
480
- - `--tao-color-input-*` — fields, because input bg/text often differs from surface/text
481
- - `--tao-radius-control`, `--tao-radius-panel` — radii (small controls vs large panels)
482
- - `--tao-shadow-panel`, `--tao-shadow-overlay` — shadows
483
- - `--tao-font-size-*` — type scale in `primitives.css`: `xs`–`xl` controls, `2xl`–`4xl` titles / prose
484
-
485
- The full list lives in `src/styles/semantic.css` — the only file worth opening
486
- to see the whole theming API.
487
-
488
- ### A theme from scratch, without primitives
489
-
490
- ```javascript
491
- import '@1337exp/taoui/styles/primitives.css' // keep the scales if you want
492
- import './my-semantic.css' // your semantic layer instead of @1337exp/taoui/styles/semantic.css
493
- import '@1337exp/taoui/styles/base.css'
494
- ```
495
-
496
- ## Showcase
497
-
498
- To browse every component:
499
-
500
- ```bash
501
- cd showcase
502
- npm install
503
- npm run dev
504
- ```
505
-
506
- The showcase has a dark / light switch in the header, so you can see token
507
- overrides without editing component code.
508
-
509
- The showcase points at `src/` directly, so component edits show up without
510
- `npm run build`. Building `dist/` is for publishing and for other projects
511
- that install the package.
512
-
513
- ## Development
514
-
515
- ```bash
516
- # Build the library
517
- npm run build
518
-
519
- # Watch mode
520
- npm run dev
521
-
522
- # Type check
523
- npm run type-check
524
- ```
525
-
526
- ## Publishing to npm
527
-
528
- Only `dist/` (the built library) and `src/styles/` (raw tokens, so you can
529
- import `primitives.css` on its own for a custom theme) are published.
530
- Component sources (`src/components/*.vue`), build configs, and `showcase/`
531
- stay in the repo — clone it to get them.
532
-
533
- `npm publish` **itself** runs the type check and a full rebuild first
534
- (`prepublishOnly` in `package.json`), so a stale or missing `dist/` cannot
535
- reach the registry. You do not need to build by hand before `npm publish`.
536
-
537
- See what would go into the tarball, without publishing:
538
-
539
- ```bash
540
- npm pack --dry-run
541
- ```
542
-
543
- ## License
544
-
545
- MIT
1
+ # Tao UI
2
+
3
+ **English** · [Русский](README.ru.md)
4
+
5
+ A Vue 3 component library for Nuxt/Vue apps, driven by design tokens:
6
+ change a handful of CSS variables and the whole kit follows.
7
+
8
+ ## Components
9
+
10
+ ### Basics
11
+
12
+ - **TaoBlock** — container with padding and radius
13
+ - **TaoButton** — `primary` / `secondary` / `ghost` / `text` / `danger`. `tone` tints ghost and text (danger, success, warning, info)
14
+ - **TaoCard** — card with cover / header / footer slots. `#cover` can be a photo or `TaoCarousel`. `flush` is a tile (no padding/shadow, fills the cell); `#overlay` is a scrim on the cover
15
+ - **TaoCopy** — clipboard icon. After a click, a “Copied” tooltip; if the trigger is gone, the tooltip is too
16
+ - **TaoInput** — text field with v-model, validation, and `#prefix` / `#suffix` slots (icon or text inside the frame)
17
+ - **TaoInputGroup** — outer glue: `#before` | field | `#after` (protocol, unit, button)
18
+ - **TaoModal** — modal with animation and slots
19
+ - **TaoDrawer** — side panel (filters, settings). Esc, backdrop click, focus trap — same as the modal
20
+ - **TaoSpoiler** — disclosure. `v-model` and `aria-expanded`. Neighbours do not close each other
21
+ - **TaoSpoilerGroup** — accordion: `v-model` is the open panel key (`null` = all closed); spoilers take `name`. Without `v-model` the group keeps that key itself
22
+ - **TaoTabs** — tabbed sections
23
+ - **TaoTooltip** — hover hint. `top` / `bottom` / `left` / `right`, flips at the viewport edge
24
+ - **TaoPopover** — click panel with any slot content, Esc and outside click. Not a tooltip, not an action menu
25
+
26
+ ### Layout
27
+
28
+ - **TaoContainer** — max-width (wide / slim / ultra-slim) and centering
29
+ - **TaoFlex** — flexbox wrapper: justify / align / direction / wrap / gap
30
+ - **TaoSpace** — even gap between children
31
+ - **TaoGrid** / **TaoGridItem** — tiles: `min` is a catalog (auto-fill), `cols` is bento. Item `col` / `row` span; below ~560px span resets. Not masonry
32
+ - **TaoDivider** — `solid` (plain border) / `dashed` / `colored` (accent) / `gap`. Optional `text` sits in the middle of any of them; `fade` softens the line ends; `size` is for `gap` (small / medium / large)
33
+ - **TaoSplit** — two panes and a gutter. `row` / `column` like Flex. `v-model` is the start size in `%` or `px`; `resizable="false"` locks the gutter. Not Divider.
34
+ - **TaoFieldset** — grouped frame: `legend` sits on the top border, `align` is start / center / end
35
+ - **TaoAnimatedBorder** — rainbow border wrapper (decorative, not tied to the theme)
36
+
37
+ ### Forms
38
+
39
+ - **TaoCheckbox** — checkbox with v-model, pre/post slots, and `indeterminate`
40
+ - **TaoSwitch** — boolean toggle (`role="switch"`)
41
+ - **TaoRadio** / **TaoRadioGroup** — classic radios: longer labels, can stack vertically
42
+ - **TaoSegmented** / **TaoSegmentedOption** — compact exclusive choice among 2–5 short options. Not tabs
43
+ - **TaoSelect** — dropdown with keyboard, same chrome as Input. Clear via “Clear” in the list or Delete
44
+ - **TaoCombobox** — Select with a field: type to filter, `allow-create` writes a custom value into `v-model`, `create` lets the parent append the list
45
+ - **TaoDate** — a single day: `YYYY-MM-DD`, no time, no timezone. Calendar popup like Select
46
+ - **TaoDateRange** — a period: `v-model` is `{ start, end }` or `null`. First click is start, second is end (they swap if end is earlier). An incomplete pick is not written to the model
47
+ - **TaoFormField** — shared label / hint / error for form controls
48
+ - **TaoInputNumber** — number with min/max/step and ± on the right. Empty value is `null`
49
+ - **TaoQuantity** — cart quantity: `− | field | +`, integers, stock; minus at the minimum can remove the row
50
+ - **TaoTextarea** — autosizing multiline. `maxlength` caps input, `count` shows “entered / max”
51
+ - **TaoPinCode** — N cells. By default a click clears the cell and everything to the right (`clear-on="focus"`); `clear-on="input"` replaces the digit only when typing. The last cell blurs and emits `complete`
52
+ - **TaoSlider** — mouse, touch, and arrows. Optional precise input on right-click. `size="small"` is a thin track for seek / volume
53
+ - **TaoFileDrop** — drop zone (drag & drop + click). File names listed under the zone, `v-model` is `File[]`. Clearing the whole batch is the parent’s job (`clear-request`)
54
+
55
+ ### Data display
56
+
57
+ - **TaoTable** — simple table: columns, empty, loading, sort via `v-model:sort`. `head` tints the header, `lines` draws row rules (both on by default). `striped` starts on the first row; `striped="even"` starts on the second. Not a datagrid.
58
+ - **TaoPagination** — pages with ellipsis. Clicking “…” jumps `jump` pages (5 by default)
59
+ - **TaoEmpty** — empty list, no results, no access
60
+ - **TaoList** / **TaoListItem** — list row: icon, title, caption, hover action. Not Select, not a table
61
+ - **TaoStages** — stage list: `wait` / `work` / `ok` / `bad`. Not a linear stepper
62
+ - **TaoSkeleton** — loading placeholder (text / title / circle / rect)
63
+ - **TaoCounter** — flipping digits (not an input)
64
+ - **TaoCarousel** — strip: full slide with `autoplay`, card with `peek`, row with `per-view`. Arrows via `controls` or `#prev` / `#next`, dots via `dots`
65
+ - **TaoAvatar** — photo or initials, sizes `small` / `medium` / `large`. Status `dot` and `count`
66
+ - **TaoTag** — badge with tones. `#prefix` (avatar), `closable` dismisses the chip, `flat` drops the border
67
+ - **TaoAlert** — inline page/form banner (success / error / warning / info)
68
+ - **TaoProgress** — static bar (`progress` in %). Percent centered on top, or `showPercentage="right"`; `#left` / `#right` slots are captions, then the percent sits in parentheses on the right. `false` hides it. Width is `minWidth` / `maxWidth`, not the value domain
69
+ - **TaoLoader** — four-dot spinner
70
+ - **TaoImage** — `<img>` wrapper: fade-in and placeholder. `lazy` turns on native `loading="lazy"`
71
+ - **TaoIcon** — icon-font wrapper (see Icons below)
72
+
73
+ ### Navigation
74
+
75
+ - **TaoLink** — `<NuxtLink>` in Nuxt, `<RouterLink>` with vue-router, otherwise a plain `<a>` — no extra setup
76
+ - **TaoBreadcrumb** — crumbs: the last item is the current page
77
+ - **TaoDropdownMenu** — dropdown that flips at the viewport edge
78
+ - **TaoScrollTop** — floating “back to top” with a ripple. Appears on reverse scroll after `boundary`
79
+
80
+ ### Feedback
81
+
82
+ - **toast()** — fluent toasts: `toast().success().message('Saved')`
83
+ - **confirm()** — overlay question: the same fluent API plus `await`. Not a toast — it waits for an answer and does not dismiss itself.
84
+ - **TaoToastViewport** / **TaoConfirmHost** — hosts; if they are missing from the tree, the first call mounts them on `body`
85
+
86
+ ## Conventions
87
+
88
+ Visual tones: `success` | `danger` | `warning` | `info` | `neutral`.
89
+ `error` on Alert, Tag, and `toast()` is an alias of `danger`. Button already uses `danger`.
90
+ Stages describe a process, not a tone: `wait` / `work` / `ok` / `bad` (`danger` is accepted as `bad`).
91
+
92
+ Control sizes: `small` | `medium` | `large`.
93
+ Avatar also accepts short `s` / `m` / `l`. Divider `gap` used to call the large size `big` — that is an alias of `large`.
94
+ Container lives on another axis: `wide` / `slim` / `ultra-slim`.
95
+
96
+ Font size scale (primitives): `xs` 12 … `xl` 18 for controls; `2xl` 24, `3xl` 32, `4xl` 36 for titles and prose.
97
+ There is no heading component — use `h1 { font-size: var(--tao-font-size-3xl) }`.
98
+
99
+ ## Icons
100
+
101
+ `TaoIcon` does not ship its own set — it renders `icon-<name>`
102
+ for an external icon font (IcoMoon / Fontello and the like). Connect a font
103
+ with classes such as `.icon-arrow-up { ... }` in the host app.
104
+
105
+ ## Install
106
+
107
+ ```bash
108
+ npm install @1337exp/taoui
109
+ ```
110
+
111
+ From a local folder:
112
+
113
+ ```bash
114
+ npm install ../path-to-tao-ui
115
+ ```
116
+
117
+ ## Usage
118
+
119
+ ### Nuxt 3
120
+
121
+ Create `plugins/tao-ui.ts`:
122
+
123
+ ```typescript
124
+ import { defineNuxtPlugin } from '#app'
125
+ import TaoUI from '@1337exp/taoui'
126
+
127
+ export default defineNuxtPlugin((nuxtApp) => {
128
+ nuxtApp.vueApp.use(TaoUI)
129
+ })
130
+ ```
131
+
132
+ Import styles in `app.vue` or a layout:
133
+
134
+ ```vue
135
+ <script setup>
136
+ import '@1337exp/taoui/styles.css'
137
+ </script>
138
+ ```
139
+
140
+ ### Vue 3
141
+
142
+ ```javascript
143
+ // main.js
144
+ import { createApp } from 'vue'
145
+ import App from './App.vue'
146
+ import TaoUI from '@1337exp/taoui'
147
+ import '@1337exp/taoui/styles.css'
148
+
149
+ const app = createApp(App)
150
+ app.use(TaoUI)
151
+ app.mount('#app')
152
+ ```
153
+
154
+ ### Individual components
155
+
156
+ ```vue
157
+ <script setup>
158
+ import { TaoButton, TaoInput, TaoModal } from '@1337exp/taoui'
159
+ import '@1337exp/taoui/styles.css'
160
+ </script>
161
+
162
+ <template>
163
+ <TaoButton variant="primary">Press me</TaoButton>
164
+ <TaoInput v-model="text" label="Your text" />
165
+ <TaoModal v-model="isOpen" title="Title">
166
+ Modal body
167
+ </TaoModal>
168
+ </template>
169
+ ```
170
+
171
+ ## Split
172
+
173
+ `TaoSplit` is two panes and a gutter. It is not `TaoDivider` (a line) and not an n-pane layout: three columns are two nested splits.
174
+
175
+ `direction` matches Flex: `row` (start | end) or `column` (start above end). Slots are `#start` / `#end`.
176
+
177
+ `v-model` is the start pane size. `unit` is `%` (default — the ratio survives a container resize) or `px` (a sidebar; the end pane takes the rest). With `px`, pass an explicit size — the default `50` is 50px. `min` / `max` apply to start; `min-end` is the other pane's floor; all three use the same unit. `resizable="false"` keeps the gutter but locks it.
178
+
179
+ The split fills the parent (`width` / `height: 100%`). Give the parent a bounded size on the split axis — a `column` split without a height has no `%` to compute. Panes use `min-width` / `min-height: 0` and `overflow: hidden`, so content cannot block shrinking. Scroll belongs on an inner wrapper (`overflow: auto; height: 100%; min-height: 0`). A nested `TaoSplit` as a direct child of a pane stretches on its own.
180
+
181
+ ```vue
182
+ <TaoSplit v-model="side" unit="px" :min="160" :max="480" :min-end="240">
183
+ <template #start>Nav</template>
184
+ <template #end>Main</template>
185
+ </TaoSplit>
186
+ ```
187
+
188
+ ## Toast
189
+
190
+ The chain runs in the same tick; `setTimeout(0)` actually shows the toast.
191
+
192
+ ```javascript
193
+ import { toast } from '@1337exp/taoui'
194
+
195
+ toast().success().message('Saved')
196
+ toast().error().title('Network').timeout(5000).message('No connection')
197
+ toast.success('Saved')
198
+ toast.warning('Draft', { timeout: 1000 })
199
+ ```
200
+
201
+ Default position is `topCenter`. Globally:
202
+
203
+ ```javascript
204
+ toast.defaults({ position: 'bottomRight' })
205
+ toast.byBottomRight()
206
+ ```
207
+
208
+ There is no overlay `question` in this kit — that is confirm, not toast. Buttons on a toast, if needed:
209
+
210
+ ```javascript
211
+ toast().info().message('Delete the file?').action('Delete', onRemove)
212
+ ```
213
+
214
+ You can skip the viewport: the first call creates it. For explicit control (tests, a custom layout):
215
+
216
+ ```vue
217
+ <TaoToastViewport />
218
+ ```
219
+
220
+ ## Confirm
221
+
222
+ An overlay question in the center of the screen. This is not a toast: no progress bar, no auto-close, `await`, Esc and overlay click = cancel.
223
+
224
+ ```javascript
225
+ import { confirm } from '@1337exp/taoui'
226
+
227
+ if (await confirm().title('Delete the file?').message('This cannot be undone').danger()) {
228
+ remove()
229
+ }
230
+
231
+ const ok = await confirm('Leave without saving?', {
232
+ ok: 'Leave',
233
+ cancel: 'Stay',
234
+ })
235
+ ```
236
+
237
+ Queue: a second `confirm()` while the first is open will wait. Default labels are “OK” / “Cancel”:
238
+
239
+ ```javascript
240
+ confirm.defaults({ ok: 'Yes', cancel: 'No' })
241
+ ```
242
+
243
+ ## Forms
244
+
245
+ `TaoFormField` attaches a shared label / hint / error and forwards `id` to the nested control.
246
+
247
+ ```vue
248
+ <TaoFormField label="City" hint="Optional">
249
+ <TaoSelect v-model="city" :options="cities" />
250
+ </TaoFormField>
251
+
252
+ <TaoSwitch v-model="dark" label="Dark theme" />
253
+
254
+ <TaoRadioGroup v-model="plan" legend="Plan">
255
+ <TaoRadio value="free" label="Free" />
256
+ <TaoRadio value="pro" label="Pro" />
257
+ </TaoRadioGroup>
258
+
259
+ <TaoSegmented v-model="period" legend="Period">
260
+ <TaoSegmentedOption value="week">Week</TaoSegmentedOption>
261
+ <TaoSegmentedOption value="month">Month</TaoSegmentedOption>
262
+ <TaoSegmentedOption value="year">Year</TaoSegmentedOption>
263
+ </TaoSegmented>
264
+
265
+ <TaoAlert type="warning" title="Draft">Save before you leave.</TaoAlert>
266
+ ```
267
+
268
+ `TaoSegmented` is a short exclusive choice with every option visible. It is not page navigation — that is `TaoTabs`. Longer labels or more than 5–6 items belong in `TaoRadioGroup` or `TaoSelect`.
269
+
270
+ `TaoSelect` opens from the keyboard (arrows, Enter, Esc); the list flips at the viewport edge. Delete or “Clear” at the bottom of the list resets the value to `null`.
271
+
272
+ `TaoCombobox` is the same menu with a field. Typing filters options. `allow-create` writes the typed string into `v-model` when there is no exact match. The list does not mutate itself: listen to `create` and append an option from the parent.
273
+
274
+ `TaoFormField` also wraps `TaoTextarea`, `TaoCheckbox`, `TaoSwitch`, `TaoRadioGroup`, and `TaoSegmented` — id, hint, and error are forwarded for you.
275
+
276
+ `TaoTag` can be a chip: `#prefix` on the left (usually `TaoAvatar`), `closable` draws a cross and emits `close`. The close click does not bubble. `flat` drops the border and keeps the fill.
277
+
278
+ ```vue
279
+ <TaoTag type="neutral" closable @close="remove">
280
+ <template #prefix>
281
+ <TaoAvatar name="Anna Kozlova" size="small" />
282
+ </template>
283
+ Anna Kozlova
284
+ </TaoTag>
285
+ ```
286
+
287
+ ## Table and pagination
288
+
289
+ The table renders what you pass in: it does not slice or sort the data. That works for both server-side and client-side paging — the page is cut outside.
290
+
291
+ ```vue
292
+ <TaoTable
293
+ :columns="columns"
294
+ :rows="pageRows"
295
+ v-model:sort="sort"
296
+ empty-text="No records yet"
297
+ >
298
+ <template #cell-status="{ row }">
299
+ <TaoTag :type="row.status === 'active' ? 'success' : 'neutral'">
300
+ {{ row.status }}
301
+ </TaoTag>
302
+ </template>
303
+ </TaoTable>
304
+
305
+ <TaoPagination v-model:page="page" :total="rows.length" :page-size="8" />
306
+ ```
307
+
308
+ ```javascript
309
+ const columns = [
310
+ { key: 'name', label: 'Name', sortable: true },
311
+ { key: 'city', label: 'City' },
312
+ { key: 'status', label: 'Status' },
313
+ ]
314
+ ```
315
+
316
+ The `cell-<key>` slot replaces a cell. `loading` shows an overlay; an empty list shows text or the `#empty` slot. Clicking a `sortable` header cycles `asc → desc → off`.
317
+
318
+ ## Empty states and loading
319
+
320
+ ```vue
321
+ <TaoEmpty title="Nothing found">
322
+ Change the filter or reset search.
323
+ <template #action>
324
+ <TaoButton variant="secondary">Reset</TaoButton>
325
+ </template>
326
+ </TaoEmpty>
327
+
328
+ <TaoSkeleton variant="title" />
329
+ <TaoSkeleton :lines="3" />
330
+ <TaoSkeleton variant="circle" />
331
+
332
+ <TaoStages :items="[
333
+ { key: 'pay', label: 'Payment', status: 'ok' },
334
+ { key: 'pack', label: 'Packing', status: 'work' },
335
+ { key: 'ship', label: 'Shipping', status: 'wait' },
336
+ ]" />
337
+
338
+ <TaoList>
339
+ <TaoListItem
340
+ title="Ann Kozlova"
341
+ description="Design · Moscow"
342
+ clickable
343
+ :selected="picked === 'ann'"
344
+ @click="picked = 'ann'"
345
+ >
346
+ <template #prefix>
347
+ <TaoAvatar name="Ann Kozlova" size="small" />
348
+ </template>
349
+ </TaoListItem>
350
+ </TaoList>
351
+ ```
352
+
353
+ ```vue
354
+ <TaoInput v-model="q" placeholder="Search…">
355
+ <template #prefix>⌕</template>
356
+ </TaoInput>
357
+
358
+ <TaoInputGroup>
359
+ <template #before>https://</template>
360
+ <TaoInput v-model="host" placeholder="example.com" />
361
+ <template #after>
362
+ <TaoButton>Check</TaoButton>
363
+ </template>
364
+ </TaoInputGroup>
365
+
366
+ <form @submit.prevent="onSave">
367
+ <TaoFormField label="Email">
368
+ <TaoInput v-model="email" type="email" />
369
+ </TaoFormField>
370
+ <TaoButton type="submit">Save</TaoButton>
371
+ </form>
372
+
373
+ <TaoFormField label="Quantity">
374
+ <TaoInputNumber v-model="qty" :min="1" :max="99" />
375
+ </TaoFormField>
376
+
377
+ <TaoQuantity v-model="qty" :max="12" @dec="onDec" />
378
+
379
+ <TaoFormField label="Delivery day" hint="The model is always YYYY-MM-DD">
380
+ <TaoDate v-model="day" min="2026-09-01" max="2026-09-30" />
381
+ </TaoFormField>
382
+
383
+ <TaoFormField label="Stay">
384
+ <TaoDateRange v-model="stay" min="2026-09-01" max="2026-09-30" />
385
+ </TaoFormField>
386
+
387
+ <TaoCounter :value="score" :max-digits="6" />
388
+
389
+ <TaoCarousel :autoplay="4000" loop dots>
390
+ <article v-for="item in banners" :key="item.id" class="hero">
391
+ <strong>{{ item.title }}</strong>
392
+ <span>{{ item.text }}</span>
393
+ </article>
394
+ </TaoCarousel>
395
+ <TaoCarousel :peek="72" :controls="false" dots>…</TaoCarousel>
396
+ <TaoCarousel v-model="slide" :per-view="5" :peek="28">
397
+ <template #prev></template>
398
+ <template #next="{ go, disabled }">
399
+ <button type="button" :disabled="disabled" @click="go">next</button>
400
+ </template>
401
+ <article v-for="item in products" :key="item.id">…</article>
402
+ </TaoCarousel>
403
+ ```
404
+
405
+ `TaoDrawer` is a side sheet, not a centered modal:
406
+
407
+ ```vue
408
+ <TaoDrawer v-model="open" title="Filters">
409
+ ...
410
+ </TaoDrawer>
411
+ ```
412
+
413
+ ## Design tokens and theming
414
+
415
+ Library styles are three layers. Components **never** touch raw colors or sizes —
416
+ only semantic variables. That is the theming mechanism: override 20–30 variables
417
+ and every component follows, with no edits inside the components themselves.
418
+
419
+ ```
420
+ primitives.css → semantic.css → themes/*.css (optional)
421
+ raw colors public theming ready-made themes on top
422
+ and scales, API: this is of semantic — light is
423
+ leave them alone what you change the example
424
+ ```
425
+
426
+ ### Layers
427
+
428
+ | File | Role | Touch it? |
429
+ |---|---|---|
430
+ | `primitives.css` | Raw palette, space / radius / shadow scales (`--tao-orange-400`, `--tao-space-3`…) | Usually no |
431
+ | `semantic.css` | Default (dark) theme tokens: `--tao-color-accent`, `--tao-color-surface`, `--tao-color-danger`, etc. | **Yes — this is the theming API** |
432
+ | `themes/light.css` | Alternate theme via `[data-tao-theme="light"]` | Yes, as a sample |
433
+ | `base.css` | Global reset (scrollbar, links, box-sizing) | Rarely |
434
+
435
+ All four come in through `@1337exp/taoui/styles.css`.
436
+
437
+ ### Your own theme
438
+
439
+ A theme is just semantic tokens under your selector.
440
+ Nothing in the components needs to change:
441
+
442
+ ```css
443
+ /* my-brand-theme.css */
444
+ [data-tao-theme="my-brand"] {
445
+ --tao-color-accent: #6c5ce7;
446
+ --tao-color-accent-hover: #5a4bd1;
447
+ --tao-color-bg: #0f0f1a;
448
+ --tao-color-surface: #0f0f1a;
449
+ --tao-color-surface-raised: #1a1a2e;
450
+ --tao-color-text: #d6d6e8;
451
+ --tao-color-text-strong: #ffffff;
452
+ /* remaining tokens: see semantic.css */
453
+ }
454
+ ```
455
+
456
+ ```vue
457
+ <!-- app.vue -->
458
+ <template>
459
+ <div data-tao-theme="my-brand">
460
+ <NuxtPage />
461
+ </div>
462
+ </template>
463
+ ```
464
+
465
+ Or switch the theme on `<html>` so modals/tooltips teleported to `body` pick it up:
466
+
467
+ ```javascript
468
+ document.documentElement.setAttribute('data-tao-theme', 'light')
469
+ ```
470
+
471
+ ### Token groups
472
+
473
+ - `--tao-color-accent*` — brand/accent (primary buttons, active tabs, links)
474
+ - `--tao-color-on-accent` — text on the accent (`--tao-color-text-on-accent` is an alias)
475
+ - `--tao-color-selection`, `--tao-color-selection-text` — text selection (`::selection`)
476
+ - `--tao-color-surface*` — container backgrounds (default / raised / sunken / hover)
477
+ - `--tao-color-border*` — borders
478
+ - `--tao-color-text*` — text (default / strong / subtle / muted / disabled)
479
+ - `--tao-color-danger*`, `--tao-color-success*`, `--tao-color-warning*`, `--tao-color-info*` — status colors
480
+ - `--tao-color-input-*` — fields, because input bg/text often differs from surface/text
481
+ - `--tao-radius-control`, `--tao-radius-panel` — radii (small controls vs large panels)
482
+ - `--tao-shadow-panel`, `--tao-shadow-overlay` — shadows
483
+ - `--tao-font-size-*` — type scale in `primitives.css`: `xs`–`xl` controls, `2xl`–`4xl` titles / prose
484
+
485
+ The full list lives in `src/styles/semantic.css` — the only file worth opening
486
+ to see the whole theming API.
487
+
488
+ ### A theme from scratch, without primitives
489
+
490
+ ```javascript
491
+ import '@1337exp/taoui/styles/primitives.css' // keep the scales if you want
492
+ import './my-semantic.css' // your semantic layer instead of @1337exp/taoui/styles/semantic.css
493
+ import '@1337exp/taoui/styles/base.css'
494
+ ```
495
+
496
+ ## Showcase
497
+
498
+ To browse every component:
499
+
500
+ ```bash
501
+ cd showcase
502
+ npm install
503
+ npm run dev
504
+ ```
505
+
506
+ The showcase has a dark / light switch in the header, so you can see token
507
+ overrides without editing component code.
508
+
509
+ The showcase points at `src/` directly, so component edits show up without
510
+ `npm run build`. Building `dist/` is for publishing and for other projects
511
+ that install the package.
512
+
513
+ ## Development
514
+
515
+ ```bash
516
+ # Build the library
517
+ npm run build
518
+
519
+ # Watch mode
520
+ npm run dev
521
+
522
+ # Type check
523
+ npm run type-check
524
+ ```
525
+
526
+ ## Publishing to npm
527
+
528
+ Only `dist/` (the built library) and `src/styles/` (raw tokens, so you can
529
+ import `primitives.css` on its own for a custom theme) are published.
530
+ Component sources (`src/components/*.vue`), build configs, and `showcase/`
531
+ stay in the repo — clone it to get them.
532
+
533
+ `npm publish` **itself** runs the type check and a full rebuild first
534
+ (`prepublishOnly` in `package.json`), so a stale or missing `dist/` cannot
535
+ reach the registry. You do not need to build by hand before `npm publish`.
536
+
537
+ See what would go into the tarball, without publishing:
538
+
539
+ ```bash
540
+ npm pack --dry-run
541
+ ```
542
+
543
+ ## License
544
+
545
+ MIT