orio-ui 1.27.0 → 1.28.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 (64) hide show
  1. package/README.md +76 -1
  2. package/bin/orio-ui.mjs +72 -0
  3. package/dist/agents/ROUTING.md +140 -0
  4. package/dist/agents/component-finder.md +142 -0
  5. package/dist/agents/component-worker.md +152 -0
  6. package/dist/agents/snippet.md +6 -0
  7. package/dist/module.json +1 -1
  8. package/dist/runtime/components/AnimatedContainer.USAGE.md +79 -0
  9. package/dist/runtime/components/Badge.USAGE.md +75 -0
  10. package/dist/runtime/components/Banner.USAGE.md +52 -0
  11. package/dist/runtime/components/Button.USAGE.md +78 -0
  12. package/dist/runtime/components/Calendar.USAGE.md +8 -0
  13. package/dist/runtime/components/Canvas/USAGE.md +8 -0
  14. package/dist/runtime/components/CheckBox.USAGE.md +63 -0
  15. package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
  16. package/dist/runtime/components/ControlElement.USAGE.md +8 -0
  17. package/dist/runtime/components/DashedContainer.USAGE.md +65 -0
  18. package/dist/runtime/components/EmptyState.USAGE.md +65 -0
  19. package/dist/runtime/components/Form.USAGE.md +102 -0
  20. package/dist/runtime/components/Icon.USAGE.md +61 -0
  21. package/dist/runtime/components/Input.USAGE.md +8 -0
  22. package/dist/runtime/components/ListItem.USAGE.md +84 -0
  23. package/dist/runtime/components/LoadingSpinner.USAGE.md +50 -0
  24. package/dist/runtime/components/LocaleSwitcher.USAGE.md +73 -0
  25. package/dist/runtime/components/Modal.USAGE.md +8 -0
  26. package/dist/runtime/components/NavButton.USAGE.md +80 -0
  27. package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
  28. package/dist/runtime/components/NumberInput/USAGE.md +74 -0
  29. package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
  30. package/dist/runtime/components/Popover.USAGE.md +103 -0
  31. package/dist/runtime/components/RadioButton.USAGE.md +72 -0
  32. package/dist/runtime/components/Selector.USAGE.md +131 -0
  33. package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
  34. package/dist/runtime/components/Tag.USAGE.md +51 -0
  35. package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
  36. package/dist/runtime/components/Textarea.USAGE.md +72 -0
  37. package/dist/runtime/components/Tooltip.USAGE.md +84 -0
  38. package/dist/runtime/components/ZoomableContainer.USAGE.md +108 -0
  39. package/dist/runtime/components/date/Picker.USAGE.md +8 -0
  40. package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
  41. package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
  42. package/dist/runtime/components/gallery/Carousel.USAGE.md +98 -0
  43. package/dist/runtime/components/gallery/CarouselPreview.USAGE.md +51 -0
  44. package/dist/runtime/components/upload/USAGE.md +91 -0
  45. package/dist/runtime/components/view/Dates.USAGE.md +67 -0
  46. package/dist/runtime/components/view/KeyBinds.USAGE.md +58 -0
  47. package/dist/runtime/components/view/Separator.USAGE.md +57 -0
  48. package/dist/runtime/components/view/Text.USAGE.md +68 -0
  49. package/dist/runtime/composables/useApi.USAGE.md +64 -0
  50. package/dist/runtime/composables/useControlSize.USAGE.md +73 -0
  51. package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
  52. package/dist/runtime/composables/useFilter.USAGE.md +120 -0
  53. package/dist/runtime/composables/useFuzzySearch.USAGE.md +68 -0
  54. package/dist/runtime/composables/useInertia.USAGE.md +80 -0
  55. package/dist/runtime/composables/useListKeyboard.USAGE.md +97 -0
  56. package/dist/runtime/composables/useModal.USAGE.md +82 -0
  57. package/dist/runtime/composables/usePinchZoom.USAGE.md +95 -0
  58. package/dist/runtime/composables/usePressAndHold.USAGE.md +70 -0
  59. package/dist/runtime/composables/useRovingGrid.USAGE.md +106 -0
  60. package/dist/runtime/composables/useSound.USAGE.md +74 -0
  61. package/dist/runtime/composables/useTheme.USAGE.md +76 -0
  62. package/dist/runtime/composables/useUrlSync.USAGE.md +91 -0
  63. package/dist/runtime/composables/useValidation.USAGE.md +100 -0
  64. package/package.json +12 -2
@@ -0,0 +1,102 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: form wrapper, submission, validation surface, auto-bind form model
5
+ short: form wrapper that auto-binds child controls by `name` prop to a single object v-model
6
+ invariants: true
7
+ ---
8
+
9
+ # Form — agent-only invariants
10
+
11
+ `<orio-form>` is a form wrapper that walks its slot vnodes and **auto-binds
12
+ child controls to a single object v-model** by matching each child's
13
+ `name` prop to a key (or dot path) in the model. Generic over `T extends
14
+ object`.
15
+
16
+ ## Invariants
17
+
18
+ - **v-model is an object `T`.** Each child with a `name="..."` prop
19
+ receives `modelValue` and `onUpdate:modelValue` cloned in via
20
+ `cloneVNode`. The user's hand-written v-model on that child is
21
+ overridden if `name` matches a field.
22
+ - **Auto-bind requires both `name` and a matching field path.** Children
23
+ without `name`, or with a `name` that doesn't resolve via `getByPath`,
24
+ render unchanged. Auto-bind is silent — no warning when a name doesn't
25
+ match.
26
+ - **Dot-paths supported**: `name="user.email"` writes to `model.user.email`.
27
+ The intermediate path must already exist (`setByPath` no-ops if the
28
+ parent is missing).
29
+ - **Children must follow the `modelValue` / `update:modelValue` contract.**
30
+ Auto-bind injects these props directly; v-model sugar is not used. Any
31
+ child that doesn't honor that pair is silently un-bound. All orio
32
+ form components do; custom children need to follow the contract.
33
+ - **Slot walking is recursive.** Vnode children, slot functions, and slot
34
+ objects are all descended. Wrappers (`<div>` groupers, layout columns)
35
+ are transparent.
36
+ - **`disabled` wraps the children in `<fieldset disabled>`** with
37
+ `display: contents` so nothing visual changes. The native `disabled`
38
+ attribute on the fieldset gates every form control inside (browser-
39
+ native behavior).
40
+ - **`loading` adds `pointer-events: none`** plus 0.6 opacity on the form.
41
+ Visual + interaction lock without disabling form values.
42
+ - **`novalidate` is set on the `<form>`** — HTML5 native validation
43
+ bubbles are off. Pair with `useValidation` for declarative checks.
44
+ - **`@submit` emits a single `submit` event** after `preventDefault`. The
45
+ emit is gated by `disabled || loading`. There is **no** built-in submit
46
+ button — render `<orio-button type="submit">` (or similar) inside.
47
+
48
+ ## Gotchas
49
+
50
+ - **No per-field error wiring.** `error` props on children are still
51
+ user-managed. The Form is only a binder, not a validator.
52
+ - **Reactivity through `setByPath` requires mutable nested objects.** A
53
+ frozen / readonly model breaks silently. Use a plain ref'd object.
54
+ - **Auto-bind clones the vnode** — keyed lists work, but if a child also
55
+ emits something other than `update:modelValue` via the same `onUpdate`
56
+ pattern, the clone replaces only the listed handlers.
57
+ - **Slot is descended into every render** — performance scales linearly
58
+ with slot depth. For very large forms (hundreds of fields), consider
59
+ splitting into nested `<orio-form>`s by section.
60
+ - **`name` collisions silently overwrite.** Two children with the same
61
+ `name` both bind to the same field; both updates write to the same
62
+ spot. Order is undefined.
63
+
64
+ ## Quick reference
65
+
66
+ ```vue
67
+ <script setup lang="ts">
68
+ interface Profile {
69
+ name: string;
70
+ email: string;
71
+ preferences: { newsletter: boolean };
72
+ }
73
+
74
+ const profile = ref<Profile>({
75
+ name: "",
76
+ email: "",
77
+ preferences: { newsletter: false },
78
+ });
79
+
80
+ function save() {
81
+ // Submit profile.value to API
82
+ }
83
+ </script>
84
+
85
+ <template>
86
+ <orio-form v-model="profile" :loading="saving" @submit="save">
87
+ <orio-input name="name" :label="$t('profile.name')" />
88
+ <orio-input name="email" :label="$t('profile.email')" type="email" />
89
+ <orio-check-box name="preferences.newsletter">
90
+ {{ $t("profile.newsletter") }}
91
+ </orio-check-box>
92
+
93
+ <orio-button type="submit">{{ $t("common.save") }}</orio-button>
94
+ </orio-form>
95
+ </template>
96
+ ```
97
+
98
+ ## Related
99
+
100
+ - `useValidation` — declarative validation rules for form fields.
101
+ - `<orio-control-element>` — wraps every form input with label/error.
102
+ - Public API reference: `docs/components/form.md`.
@@ -0,0 +1,61 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: icon, SVG renderer, glyph, symbol
5
+ short: SVG icon renderer that pulls from `utils/icon-registry` and renders via v-html
6
+ invariants: true
7
+ ---
8
+
9
+ # Icon — agent-only invariants
10
+
11
+ `<orio-icon>` renders an SVG icon from `utils/icon-registry` into a
12
+ `<span>` via `v-html`. Unknown icon names render empty.
13
+
14
+ ## Invariants
15
+
16
+ - **`name` is required.** Either a registered `IconName` (typed) or any
17
+ string. Unregistered names render as empty string (no visible
18
+ output, no warning).
19
+ - **`size`**: `string | number`. A number is treated as pixels (`"24px"`).
20
+ A string is used verbatim (`"1.5em"`, `"2rem"`, `"100%"`). When
21
+ `undefined`, falls back to the CSS var `--control-icon-size` (default
22
+ `1.5em`).
23
+ - **`color` defaults to `"currentColor"`** — the icon inherits parent
24
+ text color. Pass a CSS color string to override.
25
+ - **`v-html` is used to inject the raw SVG markup.** The icon registry
26
+ is the trust boundary — never render dynamic / user-controlled SVG
27
+ strings through it.
28
+ - **SVGs inside use `fill: currentColor`** so the `color` prop / parent
29
+ color flows through.
30
+ - **`flex-shrink: 0`** is set on the span. The icon never shrinks inside
31
+ a flex layout — important for inline labels with overflow.
32
+ - **The wrapper is `display: inline-flex; align-items: center; justify-content: center`**
33
+ so the SVG is centered when the size exceeds the SVG viewBox.
34
+
35
+ ## Gotchas
36
+
37
+ - **Bypass-XSS surface is the registry.** Anything in `utils/icon-registry`
38
+ is rendered as raw HTML. Do not extend the registry with strings
39
+ derived from user input.
40
+ - **Sizing a string `100%` requires a sized parent.** The span goes to
41
+ the size you pass; the inner SVG fills 100% of that.
42
+ - **`color` is applied via inline style**, so it beats class-based
43
+ styling. To recolor via CSS class, omit the `color` prop.
44
+ - **Spelling errors silently render nothing.** If an icon is missing
45
+ during dev, check the registry — there's no console warning.
46
+
47
+ ## Quick reference
48
+
49
+ ```vue
50
+ <template>
51
+ <orio-icon name="check" :size="24" />
52
+ <orio-icon name="warning" size="1.5rem" color="var(--color-alert)" />
53
+ <orio-icon name="search" />
54
+ </template>
55
+ ```
56
+
57
+ ## Related
58
+
59
+ - `<orio-loading-spinner>` — thin wrapper for the `loading-loop` icon.
60
+ - `utils/icon-registry` — the source of all available icon names.
61
+ - Public API reference: `docs/components/icon.md`.
@@ -1,3 +1,11 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: text input, single-line input
5
+ short: text input wrapping ControlElement; supports inner-floating label layout
6
+ invariants: true
7
+ ---
8
+
1
9
  # Input — agent-only invariants
2
10
 
3
11
  `<orio-input>` is the text input wrapping `ControlElement`. Read
@@ -0,0 +1,84 @@
1
+ ---
2
+ kind: component
3
+ category: Media & misc
4
+ purpose: list row, list item, selectable row, list entry
5
+ short: `<li>` row with start/end slots and optional selectable checkbox-style behavior
6
+ invariants: true
7
+ ---
8
+
9
+ # ListItem — agent-only invariants
10
+
11
+ `<orio-list-item>` is a `<li>` row with three content zones (start /
12
+ center / end) and an optional selectable mode. It is also the row
13
+ primitive used internally by `<orio-selector>`.
14
+
15
+ ## Invariants
16
+
17
+ - **Renders an `<li>`.** Must be a child of `<ul>` / `<ol>` for valid
18
+ HTML. Rendering it loose works but breaks list semantics.
19
+ - **`selectable` prop** turns the row into an interactive checkbox-style
20
+ control:
21
+ - `tabindex="0"` (keyboard focusable).
22
+ - `role="checkbox"` + `aria-checked` reflect `selected`.
23
+ - Click and Enter / Space toggle the v-model.
24
+ - `cursor: pointer`.
25
+ - **v-model is `selected: boolean`.** Updates on toggle. Without
26
+ `selectable`, the model is still bound but no toggle handler runs.
27
+ - **Three slots**:
28
+ - `#start` — left zone. **Only renders** when the slot is provided
29
+ OR `selectable` is true. When selectable and no slot, defaults to
30
+ `<orio-check-box :model-value="selected">`.
31
+ - `#default` — center content. Always renders, `flex-grow: 1`.
32
+ - `#end` — right zone. Only renders when the slot is provided.
33
+ - **Selected state** uses `--color-accent` background and
34
+ `--color-accent-soft-darker` text. Hover swaps to surface bg when
35
+ not selected.
36
+ - **Used internally by `<orio-selector>`** as `role="option"` rows. In
37
+ that usage the role gets overridden via `$attrs`.
38
+
39
+ ## Gotchas
40
+
41
+ - **No multi-selection grouping.** A single ListItem holds one
42
+ selected boolean. For grouped list selection, wire each item's
43
+ `v-model:selected` to a parent array.
44
+ - **Default `<orio-check-box>` in `#start`** uses bare props — it has
45
+ no label, no accent state of its own. If the row is `selected`, the
46
+ checkbox shows checked.
47
+ - **Keyboard `Enter` and `Space` `.preventDefault()`** — Space won't
48
+ scroll the page, Enter won't submit a form. Useful, but
49
+ unconfigurable.
50
+ - **Without `selectable`**, the row is still clickable but no toggle /
51
+ focus / role is applied. To make it a button-like row without a
52
+ checkbox-style toggle, wrap content in a real `<button>` inside
53
+ `#default`.
54
+
55
+ ## Quick reference
56
+
57
+ ```vue
58
+ <template>
59
+ <ul>
60
+ <orio-list-item
61
+ v-for="item in items"
62
+ :key="item.id"
63
+ v-model:selected="item.selected"
64
+ selectable
65
+ >
66
+ <template #start>
67
+ <orio-icon :name="item.icon" />
68
+ </template>
69
+
70
+ {{ item.label }}
71
+
72
+ <template #end>
73
+ <orio-tag :text="item.badge" variant="accent" />
74
+ </template>
75
+ </orio-list-item>
76
+ </ul>
77
+ </template>
78
+ ```
79
+
80
+ ## Related
81
+
82
+ - `<orio-selector>` — uses this as listbox rows.
83
+ - `<orio-check-box>` — default `#start` content when selectable.
84
+ - Public API reference: `docs/components/list-item.md`.
@@ -0,0 +1,50 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: spinner, loading indicator, loading icon, busy indicator
5
+ short: thin wrapper that renders the bundled `loading-loop` icon; no props
6
+ invariants: false
7
+ ---
8
+
9
+ # LoadingSpinner — agent-only invariants
10
+
11
+ `<orio-loading-spinner>` renders the bundled `loading-loop` icon. That's
12
+ it. There are **no props**, no slots, no emits.
13
+
14
+ ## Invariants
15
+
16
+ - **Zero-prop wrapper.** Template is literally
17
+ `<orio-icon name="loading-loop" />`.
18
+ - **Animation lives in the icon SVG itself** (via the registry's
19
+ `loading-loop` entry). The component does not apply any CSS animation.
20
+ - **Size and color follow `<orio-icon>` defaults** — `1.5em` from
21
+ `--control-icon-size`, `currentColor`. Override via parent CSS:
22
+ `font-size`, `color`, or by passing direct CSS to a wrapper.
23
+ - **Used internally by `<orio-button :loading>`** — when wiring loading
24
+ states into buttons, prefer `:loading="..."` on the button to swapping
25
+ in this spinner manually.
26
+
27
+ ## Gotchas
28
+
29
+ - **No way to change spin direction, speed, or thickness.** The SVG is
30
+ fixed. For a custom spinner, render `<orio-icon>` with your own icon
31
+ name + CSS animation.
32
+ - **Aria semantics are absent.** No `role="status"`, no
33
+ `aria-label`. For screen-reader announcement, wrap in a `<span
34
+ role="status" aria-label="Loading">` at the consumer.
35
+
36
+ ## Quick reference
37
+
38
+ ```vue
39
+ <template>
40
+ <orio-loading-spinner v-if="loading" />
41
+ </template>
42
+ ```
43
+
44
+ ## Related
45
+
46
+ - `<orio-icon>` — under the hood; use it directly for non-spinner
47
+ glyphs.
48
+ - `<orio-button :loading>` — preferred way to show busy state on
49
+ buttons.
50
+ - Public API reference: `docs/components/loading-spinner.md`.
@@ -0,0 +1,73 @@
1
+ ---
2
+ kind: component
3
+ category: Media & misc
4
+ purpose: locale switcher, language toggle, i18n switcher
5
+ short: preconfigured Selector that mutates vue-i18n's locale; defaults to English + Ukrainian with flag emojis
6
+ invariants: true
7
+ ---
8
+
9
+ # LocaleSwitcher — agent-only invariants
10
+
11
+ `<orio-locale-switcher>` is a thin wrapper around `<orio-selector>` that
12
+ reads and writes `useI18n().locale` directly. Drop it anywhere in the app
13
+ to give users a language toggle.
14
+
15
+ ## Invariants
16
+
17
+ - **Mutates `useI18n().locale` on selection.** No model is exposed —
18
+ the side effect is the API.
19
+ - **Default `locales`**:
20
+ ```ts
21
+ [
22
+ { code: "en", flag: "🇬🇧", label: "English" },
23
+ { code: "uk", flag: "🇺🇦", label: "Українська" },
24
+ ]
25
+ ```
26
+ Override with the `locales` prop if your app supports a different set.
27
+ - **`LocaleOption` shape** (exported): `{ code, flag, label }`. All three
28
+ are strings; `flag` is rendered verbatim (emoji or any unicode).
29
+ - **Selector wiring**: `field: "code"`, `optionName: "label"`. Active
30
+ option matches by `code === currentLocale`. Falls back to the first
31
+ locale if the current i18n locale isn't in the list.
32
+ - **Custom `#trigger-label` and `#option` slots** render `flag + label`
33
+ side-by-side with 0.5rem gap.
34
+ - **Requires `useI18n` setup.** The component throws if called outside
35
+ a vue-i18n context.
36
+
37
+ ## Gotchas
38
+
39
+ - **Direct locale mutation bypasses any persistence layer.** If your
40
+ app saves locale to cookies / localStorage / API, hook into
41
+ `useI18n().locale` from elsewhere — this component does not call
42
+ any side effect beyond the i18n update.
43
+ - **Flag emojis depend on font support.** macOS / iOS render them
44
+ correctly; Windows often shows letter pairs (e.g. "GB", "UA"). For
45
+ cross-platform consistency, swap to icons via a custom `locales`
46
+ prop with icon names + a custom `#option`/`#trigger-label`.
47
+ - **No client/server hydration story.** If the locale is mutated
48
+ before vue-i18n is hydrated on the client, mismatches can occur.
49
+ Best to initialize locale in your app setup and let this switcher
50
+ only handle user-driven changes.
51
+
52
+ ## Quick reference
53
+
54
+ ```vue
55
+ <template>
56
+ <orio-locale-switcher />
57
+
58
+ <orio-locale-switcher
59
+ :locales="[
60
+ { code: 'en', flag: '🇺🇸', label: 'English' },
61
+ { code: 'es', flag: '🇪🇸', label: 'Español' },
62
+ { code: 'pt', flag: '🇧🇷', label: 'Português' },
63
+ ]"
64
+ />
65
+ </template>
66
+ ```
67
+
68
+ ## Related
69
+
70
+ - `<orio-selector>` — under the hood. Build your own switcher from
71
+ Selector if you need different side effects (e.g. routing).
72
+ - Public API reference: `docs/components/locale-switcher.md` (if
73
+ present).
@@ -1,3 +1,11 @@
1
+ ---
2
+ kind: component
3
+ category: Layout & containers
4
+ purpose: modal, dialog, popup overlay, lightbox
5
+ short: teleported overlay dialog with open-from-origin animation
6
+ invariants: true
7
+ ---
8
+
1
9
  # Modal — agent-only invariants
2
10
 
3
11
  `<orio-modal>` is the teleported overlay dialog.
@@ -0,0 +1,80 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: nav button, link-styled button, navigation item, sidebar item
5
+ short: bare nav-styled button with `active` state and `aria-current="page"` for the current route
6
+ invariants: true
7
+ ---
8
+
9
+ # NavButton — agent-only invariants
10
+
11
+ `<orio-nav-button>` is a transparent, text-styled button for navigation
12
+ menus and tab bars. It is not a `<router-link>` — wrap it or wire
13
+ navigation in the `click` handler yourself.
14
+
15
+ ## Invariants
16
+
17
+ - **`active` prop is the "is this the current item" flag.** When true:
18
+ - Text becomes accent color, font-weight 600.
19
+ - `aria-current="page"` is set on the inner `<button>`.
20
+ - `undefined` (not removed) otherwise — so it doesn't appear in the
21
+ DOM at all when inactive.
22
+ - **`icon` prop OR `#icon` slot** — same pattern as `<orio-button>`.
23
+ - **Icon-only mode is auto-detected** (icon + no default slot) →
24
+ `border-radius: 50%`, `aspect-ratio: 1`, `padding: var(--control-py)`.
25
+ - **No `variant` prop.** One look only — transparent background, text
26
+ color, no border.
27
+ - **`disabled` blocks click** and applies 0.5 opacity + `cursor:
28
+ not-allowed`.
29
+ - **Only emits `click`.** No mousedown/mouseup like `<orio-button>`.
30
+ - **Focus ring**: `outline: 2px solid var(--color-accent)` with
31
+ `outline-offset: 2px`. Keyboard-only via `:focus-visible`.
32
+
33
+ ## Gotchas
34
+
35
+ - **Not a router link.** No `to`, no `href`. Wire navigation in
36
+ `@click`. If a real anchor is needed for a11y / right-click-to-open,
37
+ fall back to your router's link component.
38
+ - **Same `$attrs` duplication caveat as `<orio-button>`** — attrs may
39
+ land on both the wrapper and the inner `<button>`.
40
+ - **Active state is purely visual + ARIA**; the component does not
41
+ detect the current route. Compute `active` from `useRoute()` or your
42
+ router state.
43
+ - **`type` defaults to `submit`** (native default). Pass `type="button"`
44
+ if mounted inside a form to avoid accidental submits.
45
+
46
+ ## Quick reference
47
+
48
+ ```vue
49
+ <script setup lang="ts">
50
+ import { useRoute, useRouter } from "vue-router";
51
+
52
+ const route = useRoute();
53
+ const router = useRouter();
54
+ </script>
55
+
56
+ <template>
57
+ <nav>
58
+ <orio-nav-button
59
+ icon="home"
60
+ :active="route.path === '/'"
61
+ @click="router.push('/')"
62
+ >
63
+ {{ $t("nav.home") }}
64
+ </orio-nav-button>
65
+
66
+ <orio-nav-button
67
+ icon="settings"
68
+ :active="route.path === '/settings'"
69
+ @click="router.push('/settings')"
70
+ >
71
+ {{ $t("nav.settings") }}
72
+ </orio-nav-button>
73
+ </nav>
74
+ </template>
75
+ ```
76
+
77
+ ## Related
78
+
79
+ - `<orio-button>` — primary actions; use that for CTAs.
80
+ - Public API reference: `docs/components/nav-button.md`.
@@ -0,0 +1,61 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: number input horizontal, minus-plus stepper, quantity stepper
5
+ short: number input variant with minus/plus buttons flanking the field and press-and-hold repeat
6
+ invariants: true
7
+ ---
8
+
9
+ # NumberInput/Horizontal — agent-only invariants
10
+
11
+ `<orio-number-input-horizontal>` is a pre-styled wrapper around
12
+ `<orio-number-input>` that renders minus/plus buttons on either side of the
13
+ field. Read `NumberInput/USAGE.md` first — this variant inherits all of its
14
+ contract.
15
+
16
+ ## Invariants
17
+
18
+ - **Accepts the full `NumberInputProps` interface** plus a `disabled` prop
19
+ for the buttons. All props are forwarded via `v-bind="$props"`.
20
+ - **Buttons use `usePressAndHold`** — `@mousedown` starts the auto-repeat,
21
+ `@mouseup`/`@mouseleave` stops it. Hold to ramp through a range.
22
+ - **`disabled` and the per-button bound state both apply.** A minus button
23
+ is disabled when `disabled || isAtMin`; a plus button when
24
+ `disabled || isAtMax`.
25
+ - **Input text is centered** (`text-align: center` via `:deep(.number-input)`).
26
+ - **Controls are full-width inside the wrapper**: `justify-content: space-between`
27
+ with 3px horizontal padding. Buttons sit at the edges.
28
+ - **`layout="inner"` is supported** — the label centers between the two
29
+ buttons (`left: 0; right: 0; text-align: center`).
30
+ - **No keyboard auto-repeat.** Press-and-hold listens to mouse events
31
+ only; holding Enter on a focused button does not ramp.
32
+
33
+ ## Gotchas
34
+
35
+ - **Buttons are `<orio-button appearance="minimal" variant="subdued">`** —
36
+ they take theme tokens but are not slotted. To swap iconography, fall
37
+ back to the base `<orio-number-input>` with a custom `#controls` slot.
38
+ - **Touch behavior**: press-and-hold uses `@mousedown`/`@mouseup`. On touch
39
+ devices these may not fire reliably across all browsers — confirm on
40
+ iOS Safari if mobile is a target.
41
+
42
+ ## Quick reference
43
+
44
+ ```vue
45
+ <template>
46
+ <orio-number-input-horizontal
47
+ v-model="quantity"
48
+ :min="0"
49
+ :max="10"
50
+ :step="1"
51
+ :label="$t('cart.quantity')"
52
+ />
53
+ </template>
54
+ ```
55
+
56
+ ## Related
57
+
58
+ - `<orio-number-input>` — the base; use it when you need custom button
59
+ iconography or layout.
60
+ - `<orio-number-input-vertical>` — chevron-stack variant.
61
+ - `usePressAndHold` — composable behind the auto-repeat.
@@ -0,0 +1,74 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: number input, numeric input, custom-control numeric stepper
5
+ short: numeric input base with overlay controls slot; pair with Horizontal/Vertical variants for ready-made spinners
6
+ invariants: true
7
+ ---
8
+
9
+ # NumberInput — agent-only invariants
10
+
11
+ `<orio-number-input>` is the **base** numeric input. By itself it renders a
12
+ `<input type="number">` with no spinner. Custom step controls go in the
13
+ `#controls` slot. For ready-made stepper UIs, use
14
+ `<orio-number-input-horizontal>` or `<orio-number-input-vertical>`.
15
+
16
+ ## Invariants
17
+
18
+ - **Extends `NumberInputProps`** (exported from this file): `ControlProps`
19
+ minus `layout`, plus `layout?: InputLayout`, `min`, `max`, `step`,
20
+ `decimalPlaces`. Same `"inner"` layout trick as Input/Textarea.
21
+ - **v-model is `number`** (default `0`). Native input value is coerced via
22
+ Vue's `v-model.number` semantics.
23
+ - **Validation runs on blur and on every increase/decrease.** Value is
24
+ clamped to `[min, max]` (if finite) and then rounded to `decimalPlaces`
25
+ via `toFixed`. Typing an out-of-range value is allowed *during* edit;
26
+ blur snaps it back.
27
+ - **`decimalPlaces` defaults to `0`.** Decimal input requires both
28
+ `decimalPlaces` and a matching `step` (e.g. `:decimalPlaces="2" :step="0.01"`).
29
+ - **Native webkit/firefox spin buttons are hidden** via CSS. Always
30
+ `appearance: textfield`.
31
+ - **`#controls` slot overlays the input absolutely** with `pointer-events:
32
+ none` on the container and `:deep(button) { pointer-events: auto }`.
33
+ Only buttons receive clicks; the rest of the overlay passes through to
34
+ the input.
35
+ - **`#controls` slot props**: `{ increase, decrease, isAtMax, isAtMin }`.
36
+ `increase`/`decrease` apply `step` and run validation; `isAtMax`/`isAtMin`
37
+ are `false` when `min`/`max` are undefined.
38
+ - **`min`, `max`, `step`, `decimalPlaces` are stripped from `controlProps`**
39
+ before forwarding to ControlElement — they do not pollute the wrapper's
40
+ prop bag.
41
+ - **`$attrs` is spread before `control`** on the inner `<input>`, same as
42
+ Input.
43
+
44
+ ## Gotchas
45
+
46
+ - **No spinner UI without the slot or a variant.** A bare
47
+ `<orio-number-input v-model="n" />` renders nothing in the controls area.
48
+ - **`min`/`max` of `0` are honored** because the check uses `Number.isFinite`,
49
+ not truthiness.
50
+ - **Blur normalization rewrites the model.** Even if the user types a value
51
+ that is already valid, it gets re-`toFixed`d on blur — `"3"` becomes
52
+ `"3.00"` displayed when `decimalPlaces: 2`.
53
+ - **Negative `step` is not blocked.** Passing `step: -1` makes increase
54
+ decrement.
55
+
56
+ ## Quick reference — custom controls slot
57
+
58
+ ```vue
59
+ <template>
60
+ <orio-number-input v-model="quantity" :min="1" :max="99" :label="$t('cart.qty')">
61
+ <template #controls="{ increase, decrease, isAtMax, isAtMin }">
62
+ <orio-button :disabled="isAtMin" @click="decrease">−</orio-button>
63
+ <orio-button :disabled="isAtMax" @click="increase">+</orio-button>
64
+ </template>
65
+ </orio-number-input>
66
+ </template>
67
+ ```
68
+
69
+ ## Related
70
+
71
+ - `<orio-number-input-horizontal>` — pre-styled minus/plus on either side.
72
+ - `<orio-number-input-vertical>` — pre-styled chevron stack on the right.
73
+ - `<orio-input>` — when you want raw text, not a number.
74
+ - Public API reference: `docs/components/number-input.md`.
@@ -0,0 +1,55 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: number input vertical, chevron stepper, stacked-arrow numeric input
5
+ short: number input variant with chevron up/down stacked on the right and press-and-hold repeat
6
+ invariants: true
7
+ ---
8
+
9
+ # NumberInput/Vertical — agent-only invariants
10
+
11
+ `<orio-number-input-vertical>` is a pre-styled wrapper around
12
+ `<orio-number-input>` that renders stacked chevron up/down buttons on the
13
+ right edge. Read `NumberInput/USAGE.md` first — this variant inherits all
14
+ of its contract.
15
+
16
+ ## Invariants
17
+
18
+ - **Accepts the full `NumberInputProps` interface** plus a `disabled` prop
19
+ for the buttons. Forwarded via `v-bind="$props"`.
20
+ - **Buttons use `usePressAndHold`** — `@mousedown` starts auto-repeat,
21
+ `@mouseup`/`@mouseleave` stops.
22
+ - **Chevron up = `increase`, chevron down = `decrease`.** Standard
23
+ direction; do not swap them in a consumer.
24
+ - **Buttons are stacked vertically** in a column flex (`flex-direction:
25
+ column; justify-content: space-around`) anchored to `right: 3px`.
26
+ - **Input remains left-aligned** — text stays at its natural alignment;
27
+ only the controls move.
28
+
29
+ ## Gotchas
30
+
31
+ - **Padding-right may need a bump on long values.** The chevron stack
32
+ overlaps the input's right edge. For decimal values with many digits,
33
+ numbers may render under the buttons. Pad the input or switch to
34
+ `<orio-number-input-horizontal>`.
35
+ - **No keyboard auto-repeat.** Same limitation as the horizontal variant.
36
+
37
+ ## Quick reference
38
+
39
+ ```vue
40
+ <template>
41
+ <orio-number-input-vertical
42
+ v-model="zoomPercent"
43
+ :min="10"
44
+ :max="400"
45
+ :step="5"
46
+ :label="$t('editor.zoom')"
47
+ />
48
+ </template>
49
+ ```
50
+
51
+ ## Related
52
+
53
+ - `<orio-number-input>` — base; use for custom controls.
54
+ - `<orio-number-input-horizontal>` — minus/plus variant.
55
+ - `usePressAndHold` — composable behind auto-repeat.