orio-ui 1.27.0 → 1.28.1
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 +76 -1
- 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 +83 -0
- package/dist/runtime/components/Button.d.vue.ts +1 -0
- package/dist/runtime/components/Button.vue +5 -1
- package/dist/runtime/components/Button.vue.d.ts +1 -0
- package/dist/runtime/components/Calendar.USAGE.md +8 -0
- package/dist/runtime/components/Canvas/USAGE.md +8 -0
- package/dist/runtime/components/CheckBox.USAGE.md +63 -0
- package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
- package/dist/runtime/components/ControlElement.USAGE.md +8 -0
- package/dist/runtime/components/ControlElement.d.vue.ts +1 -1
- package/dist/runtime/components/ControlElement.vue.d.ts +1 -1
- 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 +8 -0
- 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 +8 -0
- package/dist/runtime/components/NavButton.USAGE.md +80 -0
- package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
- package/dist/runtime/components/NumberInput/Horizontal.vue +5 -0
- 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 +9 -1
- package/dist/runtime/components/Popover.USAGE.md +103 -0
- package/dist/runtime/components/RadioButton.USAGE.md +72 -0
- package/dist/runtime/components/Selector.USAGE.md +131 -0
- package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
- package/dist/runtime/components/Tag.USAGE.md +51 -0
- package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
- package/dist/runtime/components/Textarea.USAGE.md +72 -0
- 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 +8 -0
- package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
- package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
- 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/useControlSize.js +12 -0
- package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
- package/dist/runtime/composables/useFilter.USAGE.md +120 -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/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/package.json +12 -2
|
@@ -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.
|
|
@@ -31,6 +31,8 @@ const { pressAndHold, stop } = usePressAndHold();
|
|
|
31
31
|
appearance="minimal"
|
|
32
32
|
icon="chevron-up"
|
|
33
33
|
variant="subdued"
|
|
34
|
+
size="xs"
|
|
35
|
+
pill
|
|
34
36
|
:disabled="isAtMax || disabled"
|
|
35
37
|
@mousedown="pressAndHold(increase)"
|
|
36
38
|
@mouseup="stop"
|
|
@@ -40,6 +42,8 @@ const { pressAndHold, stop } = usePressAndHold();
|
|
|
40
42
|
appearance="minimal"
|
|
41
43
|
icon="chevron-down"
|
|
42
44
|
variant="subdued"
|
|
45
|
+
size="xs"
|
|
46
|
+
pill
|
|
43
47
|
:disabled="isAtMin || disabled"
|
|
44
48
|
@mousedown="pressAndHold(decrease)"
|
|
45
49
|
@mouseup="stop"
|
|
@@ -52,10 +56,14 @@ const { pressAndHold, stop } = usePressAndHold();
|
|
|
52
56
|
<style scoped>
|
|
53
57
|
.vertical :deep(.controls) {
|
|
54
58
|
flex-direction: column;
|
|
55
|
-
justify-content:
|
|
59
|
+
justify-content: center;
|
|
60
|
+
gap: 0;
|
|
56
61
|
right: 3px;
|
|
57
62
|
left: auto;
|
|
58
63
|
}
|
|
64
|
+
.vertical :deep(.controls button) {
|
|
65
|
+
padding-block: 0;
|
|
66
|
+
}
|
|
59
67
|
.vertical :deep(.slot-wrapper) {
|
|
60
68
|
line-height: 0;
|
|
61
69
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Layout & containers
|
|
4
|
+
purpose: popover, anchored floating panel, dropdown menu base, contextual menu
|
|
5
|
+
short: anchored floating panel teleported to body with auto-flip placement and click-outside dismissal
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Popover — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-popover>` anchors a floating panel to a trigger element. The trigger
|
|
12
|
+
is the default slot; the panel content is the `#content` slot.
|
|
13
|
+
|
|
14
|
+
## Invariants
|
|
15
|
+
|
|
16
|
+
- **Two slots, both get `{ toggle, isOpen }`.** Default slot wraps the
|
|
17
|
+
trigger; `#content` is the floating panel. You **must** call `toggle`
|
|
18
|
+
yourself — the component does not auto-attach a click handler to the
|
|
19
|
+
trigger.
|
|
20
|
+
- **Panel is teleported to `<body>`**. Parent CSS scoping does not reach
|
|
21
|
+
it. Use `:deep()` from a parent, global styles, or scope the panel via
|
|
22
|
+
classes you control.
|
|
23
|
+
- **`toggle(force?)`** — `toggle(true)` forces open, `toggle(false)` forces
|
|
24
|
+
close, `toggle()` flips. While `disabled` is true, all three are no-ops.
|
|
25
|
+
- **`position` syntax is `main-sub`, not "diagonal".** `top-left` means
|
|
26
|
+
*above the trigger* with the panel's **right edge** aligned to the
|
|
27
|
+
trigger's right edge — NOT "above and to the left of the trigger".
|
|
28
|
+
`left-top` means *left of the trigger* with **top** edges aligned.
|
|
29
|
+
Single-word values (`"top"`, `"left"`, …) center on the cross axis.
|
|
30
|
+
- **Placement auto-flips.** If the requested position doesn't fit in the
|
|
31
|
+
viewport, the component tries the opposite-main, then center variants,
|
|
32
|
+
then perpendicular axes — first fit wins. `currentPosition` is the
|
|
33
|
+
resolved value. No way to disable the fallback.
|
|
34
|
+
- **`offset` is gap in px between trigger and panel.** Default `10`.
|
|
35
|
+
- **Click outside closes**, with `triggerRef` in the ignore list.
|
|
36
|
+
- **Reposition on scroll/resize** uses capture-phase listeners on `window`,
|
|
37
|
+
so it catches nested scrolling ancestors. Panel resizes via
|
|
38
|
+
`useElementBounding` also trigger reposition.
|
|
39
|
+
- **No keyboard support.** No Esc-to-close, no focus trap, no return-focus.
|
|
40
|
+
If you need those, wire them at the consumer level on the `#content`
|
|
41
|
+
slot.
|
|
42
|
+
- **No visual chrome.** The wrapper is `background: transparent; border: 0;
|
|
43
|
+
position: fixed; z-index: 999999`. The `#content` slot must render its
|
|
44
|
+
own surface (card, panel, menu).
|
|
45
|
+
|
|
46
|
+
## Gotchas
|
|
47
|
+
|
|
48
|
+
- **Trigger must accept the slot props.** Use `v-slot="{ toggle, isOpen }"`
|
|
49
|
+
on the default slot — without it, the trigger has no way to open the
|
|
50
|
+
popover.
|
|
51
|
+
- **No arrow / caret.** Add your own pseudo-element on the content slot if
|
|
52
|
+
needed; the wrapper offers no anchor point.
|
|
53
|
+
- **First-paint flicker is avoided via `visibility: hidden`** during
|
|
54
|
+
measurement, but only briefly. If your trigger animates while opening,
|
|
55
|
+
the trigger rect read on `nextTick` may be mid-animation — measure after
|
|
56
|
+
the animation, or open without animating the trigger.
|
|
57
|
+
- **`appear` transition fires only on first paint.** Reopening the same
|
|
58
|
+
popover does not re-trigger `appear` — only the standard enter
|
|
59
|
+
transition. Make sure your `animate-fade-slide` styles cover both.
|
|
60
|
+
- **z-index is hard-coded to `999999`.** Stacking with other body-teleports
|
|
61
|
+
(Modal at the same z-index) is order-dependent — the later-mounted
|
|
62
|
+
element wins. Mount-order is not stable across HMR.
|
|
63
|
+
- **No `v-model:show`.** State is internal. Drive it through `toggle` (via
|
|
64
|
+
the slot prop) or by mounting/unmounting the component with `v-if`.
|
|
65
|
+
|
|
66
|
+
## Quick reference
|
|
67
|
+
|
|
68
|
+
```vue
|
|
69
|
+
<template>
|
|
70
|
+
<orio-popover position="bottom-left" :offset="8">
|
|
71
|
+
<template #default="{ toggle, isOpen }">
|
|
72
|
+
<orio-button @click="toggle()">
|
|
73
|
+
Menu {{ isOpen ? "▴" : "▾" }}
|
|
74
|
+
</orio-button>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<template #content="{ toggle }">
|
|
78
|
+
<div class="menu-panel">
|
|
79
|
+
<button @click="onEdit(); toggle(false)">Edit</button>
|
|
80
|
+
<button @click="onDelete(); toggle(false)">Delete</button>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
</orio-popover>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<style scoped>
|
|
87
|
+
.menu-panel {
|
|
88
|
+
background: var(--color-surface);
|
|
89
|
+
border: 1px solid var(--color-border);
|
|
90
|
+
border-radius: var(--border-radius-md);
|
|
91
|
+
padding: 0.5rem;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Related
|
|
99
|
+
|
|
100
|
+
- `<orio-tooltip>` — for hover/focus hints; do not use Popover for tooltips.
|
|
101
|
+
- `<orio-modal>` — when you need a backdrop, focus trap, and centered
|
|
102
|
+
dialog.
|
|
103
|
+
- Public API reference: `docs/components/popover.md`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Form inputs
|
|
4
|
+
purpose: radio, radio button, single-choice from group
|
|
5
|
+
short: single radio option wrapping ControlElement; group by sharing the same v-model
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# RadioButton — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-radio-button>` is one radio option. There is **no `RadioGroup`** —
|
|
12
|
+
grouping is done by sharing the same v-model across multiple instances.
|
|
13
|
+
|
|
14
|
+
## Invariants
|
|
15
|
+
|
|
16
|
+
- **`value` is the option's payload.** It is what gets written to v-model
|
|
17
|
+
when this radio is selected. Comparison is strict (`===`).
|
|
18
|
+
- **v-model is `unknown`** (typically `string` / `number` / object id) —
|
|
19
|
+
the selected value. Two `<orio-radio-button>`s with the same v-model
|
|
20
|
+
form a group; whichever value matches the model is checked.
|
|
21
|
+
- **`text` prop _or_ default slot** for the inline label. Slot wins.
|
|
22
|
+
Render nothing if both are absent.
|
|
23
|
+
- **`hideLabel` prop** applies `.sr-only` to the text — visually hidden,
|
|
24
|
+
still announced by screen readers. Use for icon-only radios that still
|
|
25
|
+
need an accessible name.
|
|
26
|
+
- **Visual is a CSS-only rounded box** with `::after` for the inner dot
|
|
27
|
+
when checked.
|
|
28
|
+
- **Native `<input type="radio">` has `tabindex="-1"`** in the template.
|
|
29
|
+
Default tab order skips it; clicking the label still toggles. If you
|
|
30
|
+
need keyboard arrow-key roving across the group, layer that on top —
|
|
31
|
+
it is not provided.
|
|
32
|
+
- **No `name` HTML attribute by default.** Two browser radios with the
|
|
33
|
+
same DOM `name` form a native group; this component groups via Vue
|
|
34
|
+
v-model only. Set `name` via `$attrs` if a `<form>` submission needs
|
|
35
|
+
the radio group serialized natively.
|
|
36
|
+
|
|
37
|
+
## Gotchas
|
|
38
|
+
|
|
39
|
+
- **Multiple instances bound to the same v-model are not visually grouped.**
|
|
40
|
+
Layout (column / row) is up to the consumer — wrap them in a div or
|
|
41
|
+
`<orio-control-element group>` with a shared label.
|
|
42
|
+
- **Object values must be the same reference** as the one stored in the
|
|
43
|
+
model. Comparing `{ id: 1 }` to a new `{ id: 1 }` is false. Bind to
|
|
44
|
+
primitive ids when possible.
|
|
45
|
+
- **No "deselect" behavior.** Once a radio is selected, clicking it again
|
|
46
|
+
does not clear. To allow clearing, expose a separate "None" radio with
|
|
47
|
+
`value: null`.
|
|
48
|
+
- **`required` from `ControlProps`** flows through but only takes effect
|
|
49
|
+
inside a `<form>` that calls `reportValidity`.
|
|
50
|
+
|
|
51
|
+
## Quick reference
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
const plan = defineModel<"basic" | "pro" | "team">();
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<orio-control-element group :label="$t('billing.plan')">
|
|
60
|
+
<orio-radio-button v-model="plan" value="basic" :text="$t('billing.basic')" />
|
|
61
|
+
<orio-radio-button v-model="plan" value="pro" :text="$t('billing.pro')" />
|
|
62
|
+
<orio-radio-button v-model="plan" value="team" :text="$t('billing.team')" />
|
|
63
|
+
</orio-control-element>
|
|
64
|
+
</template>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Related
|
|
68
|
+
|
|
69
|
+
- `<orio-control-element>` (group mode) — wrap multiple radios for an
|
|
70
|
+
accessible group label.
|
|
71
|
+
- `<orio-switch-button>` — boolean on/off pill button.
|
|
72
|
+
- Public API reference: `docs/components/radio-button.md`.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Form inputs
|
|
4
|
+
purpose: select, dropdown, combobox, listbox picker, single or multi-select
|
|
5
|
+
short: button-triggered listbox in a popover; supports single and multi-select with string or object options
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Selector — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-selector>` is a button-triggered listbox rendered inside an
|
|
12
|
+
`<orio-popover>`. It is **not** a `<select>` element and **not** a
|
|
13
|
+
combobox — there is no text input or filter. For free-text + filter,
|
|
14
|
+
combine with `useFuzzySearch` and slot `#options-addon`. Generic over
|
|
15
|
+
`T extends object`.
|
|
16
|
+
|
|
17
|
+
## Invariants
|
|
18
|
+
|
|
19
|
+
- **`options` accepts `string` or object items** (typed
|
|
20
|
+
`SelectableOption<T> = string | T`). Mixing is allowed but uncommon —
|
|
21
|
+
treat the array as homogenous.
|
|
22
|
+
- **For object options, set `field`** (defaults to `"id"`) to the
|
|
23
|
+
uniqueness key, and **`optionName`** to the display label key. Without
|
|
24
|
+
`optionName`, objects render as `JSON.stringify(option)` — visible bug.
|
|
25
|
+
- **v-model is required.** Type: `SelectableOption | SelectableOption[] |
|
|
26
|
+
null | undefined`. Single-select binds the option (or its primitive);
|
|
27
|
+
multi-select binds an array.
|
|
28
|
+
- **`multiple: true` mutates the bound array in place** via
|
|
29
|
+
`modelValue.value.splice(...)` / `modelValue.value.push(...)`. The bound
|
|
30
|
+
ref must be a writable array — a deep `readonly` v-model blocks those
|
|
31
|
+
mutations: Vue warns in dev (`Set operation on key … failed: target is
|
|
32
|
+
readonly`) and fails silently in prod, so the selection won't update.
|
|
33
|
+
- **Single-select closes the popover on choice**; multi-select keeps it
|
|
34
|
+
open. Wire your own close on multi-select via the `option` slot
|
|
35
|
+
`toggle` prop if needed.
|
|
36
|
+
- **Built on `<orio-popover>` with `position="bottom-right"` offset 5**.
|
|
37
|
+
Same teleport/scroll/auto-flip caveats apply.
|
|
38
|
+
- **Built on `<orio-list-item>` rows** with `role="option"` and
|
|
39
|
+
`aria-selected`. Listbox itself has `role="listbox"` and
|
|
40
|
+
`aria-multiselectable` when `multiple` is set.
|
|
41
|
+
- **Keyboard via `useListKeyboard`**: arrow-down opens (or moves
|
|
42
|
+
highlight), Enter selects, Esc closes. `popoverToggleRef` is captured
|
|
43
|
+
lazily from the trigger slot — the first keydown after mount may
|
|
44
|
+
no-op if the popover hasn't initialized; subsequent keys work.
|
|
45
|
+
- **i18n keys are required.** The component calls `useI18n()` and
|
|
46
|
+
references `selector.placeholder`, `selector.selected` ({count}), and
|
|
47
|
+
`selector.noOptions`. Consumers must have these keys in their locale
|
|
48
|
+
files.
|
|
49
|
+
- **`useControlTokens(size)`** injects CSS vars (`--control-py`,
|
|
50
|
+
`--control-px`, etc.) onto the popover content — size prop on the
|
|
51
|
+
Selector flows through to dropdown padding.
|
|
52
|
+
|
|
53
|
+
## Slots
|
|
54
|
+
|
|
55
|
+
- `#trigger` — replaces the entire button. Receives `{ toggle, control }`.
|
|
56
|
+
- `#trigger-content` — replaces the *inside* of the default button.
|
|
57
|
+
Receives `{ toggle, getOptionKey, getOptionLabel, attrs }`. Use to
|
|
58
|
+
customize the label/chevron without rebuilding the button shell.
|
|
59
|
+
- `#trigger-label` — replaces only the label text. Receives same props.
|
|
60
|
+
Use to render tags for multi-select instead of "N selected".
|
|
61
|
+
- `#option` — replaces each row's content. Receives `{ option, toggle,
|
|
62
|
+
selected, getOptionKey, getOptionLabel }`.
|
|
63
|
+
- `#no-options` — replaces the default `<orio-empty-state>` when
|
|
64
|
+
`options.length === 0`.
|
|
65
|
+
- `#options-addon` — extra content rendered **after** the list (e.g. a
|
|
66
|
+
"create new" button, a search input).
|
|
67
|
+
|
|
68
|
+
## Gotchas
|
|
69
|
+
|
|
70
|
+
- **No built-in search.** For filterable selectors, render an
|
|
71
|
+
`<orio-input>` in `#options-addon` and pass a filtered array to
|
|
72
|
+
`:options`.
|
|
73
|
+
- **Multi-select trigger shows "N selected" by default.** Override via
|
|
74
|
+
`#trigger-label` to render tags — consider `<orio-tag>` chips. Removal
|
|
75
|
+
must call `toggleOption` (exposed via `#option` slot, not the trigger).
|
|
76
|
+
- **String options bypass `field`/`optionName`.** Selection equality is
|
|
77
|
+
`===`. Object options compare via `field`.
|
|
78
|
+
- **`placeholder` falls back to `t("selector.placeholder")`**. Pass an
|
|
79
|
+
explicit `placeholder` to override per-instance; do not assume English.
|
|
80
|
+
- **`controlProps` strip is exhaustive** — `options`, `multiple`,
|
|
81
|
+
`field`, `optionName`, `placeholder` never reach the ControlElement
|
|
82
|
+
wrapper. Adding new selector-specific props requires adding them to the
|
|
83
|
+
strip list.
|
|
84
|
+
- **Trigger is a `<button>`**, not a real `<select>`. Form serialization
|
|
85
|
+
and native submission do not include the value — handle submit
|
|
86
|
+
manually.
|
|
87
|
+
|
|
88
|
+
## Quick reference — single-select with object options
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
interface Country { id: string; name: string }
|
|
93
|
+
const countries: Country[] = [
|
|
94
|
+
{ id: "uk", name: "United Kingdom" },
|
|
95
|
+
{ id: "fi", name: "Finland" },
|
|
96
|
+
];
|
|
97
|
+
const country = defineModel<Country | null>({ default: null });
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<orio-selector
|
|
102
|
+
v-model="country"
|
|
103
|
+
:options="countries"
|
|
104
|
+
field="id"
|
|
105
|
+
option-name="name"
|
|
106
|
+
:label="$t('settings.country')"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Quick reference — multi-select with tag chips
|
|
112
|
+
|
|
113
|
+
```vue
|
|
114
|
+
<template>
|
|
115
|
+
<orio-selector v-model="tags" :options="allTags" multiple field="id" option-name="label">
|
|
116
|
+
<template #trigger-label>
|
|
117
|
+
<orio-tag v-for="tag in tags" :key="tag.id" removable @remove="removeTag(tag)">
|
|
118
|
+
{{ tag.label }}
|
|
119
|
+
</orio-tag>
|
|
120
|
+
</template>
|
|
121
|
+
</orio-selector>
|
|
122
|
+
</template>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- `<orio-taggable-selector>` — pre-built multi-select with tag chips.
|
|
128
|
+
- `<orio-list-item>` — the row primitive used inside the listbox.
|
|
129
|
+
- `useListKeyboard` — keyboard nav composable.
|
|
130
|
+
- `useFuzzySearch` — pair with `#options-addon` for a search filter.
|
|
131
|
+
- Public API reference: `docs/components/selector.md`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Form inputs
|
|
4
|
+
purpose: toggle, on/off switch, pill toggle, boolean button
|
|
5
|
+
short: boolean on/off pill button (not a sliding switch) wrapping ControlElement; click/Enter/Space toggle
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SwitchButton — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-switch-button>` is a **pill-shaped button** that toggles a boolean.
|
|
12
|
+
Despite the name it is **not** a sliding toggle switch — it is a chip-style
|
|
13
|
+
button that flips between active and inactive states.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **Renders a `<button type=...>` element**, not a checkbox or sliding
|
|
18
|
+
knob. The "switch" is purely visual state via the `.active` class.
|
|
19
|
+
- **v-model is `boolean`** (not required — renders as off when unbound).
|
|
20
|
+
- **Click, Enter, and Space all toggle.** `Enter` and `Space` use
|
|
21
|
+
`.prevent` to avoid form submit / page scroll.
|
|
22
|
+
- **`disabled` blocks toggling** and applies `.disabled` styles (0.5
|
|
23
|
+
opacity, `cursor: not-allowed`). Keyboard activation is also gated.
|
|
24
|
+
- **Default slot is the button content** (label, icon, or both). Visual
|
|
25
|
+
active state changes background/border/color; the slot does not
|
|
26
|
+
receive any reactive prop.
|
|
27
|
+
- **Wraps `<orio-control-element>`** — supports the standard `label`,
|
|
28
|
+
`error`, `layout`, `size`, etc. The control bag is spread on the
|
|
29
|
+
inner `<button>` along with `$attrs`.
|
|
30
|
+
|
|
31
|
+
## Gotchas
|
|
32
|
+
|
|
33
|
+
- **The component name is misleading.** If you want a sliding toggle
|
|
34
|
+
switch (knob that animates), this is not it. Build that yourself or
|
|
35
|
+
pick another primitive.
|
|
36
|
+
- **No `aria-pressed`.** The active state is visual only. For correct
|
|
37
|
+
screen reader semantics, pass `:aria-pressed="modelValue"` via
|
|
38
|
+
`$attrs`.
|
|
39
|
+
- **`@keydown.enter.prevent`** swallows form-submit Enter inside a
|
|
40
|
+
`<form>`. If the SwitchButton is inside a form, Enter on it will
|
|
41
|
+
toggle but not submit.
|
|
42
|
+
|
|
43
|
+
## Quick reference
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<template>
|
|
47
|
+
<orio-switch-button
|
|
48
|
+
v-model="notifications"
|
|
49
|
+
:label="$t('settings.notifications')"
|
|
50
|
+
:aria-pressed="notifications"
|
|
51
|
+
>
|
|
52
|
+
<orio-icon :name="notifications ? 'bell' : 'bell-off'" />
|
|
53
|
+
{{ notifications ? $t("common.on") : $t("common.off") }}
|
|
54
|
+
</orio-switch-button>
|
|
55
|
+
</template>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Related
|
|
59
|
+
|
|
60
|
+
- `<orio-check-box>` — when you want a real checkbox.
|
|
61
|
+
- `<orio-radio-button>` — single-choice from a group.
|
|
62
|
+
- Public API reference: `docs/components/switch-button.md`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Buttons & indicators
|
|
4
|
+
purpose: tag, chip, label, removable chip, category pill
|
|
5
|
+
short: small text chip with neutral or accent variant; static display only (no remove behavior)
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Tag — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-tag>` is a static text chip. No close button, no click handler, no
|
|
12
|
+
removal behavior — display only.
|
|
13
|
+
|
|
14
|
+
## Invariants
|
|
15
|
+
|
|
16
|
+
- **`text` is required.** It is the only way to set the chip's visible
|
|
17
|
+
content — there is no default slot.
|
|
18
|
+
- **`variant`**: `"neutral"` (default, gray) or `"accent"` (themed
|
|
19
|
+
accent). No `danger` / `alert` colors here — use `<orio-badge>` for
|
|
20
|
+
that.
|
|
21
|
+
- **`id` is an optional uniqueness key** used by upstream components
|
|
22
|
+
(`<orio-taggable-selector>`) to match selected tags. Pass it whenever
|
|
23
|
+
the tag participates in selection state.
|
|
24
|
+
- **`user-select: none`** — text inside the chip cannot be selected with
|
|
25
|
+
the cursor.
|
|
26
|
+
|
|
27
|
+
## Gotchas
|
|
28
|
+
|
|
29
|
+
- **No remove / close button.** The `TagProps` shape is intentionally
|
|
30
|
+
passive. For removable chips, wrap a custom button next to it or build
|
|
31
|
+
your own primitive.
|
|
32
|
+
- **No icon support.** No `icon` prop, no slot. For icon+text chips,
|
|
33
|
+
build directly from a `<span>` with `<orio-icon>`.
|
|
34
|
+
- **No size variants.** Padding (`0.25rem 0.6rem`) and font size
|
|
35
|
+
(`--font-sm`) are fixed.
|
|
36
|
+
|
|
37
|
+
## Quick reference
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<template>
|
|
41
|
+
<orio-tag :text="$t('category.urgent')" variant="accent" />
|
|
42
|
+
<orio-tag :text="$t('category.draft')" />
|
|
43
|
+
</template>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Related
|
|
47
|
+
|
|
48
|
+
- `<orio-taggable-selector>` — uses `TagProps` as its option shape.
|
|
49
|
+
- `<orio-badge>` — when you need a status pill with semantic color
|
|
50
|
+
variants.
|
|
51
|
+
- Public API reference: `docs/components/tag.md`.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Form inputs
|
|
4
|
+
purpose: multi-select with tag chips, taggable selector, chip picker
|
|
5
|
+
short: multi-select Selector that renders chosen options as tag chips in the trigger
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# TaggableSelector — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-taggable-selector>` is a thin wrapper over `<orio-selector>` that
|
|
12
|
+
forces `multiple` mode and renders the selection as a row of `<orio-tag>`
|
|
13
|
+
chips inside the trigger. Read `Selector.USAGE.md` for the full contract.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **Always multi-select.** The internal `<orio-selector>` is passed
|
|
18
|
+
`multiple` unconditionally; the prop cannot be turned off.
|
|
19
|
+
- **`optionName` defaults to `"text"`** to align with `TagProps.text`. If
|
|
20
|
+
your option type uses a different label key, pass `option-name`.
|
|
21
|
+
- **v-model type is `TagProps[]`** — `{ id?: string; text: string;
|
|
22
|
+
variant?: "neutral" | "accent" }[]`. Options must be tag-shaped or
|
|
23
|
+
augmented to satisfy this contract.
|
|
24
|
+
- **Tags in the trigger are display-only.** Clicking a chip does **not**
|
|
25
|
+
remove the option. The user must reopen the dropdown and uncheck it.
|
|
26
|
+
This is a real usability tradeoff — for click-to-remove, fall back to
|
|
27
|
+
`<orio-selector>` with a custom `#trigger-label`.
|
|
28
|
+
- **Trigger lays chips out as `flex-wrap`** with `0.5rem` gap, left-aligned.
|
|
29
|
+
Selecting many chips grows the trigger height; constrain it if your
|
|
30
|
+
layout depends on a fixed height.
|
|
31
|
+
|
|
32
|
+
## Gotchas
|
|
33
|
+
|
|
34
|
+
- **`field` (uniqueness key) still defaults to `"id"`** from the underlying
|
|
35
|
+
Selector. Each tag should have a stable `id` — without one, multi-select
|
|
36
|
+
add/remove will mis-match items with identical `text`.
|
|
37
|
+
- **No empty-state placeholder by default in the trigger.** When the
|
|
38
|
+
selection is empty, the trigger is empty space. Pass a `placeholder` so
|
|
39
|
+
the dropdown shows a hint, but the trigger label itself will not show
|
|
40
|
+
it (the `#trigger-label` slot renders an empty chip list).
|
|
41
|
+
- **Variant per chip is per-option.** Set `variant: "accent"` on individual
|
|
42
|
+
option objects to tint specific chips.
|
|
43
|
+
|
|
44
|
+
## Quick reference
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import type { TagProps } from "../components/Tag.vue";
|
|
49
|
+
|
|
50
|
+
const allCategories: TagProps[] = [
|
|
51
|
+
{ id: "fiction", text: "Fiction" },
|
|
52
|
+
{ id: "non-fiction", text: "Non-fiction", variant: "accent" },
|
|
53
|
+
{ id: "poetry", text: "Poetry" },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const selected = defineModel<TagProps[]>({ default: () => [] });
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<orio-taggable-selector
|
|
61
|
+
v-model="selected"
|
|
62
|
+
:options="allCategories"
|
|
63
|
+
:label="$t('book.categories')"
|
|
64
|
+
/>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Related
|
|
69
|
+
|
|
70
|
+
- `<orio-selector>` — base; use with custom `#trigger-label` for
|
|
71
|
+
removable chips or different layouts.
|
|
72
|
+
- `<orio-tag>` — the chip primitive rendered in the trigger.
|
|
73
|
+
- Public API reference: `docs/components/taggable-selector.md`.
|