iryx-ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +180 -20
  2. package/dist/component-names.d.ts +1 -1
  3. package/dist/components/Alert.vue.d.ts +3 -3
  4. package/dist/components/Breadcrumb.vue.d.ts +45 -0
  5. package/dist/components/Button.vue.d.ts +8 -0
  6. package/dist/components/ButtonGroup.vue.d.ts +32 -0
  7. package/dist/components/ConfirmDialog.vue.d.ts +18 -0
  8. package/dist/components/Dialog.vue.d.ts +65 -0
  9. package/dist/components/DropdownMenu.vue.d.ts +49 -0
  10. package/dist/components/DropdownMenuItems.vue.d.ts +24 -0
  11. package/dist/components/EmptyState.vue.d.ts +46 -0
  12. package/dist/components/FormField.vue.d.ts +9 -0
  13. package/dist/components/Icon.vue.d.ts +8 -0
  14. package/dist/components/Label.vue.d.ts +6 -0
  15. package/dist/components/Pagination.vue.d.ts +57 -0
  16. package/dist/components/Progress.vue.d.ts +52 -0
  17. package/dist/components/Separator.vue.d.ts +36 -0
  18. package/dist/components/Skeleton.vue.d.ts +20 -0
  19. package/dist/components/Stat.vue.d.ts +57 -0
  20. package/dist/components/Stepper.vue.d.ts +61 -0
  21. package/dist/components/Tabs.vue.d.ts +63 -0
  22. package/dist/components/Toaster.vue.d.ts +28 -0
  23. package/dist/components/Tooltip.vue.d.ts +49 -0
  24. package/dist/components/index.d.ts +15 -0
  25. package/dist/composables/button-group.d.ts +12 -0
  26. package/dist/composables/confirm.d.ts +36 -0
  27. package/dist/composables/dropdown-menu.d.ts +22 -0
  28. package/dist/composables/icon.d.ts +10 -0
  29. package/dist/composables/toast.d.ts +42 -0
  30. package/dist/config.d.ts +9 -0
  31. package/dist/index.d.ts +19 -0
  32. package/dist/index.js +64 -31
  33. package/dist/node_modules/.pnpm/@hugeicons_core-free-icons@4.2.3/node_modules/@hugeicons/core-free-icons/dist/esm/index.js +201 -0
  34. package/dist/node_modules/.pnpm/@hugeicons_vue@1.0.7_vue@3.5.35_typescript@6.0.3_/node_modules/@hugeicons/vue/dist/esm/_virtual/_plugin-vue_export-helper.js +8 -0
  35. package/dist/node_modules/.pnpm/@hugeicons_vue@1.0.7_vue@3.5.35_typescript@6.0.3_/node_modules/@hugeicons/vue/dist/esm/components/HugeiconsIcon.vue.js +25 -0
  36. package/dist/node_modules/.pnpm/@hugeicons_vue@1.0.7_vue@3.5.35_typescript@6.0.3_/node_modules/@hugeicons/vue/dist/esm/components/HugeiconsIcon.vue2.js +63 -0
  37. package/dist/nuxt.js +1 -1
  38. package/dist/packages/iryx-ui/src/component-names.js +4 -0
  39. package/dist/packages/iryx-ui/src/components/Alert.vue_vue_type_script_setup_true_lang.js +75 -0
  40. package/dist/packages/iryx-ui/src/components/Breadcrumb.js +5 -0
  41. package/dist/packages/iryx-ui/src/components/Breadcrumb.vue_vue_type_script_setup_true_lang.js +68 -0
  42. package/dist/packages/iryx-ui/src/components/Button.vue_vue_type_script_setup_true_lang.js +59 -0
  43. package/dist/packages/iryx-ui/src/components/ButtonGroup.js +5 -0
  44. package/dist/packages/iryx-ui/src/components/ButtonGroup.vue_vue_type_script_setup_true_lang.js +39 -0
  45. package/dist/packages/iryx-ui/src/components/Checkbox.vue_vue_type_script_setup_true_lang.js +92 -0
  46. package/dist/packages/iryx-ui/src/components/ConfirmDialog.js +5 -0
  47. package/dist/packages/iryx-ui/src/components/ConfirmDialog.vue_vue_type_script_setup_true_lang.js +63 -0
  48. package/dist/packages/iryx-ui/src/components/Dialog.js +5 -0
  49. package/dist/packages/iryx-ui/src/components/Dialog.vue_vue_type_script_setup_true_lang.js +99 -0
  50. package/dist/packages/iryx-ui/src/components/DropdownMenu.js +5 -0
  51. package/dist/packages/iryx-ui/src/components/DropdownMenu.vue_vue_type_script_setup_true_lang.js +67 -0
  52. package/dist/packages/iryx-ui/src/components/DropdownMenuItems.js +5 -0
  53. package/dist/packages/iryx-ui/src/components/DropdownMenuItems.vue_vue_type_script_setup_true_lang.js +92 -0
  54. package/dist/packages/iryx-ui/src/components/EmptyState.js +5 -0
  55. package/dist/packages/iryx-ui/src/components/EmptyState.vue_vue_type_script_setup_true_lang.js +55 -0
  56. package/dist/{components → packages/iryx-ui/src/components}/FormField.vue_vue_type_script_setup_true_lang.js +5 -3
  57. package/dist/packages/iryx-ui/src/components/Icon.js +5 -0
  58. package/dist/packages/iryx-ui/src/components/Icon.vue_vue_type_script_setup_true_lang.js +26 -0
  59. package/dist/{components → packages/iryx-ui/src/components}/Label.vue_vue_type_script_setup_true_lang.js +2 -0
  60. package/dist/packages/iryx-ui/src/components/Pagination.js +5 -0
  61. package/dist/packages/iryx-ui/src/components/Pagination.vue_vue_type_script_setup_true_lang.js +94 -0
  62. package/dist/packages/iryx-ui/src/components/Progress.js +5 -0
  63. package/dist/packages/iryx-ui/src/components/Progress.vue_vue_type_script_setup_true_lang.js +65 -0
  64. package/dist/packages/iryx-ui/src/components/Select.vue_vue_type_script_setup_true_lang.js +85 -0
  65. package/dist/packages/iryx-ui/src/components/Separator.js +5 -0
  66. package/dist/packages/iryx-ui/src/components/Separator.vue_vue_type_script_setup_true_lang.js +56 -0
  67. package/dist/packages/iryx-ui/src/components/Skeleton.js +5 -0
  68. package/dist/packages/iryx-ui/src/components/Skeleton.vue_vue_type_script_setup_true_lang.js +48 -0
  69. package/dist/packages/iryx-ui/src/components/Stat.js +5 -0
  70. package/dist/packages/iryx-ui/src/components/Stat.vue_vue_type_script_setup_true_lang.js +63 -0
  71. package/dist/packages/iryx-ui/src/components/Stepper.js +5 -0
  72. package/dist/packages/iryx-ui/src/components/Stepper.vue_vue_type_script_setup_true_lang.js +89 -0
  73. package/dist/packages/iryx-ui/src/components/Tabs.js +5 -0
  74. package/dist/packages/iryx-ui/src/components/Tabs.vue_vue_type_script_setup_true_lang.js +78 -0
  75. package/dist/packages/iryx-ui/src/components/Toaster.js +5 -0
  76. package/dist/packages/iryx-ui/src/components/Toaster.vue_vue_type_script_setup_true_lang.js +115 -0
  77. package/dist/packages/iryx-ui/src/components/Tooltip.js +5 -0
  78. package/dist/packages/iryx-ui/src/components/Tooltip.vue_vue_type_script_setup_true_lang.js +70 -0
  79. package/dist/packages/iryx-ui/src/components/index.js +64 -0
  80. package/dist/{composables → packages/iryx-ui/src/composables}/appearance.js +11 -7
  81. package/dist/packages/iryx-ui/src/composables/button-group.js +8 -0
  82. package/dist/packages/iryx-ui/src/composables/confirm.js +25 -0
  83. package/dist/packages/iryx-ui/src/composables/dropdown-menu.js +9 -0
  84. package/dist/packages/iryx-ui/src/composables/icon.js +6 -0
  85. package/dist/packages/iryx-ui/src/composables/toast.js +38 -0
  86. package/dist/{theme → packages/iryx-ui/src/theme}/badge.js +3 -3
  87. package/dist/packages/iryx-ui/src/theme/breadcrumb.js +12 -0
  88. package/dist/packages/iryx-ui/src/theme/button-group.js +15 -0
  89. package/dist/packages/iryx-ui/src/theme/button.js +59 -0
  90. package/dist/packages/iryx-ui/src/theme/dialog.js +23 -0
  91. package/dist/packages/iryx-ui/src/theme/dropdown-menu.js +16 -0
  92. package/dist/packages/iryx-ui/src/theme/empty-state.js +34 -0
  93. package/dist/packages/iryx-ui/src/theme/form.js +38 -0
  94. package/dist/{theme → packages/iryx-ui/src/theme}/label.js +10 -1
  95. package/dist/packages/iryx-ui/src/theme/pagination.js +29 -0
  96. package/dist/packages/iryx-ui/src/theme/progress.js +33 -0
  97. package/dist/packages/iryx-ui/src/theme/separator.js +22 -0
  98. package/dist/packages/iryx-ui/src/theme/skeleton.js +13 -0
  99. package/dist/packages/iryx-ui/src/theme/stat.js +30 -0
  100. package/dist/packages/iryx-ui/src/theme/stepper.js +25 -0
  101. package/dist/packages/iryx-ui/src/theme/tabs.js +52 -0
  102. package/dist/packages/iryx-ui/src/theme/toast.js +53 -0
  103. package/dist/packages/iryx-ui/src/theme/tooltip.js +8 -0
  104. package/dist/theme/breadcrumb.d.ts +59 -0
  105. package/dist/theme/button-group.d.ts +29 -0
  106. package/dist/theme/button.d.ts +17 -2
  107. package/dist/theme/dialog.d.ts +74 -0
  108. package/dist/theme/dropdown-menu.d.ts +53 -0
  109. package/dist/theme/empty-state.d.ts +86 -0
  110. package/dist/theme/form.d.ts +62 -35
  111. package/dist/theme/index.d.ts +14 -0
  112. package/dist/theme/label.d.ts +18 -0
  113. package/dist/theme/pagination.d.ts +71 -0
  114. package/dist/theme/progress.d.ts +143 -0
  115. package/dist/theme/separator.d.ts +50 -0
  116. package/dist/theme/skeleton.d.ts +23 -0
  117. package/dist/theme/stat.d.ts +110 -0
  118. package/dist/theme/stepper.d.ts +56 -0
  119. package/dist/theme/tabs.d.ts +104 -0
  120. package/dist/theme/toast.d.ts +176 -0
  121. package/dist/theme/tooltip.d.ts +32 -0
  122. package/package.json +4 -3
  123. package/theme.css +55 -0
  124. package/dist/component-names.js +0 -19
  125. package/dist/components/Alert.vue_vue_type_script_setup_true_lang.js +0 -74
  126. package/dist/components/Button.vue_vue_type_script_setup_true_lang.js +0 -54
  127. package/dist/components/Checkbox.vue_vue_type_script_setup_true_lang.js +0 -79
  128. package/dist/components/Select.vue_vue_type_script_setup_true_lang.js +0 -84
  129. package/dist/components/index.js +0 -34
  130. package/dist/theme/button.js +0 -27
  131. package/dist/theme/form.js +0 -14
  132. /package/dist/{components → packages/iryx-ui/src/components}/Alert.js +0 -0
  133. /package/dist/{components → packages/iryx-ui/src/components}/App.js +0 -0
  134. /package/dist/{components → packages/iryx-ui/src/components}/App.vue_vue_type_script_setup_true_lang.js +0 -0
  135. /package/dist/{components → packages/iryx-ui/src/components}/Badge.js +0 -0
  136. /package/dist/{components → packages/iryx-ui/src/components}/Badge.vue_vue_type_script_setup_true_lang.js +0 -0
  137. /package/dist/{components → packages/iryx-ui/src/components}/Button.js +0 -0
  138. /package/dist/{components → packages/iryx-ui/src/components}/Card.js +0 -0
  139. /package/dist/{components → packages/iryx-ui/src/components}/Card.vue_vue_type_script_setup_true_lang.js +0 -0
  140. /package/dist/{components → packages/iryx-ui/src/components}/Checkbox.js +0 -0
  141. /package/dist/{components → packages/iryx-ui/src/components}/Form.js +0 -0
  142. /package/dist/{components → packages/iryx-ui/src/components}/Form.vue_vue_type_script_setup_true_lang.js +0 -0
  143. /package/dist/{components → packages/iryx-ui/src/components}/FormField.js +0 -0
  144. /package/dist/{components → packages/iryx-ui/src/components}/Input.js +0 -0
  145. /package/dist/{components → packages/iryx-ui/src/components}/Input.vue_vue_type_script_setup_true_lang.js +0 -0
  146. /package/dist/{components → packages/iryx-ui/src/components}/Label.js +0 -0
  147. /package/dist/{components → packages/iryx-ui/src/components}/RadioGroup.js +0 -0
  148. /package/dist/{components → packages/iryx-ui/src/components}/RadioGroup.vue_vue_type_script_setup_true_lang.js +0 -0
  149. /package/dist/{components → packages/iryx-ui/src/components}/Select.js +0 -0
  150. /package/dist/{components → packages/iryx-ui/src/components}/Switch.js +0 -0
  151. /package/dist/{components → packages/iryx-ui/src/components}/Switch.vue_vue_type_script_setup_true_lang.js +0 -0
  152. /package/dist/{components → packages/iryx-ui/src/components}/Textarea.js +0 -0
  153. /package/dist/{components → packages/iryx-ui/src/components}/Textarea.vue_vue_type_script_setup_true_lang.js +0 -0
  154. /package/dist/{composables → packages/iryx-ui/src/composables}/form.js +0 -0
  155. /package/dist/{config.js → packages/iryx-ui/src/config.js} +0 -0
  156. /package/dist/{plugin.js → packages/iryx-ui/src/plugin.js} +0 -0
  157. /package/dist/{theme → packages/iryx-ui/src/theme}/alert.js +0 -0
  158. /package/dist/{theme → packages/iryx-ui/src/theme}/card.js +0 -0
  159. /package/dist/{theme → packages/iryx-ui/src/theme}/checkbox.js +0 -0
  160. /package/dist/{theme → packages/iryx-ui/src/theme}/input.js +0 -0
  161. /package/dist/{theme → packages/iryx-ui/src/theme}/presets.js +0 -0
  162. /package/dist/{theme → packages/iryx-ui/src/theme}/radio-group.js +0 -0
  163. /package/dist/{theme → packages/iryx-ui/src/theme}/select.js +0 -0
  164. /package/dist/{theme → packages/iryx-ui/src/theme}/switch.js +0 -0
package/README.md CHANGED
@@ -1,18 +1,10 @@
1
1
  # iryx-ui
2
2
 
3
- > An artful Vue 3 component library built on [Reka UI](https://reka-ui.com) and [Tailwind CSS v4](https://tailwindcss.com).
3
+ A Vue 3 component library built on [Reka UI](https://reka-ui.com) and [Tailwind CSS v4](https://tailwindcss.com). Styled by default, headless when you need it, with a Vue plugin and a Nuxt module in one package.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/iryx-ui.svg)](https://www.npmjs.com/package/iryx-ui)
6
6
  [![license](https://img.shields.io/npm/l/iryx-ui.svg)](https://github.com/therok1/iryx-ui/blob/main/LICENSE)
7
7
 
8
- - 🎨 **Styled by default** — sensible Tailwind v4 themes via [tailwind-variants](https://www.tailwind-variants.org)
9
- - 🌗 **Light & dark out of the box** — `useAppearance()` with `light` / `dark` / `system`, persisted
10
- - 🌈 **Swappable themes** — built-in color presets or your own, switchable at runtime with `applyTheme()`
11
- - 🪶 **Headless when you want** — `unstyled` per component or globally, leaving bare Reka UI primitives
12
- - 🧩 **Composable theming** — override any slot with the `ui` prop or re-brand with CSS theme tokens
13
- - ⚡ **Vue 3 + Nuxt** — a Vue plugin and a Nuxt module with auto-imports, from one package
14
- - 🌳 **Tree-shakeable, ESM-only, fully typed**
15
-
16
8
  ## Installation
17
9
 
18
10
  ```bash
@@ -66,24 +58,43 @@ Components are auto-imported with the `I` prefix (configurable via the `iryxUi.p
66
58
 
67
59
  ### Icons
68
60
 
69
- Just drop an icon component into the button alongside your text — leading, trailing, or both. Icons are sized automatically to match the button, and spaced for you. Works with any SVG icon set; [lucide-vue-next](https://lucide.dev/guide/packages/lucide-vue-next) pairs nicely:
61
+ Put an icon in the default slot, leading or trailing. Icons are sized to match the button, and any SVG icon set works. Iryx's own icons come from [Hugeicons](https://hugeicons.com), which ships icons as data rather than components:
70
62
 
71
63
  ```vue
72
64
  <script setup lang="ts">
73
- import { ArrowRight, Search } from 'lucide-vue-next'
65
+ import { ArrowRight01Icon, Search01Icon } from '@hugeicons/core-free-icons'
66
+ import { HugeiconsIcon } from '@hugeicons/vue'
74
67
  </script>
75
68
 
76
69
  <template>
77
70
  <IButton>
78
- <Search /> Search
71
+ <HugeiconsIcon :icon="Search01Icon" data-icon="inline-start" /> Search
79
72
  </IButton>
80
73
  <IButton variant="outline">
81
- Next <ArrowRight />
74
+ Next <HugeiconsIcon :icon="ArrowRight01Icon" data-icon="inline-end" />
75
+ </IButton>
76
+ <IButton square aria-label="Search">
77
+ <HugeiconsIcon :icon="Search01Icon" />
82
78
  </IButton>
83
79
  </template>
84
80
  ```
85
81
 
86
- When `loading` is set, a spinner appears in the leading position.
82
+ Mark the icon with `data-icon="inline-start"` or `"inline-end"` and the padding tightens on the side it sits on. For an icon with no label, add `square`.
83
+
84
+ Props that take an icon — `IAlert`'s `icon`, and the `icon` on `IDropdownMenu`, `IBreadcrumb`, `ITabs` and `IEmptyState` items — accept **either** a Hugeicons icon or any component that renders an SVG, so an icon set like Lucide still works:
85
+
86
+ ```ts
87
+ import { Alert02Icon } from '@hugeicons/core-free-icons'
88
+ import { Bell } from 'lucide-vue-next'
89
+
90
+ // Both are valid.
91
+ const a = { label: 'Alerts', icon: Alert02Icon }
92
+ const b = { label: 'Alerts', icon: Bell }
93
+ ```
94
+
95
+ The marker is needed because a label is a bare text node: CSS's `:first-child` and `:last-child` count element children, so an icon beside text matches both. An unmarked icon still renders, it just keeps the full padding.
96
+
97
+ `IBadge` follows the same convention. When `loading` is set on a button, a spinner takes the leading position automatically.
87
98
 
88
99
  ## The `IApp` wrapper
89
100
 
@@ -186,9 +197,30 @@ For a static re-brand, plain CSS works too — tokens are just variables:
186
197
  }
187
198
  ```
188
199
 
189
- Available tokens: `background`, `foreground`, `primary`, `primary-foreground`,
190
- `accent`, `accent-foreground`, `muted`, `muted-foreground`, `border` — each
191
- usable as a Tailwind color (`bg-primary`, `text-muted-foreground`, …).
200
+ Available tokens, each usable as a Tailwind color (`bg-primary`,
201
+ `text-muted-foreground`, …):
202
+
203
+ | Group | Tokens |
204
+ | --- | --- |
205
+ | Surfaces | `background`, `foreground`, `accent`, `accent-foreground`, `muted`, `muted-foreground`, `border` |
206
+ | Brand | `primary`, `primary-foreground`, `primary-from`, `primary-to` |
207
+ | Status | `success`, `warning`, `danger`, `info` — each with `-foreground`, `-muted`, `-muted-foreground` and `-border` |
208
+
209
+ `primary-from` / `primary-to` are the stops of the solid button's vertical
210
+ gradient. The status tokens carry their own dark-mode values, so components
211
+ never need a `dark:` class for them: `success` is the solid fill,
212
+ `success-foreground` the text on it, `success-muted` a tinted surface,
213
+ `success-muted-foreground` the text on that, and `success-border` the edge.
214
+
215
+ The built-in presets swap only the brand colors — red should mean danger in
216
+ every theme — but `applyTheme()` accepts the status tokens too:
217
+
218
+ ```ts
219
+ applyTheme({
220
+ light: { success: 'oklch(0.6 0.15 150)', successMuted: 'oklch(0.97 0.02 150)' },
221
+ dark: { success: 'oklch(0.7 0.15 150)', successMuted: 'oklch(0.26 0.05 150)' },
222
+ })
223
+ ```
192
224
 
193
225
  Tweak a single instance with `class` (conflicts are merged smartly) or per-slot with `ui`:
194
226
 
@@ -215,21 +247,130 @@ app.use(createIryxUi({ unstyled: true }))
215
247
 
216
248
  ## Components
217
249
 
250
+ **Layout & structure**
251
+
218
252
  | Component | Description |
219
253
  | --- | --- |
220
254
  | `IApp` | Root wrapper — reactive global config, theme, appearance, RTL/locale |
255
+ | `ICard` | Panel with `outline`/`soft` variants, four paddings, header and footer slots |
256
+ | `ISeparator` | Horizontal or vertical rule, optionally with a centred label |
257
+
258
+ **Forms**
259
+
260
+ | Component | Description |
261
+ | --- | --- |
221
262
  | `IForm` | Validating form wrapper — any Standard Schema validator, or your own function |
222
263
  | `IFormField` | Label, description, hint, help and error text around a control |
223
- | `IButton` | Variants (`solid`, `outline`, `ghost`, `link`), five sizes, `loading` and `block` states, polymorphic via `as` / `asChild` |
224
264
  | `IInput` | Text field with `sm`/`md`/`lg` sizes, `invalid` state, `v-model` |
225
265
  | `ITextarea` | Multi-line field with matching sizes and `invalid` state |
226
- | `ILabel` | Field label with optional `required` asterisk |
266
+ | `ILabel` | Field label with optional `required` asterisk, indented to line up with the control's text |
227
267
  | `ICheckbox` | Tri-state checkbox (`true` / `false` / `'indeterminate'`), optional `label` + `description` |
228
268
  | `ISelect` | Listbox with keyboard nav and typeahead, driven by an `items` array |
229
269
  | `IRadioGroup` | Radio list with labels wired up automatically; items take a `description` |
230
270
  | `ISwitch` | Accessible toggle, optional `label` + `description` |
231
271
 
232
- Every component supports `unstyled`, a `class` override, and multi-part ones take a `ui` prop for per-slot classes. More on the way.
272
+ **Actions**
273
+
274
+ | Component | Description |
275
+ | --- | --- |
276
+ | `IButton` | Variants (`solid`, `outline`, `ghost`, `link`), five sizes, `loading`, `block` and `square` states, polymorphic via `as` / `asChild` |
277
+ | `IButtonGroup` | Joins any children into a segmented control — split buttons, toolbars, pagers |
278
+ | `IDropdownMenu` | Menu driven by an `items` array, with separators, group labels, danger items and nested submenus |
279
+
280
+ **Feedback**
281
+
282
+ | Component | Description |
283
+ | --- | --- |
284
+ | `IAlert` | Inline banner in four variants, with a variant icon and optional dismiss |
285
+ | `IBadge` | Status pill — five variants × `soft`/`solid` tones × three sizes, optional `dot` |
286
+ | `IToaster` | Host for `useToast()`; six viewport positions, stacking, action buttons |
287
+ | `IDialog` | Modal with header/body/footer slots, `dismissible` and `showClose` |
288
+ | `IConfirmDialog` | Host for `useConfirm()` — renders the promise-based confirmation |
289
+ | `IProgress` | Determinate or `indeterminate` bar, five variants, `formatValue` |
290
+ | `ISkeleton` | Loading placeholder — `text`/`rect`/`circle`, stackable with `lines` |
291
+ | `IEmptyState` | Icon, title, description and an `actions` slot for empty lists |
292
+ | `ITooltip` | Hover/focus tooltip with side, align, delay and optional arrow |
293
+
294
+ **Navigation & data display**
295
+
296
+ | Component | Description |
297
+ | --- | --- |
298
+ | `ITabs` | `solid` or `line` variants with an animated indicator, horizontal or vertical |
299
+ | `IBreadcrumb` | Trail from an `items` array; the last crumb is marked as the current page |
300
+ | `IPagination` | Page list with ellipsis, edge pages and prev/next controls |
301
+ | `IStepper` | Multi-step progress, horizontal or vertical, optional `linear` ordering |
302
+ | `IStat` | KPI tile — label, value, signed delta with trend colour, and a hint |
303
+
304
+ Every component supports `unstyled` and a `class` override; multi-part ones take a `ui` prop for per-slot classes.
305
+
306
+ ### Button groups
307
+
308
+ `IButtonGroup` joins whatever you put inside it — buttons, a menu trigger, a link — squaring the inner edges and collapsing the shared borders. Set `size` once on the group and the buttons inherit it:
309
+
310
+ ```vue
311
+ <template>
312
+ <IButtonGroup>
313
+ <IButton @click="save">
314
+ Save
315
+ </IButton>
316
+ <IDropdownMenu :items="saveActions" align="end">
317
+ <template #trigger>
318
+ <IButton square aria-label="More options">
319
+ <ChevronDown />
320
+ </IButton>
321
+ </template>
322
+ </IDropdownMenu>
323
+ </IButtonGroup>
324
+ </template>
325
+ ```
326
+
327
+ ### Menus
328
+
329
+ `IDropdownMenu` takes entries as data. A `'-'` is a separator, an entry without `onSelect` is a group label, and one with its own `items` opens a submenu — to any depth:
330
+
331
+ ```ts
332
+ const items = [
333
+ { label: 'Invoice' },
334
+ { label: 'Open', icon: Search, onSelect: () => open() },
335
+ { label: 'Export as', icon: Download, items: [
336
+ { label: 'PDF', onSelect: () => exportPdf() },
337
+ { label: 'CSV', onSelect: () => exportCsv() },
338
+ ] },
339
+ '-',
340
+ { label: 'Delete', icon: Trash2, danger: true, onSelect: () => remove() },
341
+ ]
342
+ ```
343
+
344
+ ### Toasts and confirmations
345
+
346
+ Both are imperative, so they can be called from anywhere — including plain functions outside a component. Mount each host once, typically just inside `<IApp>`:
347
+
348
+ ```vue
349
+ <template>
350
+ <IApp>
351
+ <!-- your app -->
352
+ <IConfirmDialog />
353
+ <IToaster />
354
+ </IApp>
355
+ </template>
356
+ ```
357
+
358
+ ```ts
359
+ const toast = useToast()
360
+ toast.success('Saved')
361
+ toast.danger({ title: 'Failed to send', description: 'Check the address.' })
362
+ toast.toast({ title: 'Note deleted', action: { label: 'Undo', onClick: restore } })
363
+
364
+ const { confirm } = useConfirm()
365
+ if (await confirm({ title: 'Delete this draft?', danger: true }))
366
+ await remove()
367
+ ```
368
+
369
+ `confirm()` resolves `true` on confirmation and `false` on cancel or dismissal.
370
+
371
+ ### Internationalisation
372
+
373
+ No English string is baked in without an escape hatch. `IAlert`, `IDialog` and `IToaster` take a `closeLabel`, `IPagination` takes `prevLabel` / `nextLabel` / `label`, `IBreadcrumb` and `ISkeleton` take a `label`, and `IProgress` and `IStat` take `formatValue` / `formatDelta` for locale-aware numbers.
233
374
 
234
375
  ### Forms
235
376
 
@@ -315,6 +456,25 @@ The control inside a field automatically inherits its `id`, invalid styling and
315
456
 
316
457
  **Server errors and manual control** — grab a template ref to the form and call `validate()`, `clear(name?)` or `setErrors()`. `IFormField` also takes a plain `error` prop that bypasses validation entirely.
317
458
 
459
+ ### Label alignment
460
+
461
+ `ILabel` and `IFormField` indent their text so it lines up with the control's
462
+ text rather than its outer edge, putting the label directly above the
463
+ placeholder. The offset matches the input's horizontal padding — `sm` 10px,
464
+ `md` 12px, `lg` 16px — so pass the same size you gave the control:
465
+
466
+ ```vue
467
+ <template>
468
+ <ILabel for="amount" indent="lg">
469
+ Amount
470
+ </ILabel>
471
+ <IInput id="amount" size="lg" />
472
+ </template>
473
+ ```
474
+
475
+ Use `indent="none"` when the label wraps its control, as with a checkbox, since
476
+ there is no text to line up with.
477
+
318
478
  ### Labels and descriptions
319
479
 
320
480
  `ICheckbox` and `ISwitch` render bare by default. Give them a `label` and/or `description` and they render a wired-up layout instead — the text is clickable, and the description is linked with `aria-describedby`. `IRadioGroup` items take a `description` too.
@@ -3,5 +3,5 @@
3
3
  * Consumed by the Vue plugin (global registration) and the Nuxt module
4
4
  * (auto-imports). Keep in sync with `src/components/index.ts`.
5
5
  */
6
- export declare const componentNames: readonly ["Alert", "App", "Badge", "Button", "Card", "Checkbox", "Form", "FormField", "Input", "Label", "RadioGroup", "Select", "Switch", "Textarea"];
6
+ export declare const componentNames: readonly ["Alert", "App", "Badge", "Breadcrumb", "Button", "ButtonGroup", "Card", "Checkbox", "ConfirmDialog", "Dialog", "DropdownMenu", "EmptyState", "Form", "FormField", "Input", "Label", "Pagination", "Progress", "RadioGroup", "Select", "Separator", "Skeleton", "Stat", "Stepper", "Switch", "Tabs", "Textarea", "Toaster", "Tooltip"];
7
7
  export type ComponentName = (typeof componentNames)[number];
@@ -1,4 +1,4 @@
1
- import type { Component } from 'vue';
1
+ import type { IconLike } from '../composables/icon';
2
2
  export interface AlertProps {
3
3
  /** Render as a different element or component. */
4
4
  as?: string;
@@ -11,7 +11,7 @@ export interface AlertProps {
11
11
  * Leading icon. Defaults to one matching the variant; pass a component to
12
12
  * override it, or `false` to drop it entirely.
13
13
  */
14
- icon?: Component | false;
14
+ icon?: IconLike | false;
15
15
  /** Render a dismiss button that emits `close`. */
16
16
  closable?: boolean;
17
17
  /** Accessible name for the dismiss button — override for non-English apps. */
@@ -44,7 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<AlertProps, {}, {}, {},
44
44
  }, string, import("vue").PublicProps, Readonly<AlertProps> & Readonly<{
45
45
  onClose?: (() => any) | undefined;
46
46
  }>, {
47
- icon: Component | false;
47
+ icon: IconLike | false;
48
48
  as: string;
49
49
  closeLabel: string;
50
50
  unstyled: boolean;
@@ -0,0 +1,45 @@
1
+ import type { IconLike } from '../composables/icon';
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ /** Omit on the current page. */
5
+ href?: string;
6
+ icon?: IconLike;
7
+ /** Handle navigation yourself, e.g. with a router. */
8
+ onSelect?: () => void;
9
+ }
10
+ export interface BreadcrumbProps {
11
+ items?: BreadcrumbItem[];
12
+ /**
13
+ * Accessible name for the navigation landmark — override for non-English
14
+ * apps.
15
+ */
16
+ label?: string;
17
+ /** Skip built-in classes; you take over styling entirely. */
18
+ unstyled?: boolean;
19
+ class?: string;
20
+ /** Override classes per slot, e.g. `{ current: 'text-primary' }`. */
21
+ ui?: {
22
+ root?: string;
23
+ list?: string;
24
+ item?: string;
25
+ link?: string;
26
+ current?: string;
27
+ separator?: string;
28
+ };
29
+ }
30
+ declare var __VLS_26: {};
31
+ type __VLS_Slots = {} & {
32
+ separator?: (props: typeof __VLS_26) => any;
33
+ };
34
+ declare const __VLS_base: import("vue").DefineComponent<BreadcrumbProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<BreadcrumbProps> & Readonly<{}>, {
35
+ unstyled: boolean;
36
+ label: string;
37
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
39
+ declare const _default: typeof __VLS_export;
40
+ export default _default;
41
+ type __VLS_WithSlots<T, S> = T & {
42
+ new (): {
43
+ $slots: S;
44
+ };
45
+ };
@@ -7,6 +7,14 @@ export interface ButtonProps {
7
7
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
8
8
  /** Stretch to the full width of the container. */
9
9
  block?: boolean;
10
+ /**
11
+ * Square the button for icon-only use, dropping the horizontal padding.
12
+ *
13
+ * For an icon *beside* a label, mark the icon with
14
+ * `data-icon="inline-start"` or `data-icon="inline-end"` instead — the
15
+ * padding then tightens on that side.
16
+ */
17
+ square?: boolean;
10
18
  /** Show a spinner (in the leading position) and disable interaction. */
11
19
  loading?: boolean;
12
20
  disabled?: boolean;
@@ -0,0 +1,32 @@
1
+ import type { ButtonSize } from '../composables/button-group';
2
+ export interface ButtonGroupProps {
3
+ /** Render as a different element or component. */
4
+ as?: string;
5
+ orientation?: 'horizontal' | 'vertical';
6
+ /**
7
+ * Size applied to every button inside, so it only has to be set once.
8
+ * A button's own `size` still wins.
9
+ */
10
+ size?: ButtonSize;
11
+ /** Stretch to fill the container, splitting the width between children. */
12
+ block?: boolean;
13
+ /** Skip built-in classes; you take over styling entirely. */
14
+ unstyled?: boolean;
15
+ class?: string;
16
+ }
17
+ declare var __VLS_8: {};
18
+ type __VLS_Slots = {} & {
19
+ default?: (props: typeof __VLS_8) => any;
20
+ };
21
+ declare const __VLS_base: import("vue").DefineComponent<ButtonGroupProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonGroupProps> & Readonly<{}>, {
22
+ as: string;
23
+ unstyled: boolean;
24
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
25
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
26
+ declare const _default: typeof __VLS_export;
27
+ export default _default;
28
+ type __VLS_WithSlots<T, S> = T & {
29
+ new (): {
30
+ $slots: S;
31
+ };
32
+ };
@@ -0,0 +1,18 @@
1
+ export interface ConfirmDialogProps {
2
+ /** Fallback confirm label, used when a request doesn't supply one. */
3
+ confirmLabel?: string;
4
+ /** Fallback cancel label, used when a request doesn't supply one. */
5
+ cancelLabel?: string;
6
+ size?: 'sm' | 'md' | 'lg' | 'xl';
7
+ /** Skip built-in classes; you take over styling entirely. */
8
+ unstyled?: boolean;
9
+ class?: string;
10
+ }
11
+ declare const __VLS_export: import("vue").DefineComponent<ConfirmDialogProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ConfirmDialogProps> & Readonly<{}>, {
12
+ size: "sm" | "md" | "lg" | "xl";
13
+ unstyled: boolean;
14
+ confirmLabel: string;
15
+ cancelLabel: string;
16
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,65 @@
1
+ export interface DialogProps {
2
+ title?: string;
3
+ description?: string;
4
+ size?: 'sm' | 'md' | 'lg' | 'xl';
5
+ /** Clicking the overlay or pressing Escape closes it. Set false to force a choice. */
6
+ dismissible?: boolean;
7
+ /** Render the corner dismiss button. */
8
+ showClose?: boolean;
9
+ /** Accessible name for the dismiss button — override for non-English apps. */
10
+ closeLabel?: string;
11
+ /** Skip built-in classes; you take over styling entirely. */
12
+ unstyled?: boolean;
13
+ class?: string;
14
+ /** Override classes per slot, e.g. `{ content: 'max-w-3xl' }`. */
15
+ ui?: {
16
+ overlay?: string;
17
+ content?: string;
18
+ header?: string;
19
+ title?: string;
20
+ description?: string;
21
+ body?: string;
22
+ footer?: string;
23
+ close?: string;
24
+ };
25
+ }
26
+ type __VLS_Props = DialogProps;
27
+ declare function close(): void;
28
+ type __VLS_ModelProps = {
29
+ 'open'?: boolean;
30
+ };
31
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
32
+ declare var __VLS_16: {}, __VLS_35: {}, __VLS_43: {}, __VLS_51: {}, __VLS_65: {}, __VLS_67: {
33
+ close: typeof close;
34
+ };
35
+ type __VLS_Slots = {} & {
36
+ trigger?: (props: typeof __VLS_16) => any;
37
+ } & {
38
+ header?: (props: typeof __VLS_35) => any;
39
+ } & {
40
+ title?: (props: typeof __VLS_43) => any;
41
+ } & {
42
+ description?: (props: typeof __VLS_51) => any;
43
+ } & {
44
+ default?: (props: typeof __VLS_65) => any;
45
+ } & {
46
+ footer?: (props: typeof __VLS_67) => any;
47
+ };
48
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
49
+ "update:open": (value: boolean) => any;
50
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
51
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
52
+ }>, {
53
+ closeLabel: string;
54
+ unstyled: boolean;
55
+ dismissible: boolean;
56
+ showClose: boolean;
57
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
58
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
59
+ declare const _default: typeof __VLS_export;
60
+ export default _default;
61
+ type __VLS_WithSlots<T, S> = T & {
62
+ new (): {
63
+ $slots: S;
64
+ };
65
+ };
@@ -0,0 +1,49 @@
1
+ import type { DropdownMenuEntry } from '../composables/dropdown-menu';
2
+ export interface DropdownMenuProps {
3
+ /** Entries to render. Nest by giving an entry its own `items`. */
4
+ items?: DropdownMenuEntry[];
5
+ /** Which edge of the trigger to align to. */
6
+ align?: 'start' | 'center' | 'end';
7
+ side?: 'top' | 'right' | 'bottom' | 'left';
8
+ sideOffset?: number;
9
+ /** Skip built-in classes; you take over styling entirely. */
10
+ unstyled?: boolean;
11
+ class?: string;
12
+ /** Override classes per slot, e.g. `{ item: 'py-2' }`. */
13
+ ui?: {
14
+ content?: string;
15
+ item?: string;
16
+ label?: string;
17
+ separator?: string;
18
+ subTrigger?: string;
19
+ subContent?: string;
20
+ };
21
+ }
22
+ type __VLS_Props = DropdownMenuProps;
23
+ type __VLS_ModelProps = {
24
+ 'open'?: boolean;
25
+ };
26
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
27
+ declare var __VLS_14: {}, __VLS_28: {};
28
+ type __VLS_Slots = {} & {
29
+ trigger?: (props: typeof __VLS_14) => any;
30
+ } & {
31
+ default?: (props: typeof __VLS_28) => any;
32
+ };
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
34
+ "update:open": (value: boolean) => any;
35
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
36
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
37
+ }>, {
38
+ unstyled: boolean;
39
+ align: "start" | "center" | "end";
40
+ sideOffset: number;
41
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
42
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
43
+ declare const _default: typeof __VLS_export;
44
+ export default _default;
45
+ type __VLS_WithSlots<T, S> = T & {
46
+ new (): {
47
+ $slots: S;
48
+ };
49
+ };
@@ -0,0 +1,24 @@
1
+ import type { DropdownMenuEntry } from '../composables/dropdown-menu';
2
+ /**
3
+ * Renders one level of menu entries, recursing into submenus.
4
+ *
5
+ * Split out of `DropdownMenu` so it can call itself: an SFC may reference
6
+ * itself by its filename, which is what allows arbitrary nesting depth.
7
+ */
8
+ export interface DropdownMenuItemsProps {
9
+ entries: DropdownMenuEntry[];
10
+ /** Skip built-in classes; you take over styling entirely. */
11
+ unstyled?: boolean;
12
+ ui?: {
13
+ item?: string;
14
+ label?: string;
15
+ separator?: string;
16
+ subTrigger?: string;
17
+ subContent?: string;
18
+ };
19
+ }
20
+ declare const __VLS_export: import("vue").DefineComponent<DropdownMenuItemsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<DropdownMenuItemsProps> & Readonly<{}>, {
21
+ unstyled: boolean;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,46 @@
1
+ import type { IconLike } from '../composables/icon';
2
+ export interface EmptyStateProps {
3
+ /** Render as a different element or component. */
4
+ as?: string;
5
+ /** Illustrative icon shown above the title. Omitted when not given. */
6
+ icon?: IconLike | false;
7
+ title?: string;
8
+ description?: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ /** Skip built-in classes; you take over styling entirely. */
11
+ unstyled?: boolean;
12
+ class?: string;
13
+ /** Override classes per slot, e.g. `{ title: 'text-xl' }`. */
14
+ ui?: {
15
+ root?: string;
16
+ icon?: string;
17
+ title?: string;
18
+ description?: string;
19
+ actions?: string;
20
+ };
21
+ }
22
+ declare var __VLS_8: {}, __VLS_15: {}, __VLS_17: {}, __VLS_19: {}, __VLS_21: {};
23
+ type __VLS_Slots = {} & {
24
+ icon?: (props: typeof __VLS_8) => any;
25
+ } & {
26
+ title?: (props: typeof __VLS_15) => any;
27
+ } & {
28
+ description?: (props: typeof __VLS_17) => any;
29
+ } & {
30
+ default?: (props: typeof __VLS_19) => any;
31
+ } & {
32
+ actions?: (props: typeof __VLS_21) => any;
33
+ };
34
+ declare const __VLS_base: import("vue").DefineComponent<EmptyStateProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<EmptyStateProps> & Readonly<{}>, {
35
+ icon: IconLike | false;
36
+ as: string;
37
+ unstyled: boolean;
38
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
40
+ declare const _default: typeof __VLS_export;
41
+ export default _default;
42
+ type __VLS_WithSlots<T, S> = T & {
43
+ new (): {
44
+ $slots: S;
45
+ };
46
+ };
@@ -11,6 +11,15 @@ export interface FormFieldProps {
11
11
  required?: boolean;
12
12
  /** Force an error message, bypassing the form's validation. */
13
13
  error?: string;
14
+ /**
15
+ * Indent the label, description, error and help text so they line up with
16
+ * the control's own text rather than its outer edge.
17
+ *
18
+ * The values match the input's horizontal padding, so pass the same size you
19
+ * gave the control. Use `none` for controls that draw their own label, like
20
+ * a checkbox or switch.
21
+ */
22
+ indent?: 'none' | 'sm' | 'md' | 'lg';
14
23
  /** Skip built-in classes; you take over styling entirely. */
15
24
  unstyled?: boolean;
16
25
  class?: string;
@@ -0,0 +1,8 @@
1
+ import type { IconLike } from '../composables/icon';
2
+ export interface IconProps {
3
+ icon?: IconLike;
4
+ class?: string;
5
+ }
6
+ declare const __VLS_export: import("vue").DefineComponent<IconProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<IconProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -2,6 +2,12 @@ import type { LabelProps as RekaLabelProps } from 'reka-ui';
2
2
  export interface LabelProps extends RekaLabelProps {
3
3
  /** Append a red asterisk to mark the associated field as required. */
4
4
  required?: boolean;
5
+ /**
6
+ * Indent the label to line up with the control's text rather than its outer
7
+ * edge. Match it to the control's size; use `none` when the label wraps its
8
+ * control, as with a checkbox.
9
+ */
10
+ indent?: 'none' | 'sm' | 'md' | 'lg';
5
11
  /** Skip built-in classes; you take over styling entirely. */
6
12
  unstyled?: boolean;
7
13
  class?: string;