orio-ui 1.24.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.
- package/README.md +78 -3
- package/bin/orio-ui.mjs +72 -0
- package/dist/agents/ROUTING.md +140 -0
- package/dist/agents/component-finder.md +142 -0
- package/dist/agents/component-worker.md +152 -0
- package/dist/agents/snippet.md +6 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/AnimatedContainer.USAGE.md +79 -0
- package/dist/runtime/components/Badge.USAGE.md +75 -0
- package/dist/runtime/components/Banner.USAGE.md +52 -0
- package/dist/runtime/components/Button.USAGE.md +78 -0
- package/dist/runtime/components/Button.d.vue.ts +3 -2
- package/dist/runtime/components/Button.vue +19 -11
- package/dist/runtime/components/Button.vue.d.ts +3 -2
- package/dist/runtime/components/Calendar.USAGE.md +59 -0
- package/dist/runtime/components/Calendar.vue +254 -87
- package/dist/runtime/components/Canvas/USAGE.md +73 -0
- package/dist/runtime/components/CheckBox.USAGE.md +63 -0
- package/dist/runtime/components/CheckBox.vue +9 -3
- package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
- package/dist/runtime/components/CheckboxGroup.vue +7 -1
- package/dist/runtime/components/ControlElement.USAGE.md +77 -0
- package/dist/runtime/components/ControlElement.d.vue.ts +42 -27
- package/dist/runtime/components/ControlElement.vue +28 -9
- package/dist/runtime/components/ControlElement.vue.d.ts +42 -27
- package/dist/runtime/components/DashedContainer.USAGE.md +65 -0
- package/dist/runtime/components/EmptyState.USAGE.md +65 -0
- package/dist/runtime/components/Form.USAGE.md +102 -0
- package/dist/runtime/components/Icon.USAGE.md +61 -0
- package/dist/runtime/components/Input.USAGE.md +57 -0
- package/dist/runtime/components/Input.vue +13 -3
- package/dist/runtime/components/ListItem.USAGE.md +84 -0
- package/dist/runtime/components/LoadingSpinner.USAGE.md +50 -0
- package/dist/runtime/components/LocaleSwitcher.USAGE.md +73 -0
- package/dist/runtime/components/Modal.USAGE.md +72 -0
- package/dist/runtime/components/NavButton.USAGE.md +80 -0
- package/dist/runtime/components/NavButton.d.vue.ts +0 -1
- package/dist/runtime/components/NavButton.vue +9 -5
- package/dist/runtime/components/NavButton.vue.d.ts +0 -1
- package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
- package/dist/runtime/components/NumberInput/Horizontal.vue +7 -2
- package/dist/runtime/components/NumberInput/USAGE.md +74 -0
- package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
- package/dist/runtime/components/NumberInput/Vertical.vue +7 -2
- package/dist/runtime/components/NumberInput/index.d.vue.ts +0 -2
- package/dist/runtime/components/NumberInput/index.vue +9 -7
- package/dist/runtime/components/NumberInput/index.vue.d.ts +0 -2
- package/dist/runtime/components/Popover.USAGE.md +103 -0
- package/dist/runtime/components/RadioButton.USAGE.md +72 -0
- package/dist/runtime/components/RadioButton.d.vue.ts +0 -2
- package/dist/runtime/components/RadioButton.vue +9 -4
- package/dist/runtime/components/RadioButton.vue.d.ts +0 -2
- package/dist/runtime/components/Selector.USAGE.md +131 -0
- package/dist/runtime/components/Selector.d.vue.ts +1 -0
- package/dist/runtime/components/Selector.vue +10 -4
- package/dist/runtime/components/Selector.vue.d.ts +1 -0
- package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
- package/dist/runtime/components/SwitchButton.d.vue.ts +1 -4
- package/dist/runtime/components/SwitchButton.vue +10 -7
- package/dist/runtime/components/SwitchButton.vue.d.ts +1 -4
- package/dist/runtime/components/Tag.USAGE.md +51 -0
- package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
- package/dist/runtime/components/TaggableSelector.vue +7 -1
- package/dist/runtime/components/Textarea.USAGE.md +72 -0
- package/dist/runtime/components/Textarea.vue +13 -3
- package/dist/runtime/components/Tooltip.USAGE.md +84 -0
- package/dist/runtime/components/ZoomableContainer.USAGE.md +108 -0
- package/dist/runtime/components/date/Picker.USAGE.md +52 -0
- package/dist/runtime/components/date/Picker.vue +7 -1
- package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
- package/dist/runtime/components/date/PickerTrigger.vue +9 -3
- package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
- package/dist/runtime/components/date/RangePicker.vue +7 -1
- package/dist/runtime/components/gallery/Carousel.USAGE.md +98 -0
- package/dist/runtime/components/gallery/CarouselPreview.USAGE.md +51 -0
- package/dist/runtime/components/upload/USAGE.md +91 -0
- package/dist/runtime/components/view/Dates.USAGE.md +67 -0
- package/dist/runtime/components/view/KeyBinds.USAGE.md +58 -0
- package/dist/runtime/components/view/Separator.USAGE.md +57 -0
- package/dist/runtime/components/view/Text.USAGE.md +68 -0
- package/dist/runtime/composables/useApi.USAGE.md +64 -0
- package/dist/runtime/composables/useControlSize.USAGE.md +73 -0
- package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
- package/dist/runtime/composables/useFilter.USAGE.md +120 -0
- package/dist/runtime/composables/useFilter.d.ts +91 -0
- package/dist/runtime/composables/useFilter.js +111 -0
- package/dist/runtime/composables/useFuzzySearch.USAGE.md +68 -0
- package/dist/runtime/composables/useInertia.USAGE.md +80 -0
- package/dist/runtime/composables/useListKeyboard.USAGE.md +97 -0
- package/dist/runtime/composables/useModal.USAGE.md +82 -0
- package/dist/runtime/composables/usePinchZoom.USAGE.md +95 -0
- package/dist/runtime/composables/usePressAndHold.USAGE.md +70 -0
- package/dist/runtime/composables/useRovingGrid.USAGE.md +106 -0
- package/dist/runtime/composables/useRovingGrid.d.ts +35 -0
- package/dist/runtime/composables/useRovingGrid.js +115 -0
- package/dist/runtime/composables/useSound.USAGE.md +74 -0
- package/dist/runtime/composables/useTheme.USAGE.md +76 -0
- package/dist/runtime/composables/useUrlSync.USAGE.md +91 -0
- package/dist/runtime/composables/useValidation.USAGE.md +100 -0
- package/dist/runtime/i18n/en.json +4 -1
- package/dist/runtime/i18n/uk.json +4 -1
- package/package.json +12 -2
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
|
|
9
|
+
# Input — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-input>` is the text input wrapping `ControlElement`. Read
|
|
12
|
+
`ControlElement.USAGE.md` first — most of the contract lives there.
|
|
13
|
+
|
|
14
|
+
## Invariants
|
|
15
|
+
|
|
16
|
+
- **Extends `ControlProps`** with one override: `layout?: InputLayout` where
|
|
17
|
+
`InputLayout = ControlLayout | "inner"`. The extra `"inner"` is an
|
|
18
|
+
Input-specific mode that floats the label inside the input chrome.
|
|
19
|
+
- **`layout="inner"` translates internally** to `layout="vertical"` on
|
|
20
|
+
ControlElement and adds an `.inner` class on the wrapper. The label
|
|
21
|
+
reposition is driven by `:deep()` styles on ControlElement internals — no
|
|
22
|
+
duplicate label DOM is created.
|
|
23
|
+
- **The `control` slot bag is spread onto the inner `<input>`** alongside
|
|
24
|
+
`$attrs`: `v-bind="{ ...$attrs, ...control }"`. Attrs like `type`,
|
|
25
|
+
`autocomplete`, `placeholder`, `inputmode` work on `<orio-input>` and land
|
|
26
|
+
on the underlying `<input>`.
|
|
27
|
+
- **v-model is `string`** (default `""`). For numeric input use
|
|
28
|
+
`<orio-number-input>` instead.
|
|
29
|
+
|
|
30
|
+
## Gotchas
|
|
31
|
+
|
|
32
|
+
- The `.slot-wrapper` uses `display: flex; align-items: center;` so `before`
|
|
33
|
+
and `after` slots sit inline with the input. Don't add wrapping divs inside
|
|
34
|
+
those slots — they'll break alignment.
|
|
35
|
+
- `:placeholder-shown` is used internally for the inner-label "empty" state.
|
|
36
|
+
If you pass an empty placeholder, the inner-label trick still works because
|
|
37
|
+
the wrapper sets `placeholder=" "` upstream when needed.
|
|
38
|
+
- The default browser input border is removed; the visible border lives on
|
|
39
|
+
`.slot-wrapper`. Custom inputs swapped in via slots will not inherit it —
|
|
40
|
+
prefer `before`/`after` slots over replacing the input.
|
|
41
|
+
|
|
42
|
+
## Quick reference
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<orio-input
|
|
46
|
+
v-model="email"
|
|
47
|
+
label="Email"
|
|
48
|
+
layout="inner"
|
|
49
|
+
type="email"
|
|
50
|
+
autocomplete="email"
|
|
51
|
+
:error="emailError"
|
|
52
|
+
>
|
|
53
|
+
<template #before>
|
|
54
|
+
<orio-icon name="mail" />
|
|
55
|
+
</template>
|
|
56
|
+
</orio-input>
|
|
57
|
+
```
|
|
@@ -7,20 +7,30 @@ const props = defineProps({
|
|
|
7
7
|
id: { type: String, required: false },
|
|
8
8
|
label: { type: String, required: false },
|
|
9
9
|
size: { type: String, required: false },
|
|
10
|
-
fill: { type: Boolean, required: false }
|
|
10
|
+
fill: { type: Boolean, required: false },
|
|
11
|
+
tabindex: { type: [Number, String], required: false },
|
|
12
|
+
focusKey: { type: String, required: false },
|
|
13
|
+
disabled: { type: Boolean, required: false },
|
|
14
|
+
required: { type: Boolean, required: false },
|
|
15
|
+
name: { type: String, required: false },
|
|
16
|
+
ariaLabel: { type: String, required: false }
|
|
11
17
|
});
|
|
12
18
|
const modelValue = defineModel({ type: String, ...{ default: "" } });
|
|
13
19
|
</script>
|
|
14
20
|
|
|
15
21
|
<template>
|
|
16
22
|
<orio-control-element
|
|
17
|
-
v-slot="{
|
|
23
|
+
v-slot="{ control }"
|
|
18
24
|
v-bind="props"
|
|
19
25
|
:layout="layout === 'inner' ? 'vertical' : layout"
|
|
20
26
|
:class="{ inner: layout === 'inner' }"
|
|
21
27
|
>
|
|
22
28
|
<slot name="before" />
|
|
23
|
-
<input
|
|
29
|
+
<input
|
|
30
|
+
v-model="modelValue"
|
|
31
|
+
type="text"
|
|
32
|
+
v-bind="{ ...$attrs, ...control }"
|
|
33
|
+
/>
|
|
24
34
|
<slot name="after" />
|
|
25
35
|
</orio-control-element>
|
|
26
36
|
</template>
|
|
@@ -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).
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
9
|
+
# Modal — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-modal>` is the teleported overlay dialog.
|
|
12
|
+
|
|
13
|
+
## Invariants
|
|
14
|
+
|
|
15
|
+
- **Teleported to `<body>`.** Renders outside the parent DOM subtree. Any
|
|
16
|
+
CSS that targets `.modal` from a parent will not apply; scope styles via
|
|
17
|
+
`:deep()` from a parent or write global styles.
|
|
18
|
+
- **`origin` prop drives the open animation.** Pass the `getBoundingClientRect`
|
|
19
|
+
of the element that triggered the open (e.g. the clicked button) to
|
|
20
|
+
animate the modal **from** that rect to centered. Pass `null` to fade in
|
|
21
|
+
at center with no scale-from.
|
|
22
|
+
- **`v-model:show`** controls visibility. Backdrop click closes (`@click.self`
|
|
23
|
+
on the overlay). The default close button (rendered when no `header`
|
|
24
|
+
slot is supplied) also toggles `show`.
|
|
25
|
+
- **Body scroll lock** is applied automatically while `show` is true, via
|
|
26
|
+
`useScrollLock` from `@vueuse/core`. SSR-safe: ref defaults to `false`
|
|
27
|
+
on the server.
|
|
28
|
+
- **Header/footer are auto-hidden** when no `title` prop and no
|
|
29
|
+
`#header`/`#footer` slot is present. The content section (`#default`)
|
|
30
|
+
always renders.
|
|
31
|
+
|
|
32
|
+
## Gotchas
|
|
33
|
+
|
|
34
|
+
- Multiple modals stacked at once will all lock body scroll; closing one
|
|
35
|
+
releases the lock for all. If you nest modals, manage the lock yourself.
|
|
36
|
+
- `origin`'s `width` / `height` should be the trigger's rendered size, not
|
|
37
|
+
the modal's. The animation derives the inverse scale from
|
|
38
|
+
`width / modalWidth` — wrong size = visible jump.
|
|
39
|
+
- The component uses inline styles via direct `.style.transform`
|
|
40
|
+
assignment on the wrapper ref. Do not animate `transform` from outside;
|
|
41
|
+
the component will overwrite it on the next open.
|
|
42
|
+
|
|
43
|
+
## Quick reference
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref } from "vue";
|
|
48
|
+
const open = ref(false);
|
|
49
|
+
const origin = ref<DOMRect | null>(null);
|
|
50
|
+
|
|
51
|
+
function trigger(event: MouseEvent) {
|
|
52
|
+
origin.value = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
53
|
+
open.value = true;
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<orio-button @click="trigger">Open settings</orio-button>
|
|
59
|
+
|
|
60
|
+
<orio-modal v-model:show="open" :origin="origin" title="Settings">
|
|
61
|
+
<p>Modal body…</p>
|
|
62
|
+
<template #footer>
|
|
63
|
+
<orio-button @click="open = false">Done</orio-button>
|
|
64
|
+
</template>
|
|
65
|
+
</orio-modal>
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Related
|
|
70
|
+
|
|
71
|
+
- `useModal` composable — programmatic open/close API without managing
|
|
72
|
+
`show` yourself. See `docs/composables/use-modal.md`.
|
|
@@ -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`.
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { computed, toRefs, useSlots } from "vue";
|
|
3
3
|
const props = defineProps({
|
|
4
4
|
icon: { type: String, required: false },
|
|
5
|
-
disabled: { type: Boolean, required: false },
|
|
6
5
|
active: { type: Boolean, required: false, default: false },
|
|
7
6
|
appearance: { type: String, required: false },
|
|
8
7
|
error: { type: [String, null], required: false },
|
|
@@ -11,7 +10,13 @@ const props = defineProps({
|
|
|
11
10
|
label: { type: String, required: false },
|
|
12
11
|
layout: { type: String, required: false },
|
|
13
12
|
size: { type: String, required: false },
|
|
14
|
-
fill: { type: Boolean, required: false }
|
|
13
|
+
fill: { type: Boolean, required: false },
|
|
14
|
+
tabindex: { type: [Number, String], required: false },
|
|
15
|
+
focusKey: { type: String, required: false },
|
|
16
|
+
disabled: { type: Boolean, required: false },
|
|
17
|
+
required: { type: Boolean, required: false },
|
|
18
|
+
name: { type: String, required: false },
|
|
19
|
+
ariaLabel: { type: String, required: false }
|
|
15
20
|
});
|
|
16
21
|
const { disabled, active } = toRefs(props);
|
|
17
22
|
const slots = useSlots();
|
|
@@ -28,11 +33,10 @@ function click(event) {
|
|
|
28
33
|
</script>
|
|
29
34
|
|
|
30
35
|
<template>
|
|
31
|
-
<orio-control-element v-bind="props">
|
|
36
|
+
<orio-control-element v-slot="{ control }" v-bind="props">
|
|
32
37
|
<button
|
|
33
|
-
v-bind="
|
|
38
|
+
v-bind="{ ...$attrs, ...control }"
|
|
34
39
|
:class="{ 'icon-only': isIconOnly, active }"
|
|
35
|
-
:disabled
|
|
36
40
|
:aria-current="active ? 'page' : void 0"
|
|
37
41
|
@click="click"
|
|
38
42
|
>
|
|
@@ -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.
|
|
@@ -6,14 +6,19 @@ defineProps({
|
|
|
6
6
|
max: { type: Number, required: false, default: void 0 },
|
|
7
7
|
step: { type: Number, required: false, default: 1 },
|
|
8
8
|
decimalPlaces: { type: Number, required: false, default: 0 },
|
|
9
|
-
disabled: { type: Boolean, required: false, default: false },
|
|
10
9
|
appearance: { type: String, required: false },
|
|
11
10
|
error: { type: [String, null], required: false },
|
|
12
11
|
group: { type: Boolean, required: false },
|
|
13
12
|
id: { type: String, required: false },
|
|
14
13
|
label: { type: String, required: false },
|
|
15
14
|
size: { type: String, required: false },
|
|
16
|
-
fill: { type: Boolean, required: false }
|
|
15
|
+
fill: { type: Boolean, required: false },
|
|
16
|
+
tabindex: { type: [Number, String], required: false },
|
|
17
|
+
focusKey: { type: String, required: false },
|
|
18
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
19
|
+
required: { type: Boolean, required: false },
|
|
20
|
+
name: { type: String, required: false },
|
|
21
|
+
ariaLabel: { type: String, required: false }
|
|
17
22
|
});
|
|
18
23
|
const modelValue = defineModel({ type: Number, ...{ default: 0 } });
|
|
19
24
|
const { pressAndHold, stop } = usePressAndHold();
|
|
@@ -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`.
|