@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 +545 -545
- package/dist/index.cjs +77 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +78 -67
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +3240 -3240
- package/package.json +61 -61
- package/src/styles/base.css +81 -81
- package/src/styles/date-popup.css +184 -184
- package/src/styles/index.css +9 -9
- package/src/styles/primitives.css +84 -84
- package/src/styles/semantic.css +82 -82
- package/src/styles/themes/light.css +61 -61
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
|