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.
- 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 +78 -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/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/USAGE.md +74 -0
- package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
- 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/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,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Layout & containers
|
|
4
|
+
purpose: pinch/scroll zoom viewport, pan-zoom canvas, infinite board, image inspector
|
|
5
|
+
short: pan + pinch/wheel zoom viewport with inertia, momentum, space-to-grab and bounds clamping
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ZoomableContainer — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-zoomable-container>` is a pan-zoom viewport that transforms a "world"
|
|
12
|
+
(its slot) inside a fixed-size viewport. Pinch on touch, ctrl/cmd+wheel on
|
|
13
|
+
mouse, drag-to-pan with space-or-middle-button.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **Viewport sizes to its parent.** The viewport is `width: 100%; height:
|
|
18
|
+
100%`. **A parent with explicit dimensions is required** — without it,
|
|
19
|
+
the viewport collapses to 0 and nothing is visible.
|
|
20
|
+
- **World is the slot.** It is `position: absolute; transform-origin: 0 0`.
|
|
21
|
+
The container measures it via `ResizeObserver`, so the slot can be any
|
|
22
|
+
size — fixed, content-driven, or dynamic.
|
|
23
|
+
- **First-mount auto-center is one-shot.** Once viewport and world both
|
|
24
|
+
have non-zero dimensions, the world centers exactly once. Later size
|
|
25
|
+
changes call `applyBounds` only — they do **not** re-center. Use the
|
|
26
|
+
exposed `centerWorld()` to re-center on demand.
|
|
27
|
+
- **Drag-to-pan needs space, middle button, OR clicking the viewport background.**
|
|
28
|
+
Pointer-down on slot content does **not** pan by default. Hold `Space`
|
|
29
|
+
(cursor becomes `grab`/`grabbing`) or click an area outside the world to
|
|
30
|
+
pan. This is `shouldPan(e)` — confirm interactive children inside the
|
|
31
|
+
slot stop propagation if needed.
|
|
32
|
+
- **Touch gestures use `usePinchZoom`**, mouse/pen uses pointer-capture
|
|
33
|
+
drag. Wheel does pan; **ctrl/cmd + wheel** zooms at cursor; **shift +
|
|
34
|
+
vertical wheel** pans horizontally.
|
|
35
|
+
- **`v-model:scale` works; `v-model:translate` does NOT.** `update:scale`
|
|
36
|
+
emits one value, but `update:translate` emits `(x, y)` as two args — not
|
|
37
|
+
a tuple. Bind a callback to read translate, or read it via `ref` on the
|
|
38
|
+
exposed `tx`/`ty`.
|
|
39
|
+
- **Exposed methods (via `defineExpose`)**: `scale`, `tx`, `ty`,
|
|
40
|
+
`setScaleAt(target, px, py)`, `panBy(dx, dy)`, `resetView()`,
|
|
41
|
+
`centerWorld()`. Get a `ref` on the component to call them.
|
|
42
|
+
- **Pan bounds let the world drift halfway off either edge.** Clamp is
|
|
43
|
+
`tx ∈ [vw/2 − worldW, vw/2]`, so the world can move until its trailing
|
|
44
|
+
edge reaches viewport center. Intentional — keeps something always
|
|
45
|
+
reachable.
|
|
46
|
+
- **`touch-action: none` and `user-select: none`** are set on the viewport.
|
|
47
|
+
Children inside cannot select text or trigger native touch scrolling.
|
|
48
|
+
- **Context menu is suppressed** inside the viewport (`@contextmenu.prevent`).
|
|
49
|
+
- **Global keydown listener for Space.** `useEventListener("keydown", ...)`
|
|
50
|
+
binds to `document`, so holding space in a text input elsewhere on the
|
|
51
|
+
page still flips `spaceHeld` — be aware when composing with form inputs.
|
|
52
|
+
|
|
53
|
+
## Gotchas
|
|
54
|
+
|
|
55
|
+
- **Slot prop shape**: `<template v-slot="{ scale, tx, ty }">`. Use these
|
|
56
|
+
for overlays that need to track the world transform (rulers, minimaps).
|
|
57
|
+
- **No `v-model:translate` shortcut.** Wire it as
|
|
58
|
+
`@update:translate="(x, y) => { ... }"`.
|
|
59
|
+
- **Pinch zoom resets `dragId` on `onPinchStart`** to cancel any in-flight
|
|
60
|
+
single-pointer drag. If you mix touch + pen on a hybrid device, the drag
|
|
61
|
+
state can drop mid-gesture.
|
|
62
|
+
- **`zoomSpeed` is exponential**, not linear. `factor = exp(-deltaY *
|
|
63
|
+
zoomSpeed)`. Default `0.0015` is tuned for typical mousewheel deltas;
|
|
64
|
+
trackpad wheel events with tiny deltas barely zoom — bump to ~0.005 if
|
|
65
|
+
your audience is trackpad-heavy.
|
|
66
|
+
- **`minScale` / `maxScale` are clamped, not normalized.** Setting
|
|
67
|
+
`initialScale` outside the range still applies the clamp on the first
|
|
68
|
+
zoom interaction.
|
|
69
|
+
|
|
70
|
+
## Quick reference
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<script setup lang="ts">
|
|
74
|
+
import { ref, useTemplateRef } from "vue";
|
|
75
|
+
|
|
76
|
+
const board = useTemplateRef<{ resetView: () => void; centerWorld: () => void }>("board");
|
|
77
|
+
const scale = ref(1);
|
|
78
|
+
const translate = ref({ x: 0, y: 0 });
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div style="width: 100%; height: 80vh">
|
|
83
|
+
<orio-zoomable-container
|
|
84
|
+
ref="board"
|
|
85
|
+
v-model:scale="scale"
|
|
86
|
+
:min-scale="0.25"
|
|
87
|
+
:max-scale="4"
|
|
88
|
+
@update:translate="(x, y) => (translate = { x, y })"
|
|
89
|
+
>
|
|
90
|
+
<template #default="{ scale: worldScale }">
|
|
91
|
+
<div class="board-world" :style="{ width: '2000px', height: '1500px' }">
|
|
92
|
+
<p>Zoom: {{ worldScale.toFixed(2) }}×</p>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
</orio-zoomable-container>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<orio-button @click="board?.resetView()">Reset</orio-button>
|
|
99
|
+
</template>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Related
|
|
103
|
+
|
|
104
|
+
- `usePinchZoom` — pinch gesture composable used internally.
|
|
105
|
+
- `useInertia` — momentum/decay used for release-after-drag.
|
|
106
|
+
- `<orio-canvas>` — when you need a tool-driven editor, not just a pan-zoom
|
|
107
|
+
viewport. Canvas is built on the same gestures with extras.
|
|
108
|
+
- Public API reference: `docs/components/zoomable-container.md`.
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Date
|
|
4
|
+
purpose: date input, single date picker, "pick a date"
|
|
5
|
+
short: single date picker built from Calendar plus PickerTrigger
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
# date/Picker — agent-only invariants
|
|
2
10
|
|
|
3
11
|
`<orio-date-picker>` is the single-date picker: a `<orio-date-picker-trigger>`
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Date
|
|
4
|
+
purpose: date picker trigger button, date input button, popover-anchored date trigger
|
|
5
|
+
short: shared button + popover trigger used by date Picker and RangePicker; default slot renders the picker body
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# date/PickerTrigger — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-date-picker-trigger>` is the trigger button shared by
|
|
12
|
+
`<orio-date-picker>` and `<orio-date-range-picker>`. You usually do not
|
|
13
|
+
use it directly — pick those higher-level pickers unless you are building
|
|
14
|
+
a new date primitive.
|
|
15
|
+
|
|
16
|
+
## Invariants
|
|
17
|
+
|
|
18
|
+
- **Renders a `<button>` inside `<orio-control-element>` and an
|
|
19
|
+
`<orio-popover>`** with `position="bottom-right"`, `offset: 5`.
|
|
20
|
+
- **`text` prop is the visible display text.** When empty, `placeholder`
|
|
21
|
+
renders muted.
|
|
22
|
+
- **`text` and `placeholder` are stripped from `controlProps`** — they
|
|
23
|
+
don't leak to the ControlElement wrapper.
|
|
24
|
+
- **Default slot is the popover content** and receives `{ toggle }`. Call
|
|
25
|
+
`toggle(false)` to close after a user picks.
|
|
26
|
+
- **Calendar icon (`name="calendar"`) is hardcoded** on the right of the
|
|
27
|
+
button. No prop to swap it.
|
|
28
|
+
- **`aria-expanded` reflects popover state** for screen reader support.
|
|
29
|
+
- **Inherits all ControlElement contract**: `label`, `error`, `size`,
|
|
30
|
+
`layout`, etc. The control bag is bound to the inner `<button>`.
|
|
31
|
+
|
|
32
|
+
## Gotchas
|
|
33
|
+
|
|
34
|
+
- **Not for general "click-to-open" needs.** For a non-date trigger, use
|
|
35
|
+
`<orio-popover>` directly — this one is calendar-themed (icon, padding,
|
|
36
|
+
i18n placeholder).
|
|
37
|
+
- **No multi-popover stacking story.** Both single and range pickers use
|
|
38
|
+
this same component, with the same `bottom-right` placement. Side-by-
|
|
39
|
+
side pickers may collide.
|
|
40
|
+
|
|
41
|
+
## Quick reference
|
|
42
|
+
|
|
43
|
+
You normally consume this through `<orio-date-picker>` or
|
|
44
|
+
`<orio-date-range-picker>`. Direct use:
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<template>
|
|
48
|
+
<orio-date-picker-trigger
|
|
49
|
+
:text="display"
|
|
50
|
+
:placeholder="$t('date.placeholder')"
|
|
51
|
+
:label="$t('date.label')"
|
|
52
|
+
>
|
|
53
|
+
<template #default="{ toggle }">
|
|
54
|
+
<orio-calendar v-model:anchor="anchor" @select="onSelect($event, toggle)" />
|
|
55
|
+
</template>
|
|
56
|
+
</orio-date-picker-trigger>
|
|
57
|
+
</template>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Related
|
|
61
|
+
|
|
62
|
+
- `<orio-date-picker>` — single-date picker built on this trigger.
|
|
63
|
+
- `<orio-date-range-picker>` — range picker built on this trigger.
|
|
64
|
+
- `<orio-popover>` — for non-date trigger needs.
|
|
65
|
+
- Public API reference: `docs/components/date/`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Date
|
|
4
|
+
purpose: date range, from-to picker, date range input, calendar range
|
|
5
|
+
short: two-month range picker with hover-preview, min/max bounds, and ISO `{ start, end }` model
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# date/RangePicker — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-date-range-picker>` is the date range picker: a
|
|
12
|
+
`<orio-date-picker-trigger>` opens a popover containing **two side-by-side
|
|
13
|
+
calendars** (left = start month, right = start month + 1). Read
|
|
14
|
+
`Calendar.USAGE.md` and `date/Picker.USAGE.md` first.
|
|
15
|
+
|
|
16
|
+
## Invariants
|
|
17
|
+
|
|
18
|
+
- **v-model is `DateRange = { start: string | null; end: string | null }`**
|
|
19
|
+
(ISO date strings). `DateRange` is re-exported from this file. Both
|
|
20
|
+
fields can be null (no selection) or just `start` (mid-pick).
|
|
21
|
+
- **Click sequence is "set start → set end".** First click clears `end`,
|
|
22
|
+
writes `start`. Second click writes `end`. If the second pick is
|
|
23
|
+
earlier than the existing start, start and end **swap automatically**.
|
|
24
|
+
- **The popover closes on range completion** (second pick), via
|
|
25
|
+
`toggle(false)`. A single click leaves it open.
|
|
26
|
+
- **Two-month anchored display** — `leftAnchor` = first of start's month,
|
|
27
|
+
`rightAnchor` = `leftAnchor + 1 month`. They re-sync when the model's
|
|
28
|
+
start changes to a month not currently visible.
|
|
29
|
+
- **Hover preview**: hovering a day in the popover renders an "accent"
|
|
30
|
+
range marker from `start` to the hovered day (or hovered day to start
|
|
31
|
+
if the hover is earlier). Requires `start` to be already picked.
|
|
32
|
+
- **`min` / `max` are ISO strings** that gate selection via the calendar's
|
|
33
|
+
`isDisabled`. Consumer's own `isDisabled(iso)` predicate ORs in.
|
|
34
|
+
- **`getMarker(iso)` is the consumer's marker provider.** The preview
|
|
35
|
+
marker **wins** over consumer markers for days inside the previewed
|
|
36
|
+
range.
|
|
37
|
+
- **Built on `<orio-calendar>` × 2**, side-by-side in a flex row with
|
|
38
|
+
0.75rem gap.
|
|
39
|
+
- **i18n key**: `dateRangePicker.placeholder` for the empty-display label.
|
|
40
|
+
- **Display string**: `"start – end"` when both exist (en-dash, spaces).
|
|
41
|
+
Just `start` or just `end` if only one. Empty string if both null.
|
|
42
|
+
|
|
43
|
+
## Gotchas
|
|
44
|
+
|
|
45
|
+
- **The picked `start` does NOT render a marker by itself** when no `end`
|
|
46
|
+
and no hover — `previewMarker` requires both `previewStart` and
|
|
47
|
+
`previewEnd`. There is no visible feedback that a start was picked
|
|
48
|
+
until the user hovers or clicks an end. If you need a "just-start"
|
|
49
|
+
indicator, pass it via `markers` from the consumer.
|
|
50
|
+
- **Both calendars share the same `markers`, `get-marker`, and
|
|
51
|
+
`is-disabled` props.** Consumer markers spanning across months draw
|
|
52
|
+
correctly because they're date-based, not anchor-based.
|
|
53
|
+
- **Hover state clears on `mouseleave` of the popover content**, not on
|
|
54
|
+
picking. Quick double-click sequences clear hover only after the
|
|
55
|
+
second click.
|
|
56
|
+
- **No keyboard support for range selection** — arrow-key roving inside
|
|
57
|
+
Calendar still works, but Enter on the keyboard-focused day picks one
|
|
58
|
+
end at a time, mirroring mouse behavior.
|
|
59
|
+
- **Min/max bounds are ISO string comparisons**: `iso < min` works
|
|
60
|
+
because ISO dates sort lexicographically. Pass YYYY-MM-DD strings, not
|
|
61
|
+
arbitrary Date objects.
|
|
62
|
+
|
|
63
|
+
## Quick reference
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<script setup lang="ts">
|
|
67
|
+
import type { DateRange } from "../components/date/RangePicker.vue";
|
|
68
|
+
|
|
69
|
+
const range = defineModel<DateRange>({
|
|
70
|
+
default: () => ({ start: null, end: null }),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const bookedDays = ["2026-06-15", "2026-06-16"];
|
|
74
|
+
function isDisabled(iso: string) {
|
|
75
|
+
return bookedDays.includes(iso);
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<orio-date-range-picker
|
|
81
|
+
v-model="range"
|
|
82
|
+
:label="$t('booking.dates')"
|
|
83
|
+
min="2026-06-10"
|
|
84
|
+
max="2026-12-31"
|
|
85
|
+
:is-disabled="isDisabled"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Related
|
|
91
|
+
|
|
92
|
+
- `<orio-calendar>` — the underlying grid; rendered twice.
|
|
93
|
+
- `<orio-date-picker>` — single-date variant.
|
|
94
|
+
- `<orio-date-picker-trigger>` — the shared trigger button.
|
|
95
|
+
- `utils/date` — `DateRange`, `parseISO`, `formatISO`, `addMonths`,
|
|
96
|
+
`startOfMonth`, `formatDate`.
|
|
97
|
+
- Public API reference: `docs/components/date/`.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Media & misc
|
|
4
|
+
purpose: carousel, image slider, gallery, lightbox slider, image viewer
|
|
5
|
+
short: image carousel with swipe gestures, prev/next buttons, dynamic sizing, and per-image slot
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# gallery/Carousel — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-gallery-carousel>` cycles through a list of images with swipe and
|
|
12
|
+
prev/next buttons. Pair it with `<orio-gallery-carousel-preview>` for a
|
|
13
|
+
thumbnail strip that binds to the same `activeImage` model.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **v-model name is `activeImage`** and the type is `string` (the image
|
|
18
|
+
URL or id), **not** an index. Bind via `v-model:active-image="..."`.
|
|
19
|
+
- **`size` prop is a `"W:H"` string** parsed as `width:height` in
|
|
20
|
+
pixels:
|
|
21
|
+
- `"400:550"` → fixed 400×550.
|
|
22
|
+
- `"400:"` (empty after colon) → fixed width, **dynamic height** —
|
|
23
|
+
measured from the slot content via a hidden `.carousel-measure`
|
|
24
|
+
container.
|
|
25
|
+
- `":550"` → fixed height, dynamic width.
|
|
26
|
+
- **`fit`**: `"contain"` (default), `"fill"`, `"cover"`, `"scale-down"`.
|
|
27
|
+
Applied as `object-fit` to the inner image via `v-bind(fit)` CSS
|
|
28
|
+
binding.
|
|
29
|
+
- **`appearance`**: `"default"` (border + background) or `"minimal"` (no
|
|
30
|
+
border, no background; prev/next buttons appear only on hover).
|
|
31
|
+
- **Swipe threshold is 10px** of horizontal pointer movement. Drag-right
|
|
32
|
+
→ `previousImage`, drag-left → `nextImage`. Below threshold = no
|
|
33
|
+
change.
|
|
34
|
+
- **Looping is implicit.** `nextImage` past the last → first, `previousImage`
|
|
35
|
+
before the first → last. No flag to disable.
|
|
36
|
+
- **Only 3 items are visible at once**: previous (translated −100%),
|
|
37
|
+
active (0), next (translated +100%). All others have `opacity: 0;
|
|
38
|
+
pointer-events: none`.
|
|
39
|
+
- **`#image` slot** overrides the default `<img>` render. Receives `{ image }`.
|
|
40
|
+
Use for videos, captions, complex viewer markup. Slotted content is
|
|
41
|
+
also rendered into the hidden measure container when `size` has a
|
|
42
|
+
dynamic dimension.
|
|
43
|
+
- **Auto-init on mount**: if `activeImage` is unbound or empty, it is
|
|
44
|
+
set to `images[0]`.
|
|
45
|
+
- **Switch buttons only render when `images.length > 1`.**
|
|
46
|
+
- **Transitions**: opacity + transform, 0.5s ease-in-out.
|
|
47
|
+
- **`max-height` clamp**: when both dimensions are fixed, the carousel
|
|
48
|
+
scales down to `carouselWidth / aspectRatio` to respect the parent
|
|
49
|
+
width while preserving the aspect.
|
|
50
|
+
|
|
51
|
+
## Gotchas
|
|
52
|
+
|
|
53
|
+
- **Image URLs must be unique** — they are used as v-for keys and the
|
|
54
|
+
active-image model. Duplicate URLs collapse to one logical slide.
|
|
55
|
+
- **No keyboard arrow nav.** Swipe + click only. Add `@keydown` on a
|
|
56
|
+
parent if needed.
|
|
57
|
+
- **Switch buttons use `mix-blend-mode: difference`** on supporting
|
|
58
|
+
browsers (not Safari) to remain visible over any image. Custom themes
|
|
59
|
+
may need to override the `.switch-button :deep(.icon)` styles.
|
|
60
|
+
- **Dynamic sizing causes a one-frame measurement flicker** while the
|
|
61
|
+
hidden measure container resolves. For non-changing content, prefer a
|
|
62
|
+
fixed `size` like `"400:550"`.
|
|
63
|
+
- **The carousel `<img>` has `alt="image-url"`** by default — visually
|
|
64
|
+
fine but bad for accessibility. Override via `#image` slot to render
|
|
65
|
+
proper alt text.
|
|
66
|
+
|
|
67
|
+
## Quick reference
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
const images = [
|
|
72
|
+
"/photos/1.jpg",
|
|
73
|
+
"/photos/2.jpg",
|
|
74
|
+
"/photos/3.jpg",
|
|
75
|
+
];
|
|
76
|
+
const active = ref(images[0]);
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<orio-gallery-carousel
|
|
81
|
+
v-model:active-image="active"
|
|
82
|
+
:images="images"
|
|
83
|
+
size="600:"
|
|
84
|
+
fit="contain"
|
|
85
|
+
/>
|
|
86
|
+
<orio-gallery-carousel-preview
|
|
87
|
+
v-model:active-image="active"
|
|
88
|
+
:images="images"
|
|
89
|
+
/>
|
|
90
|
+
</template>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Related
|
|
94
|
+
|
|
95
|
+
- `<orio-gallery-carousel-preview>` — thumbnail strip bound to the same
|
|
96
|
+
active-image model.
|
|
97
|
+
- `<orio-modal>` — wrap a carousel for lightbox viewing.
|
|
98
|
+
- Public API reference: `docs/components/gallery/`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Media & misc
|
|
4
|
+
purpose: carousel preview, thumbnails strip, image picker strip, gallery thumbnails
|
|
5
|
+
short: horizontal thumbnail strip for the Carousel; clicking a thumb updates the shared `activeImage` model
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# gallery/CarouselPreview — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-gallery-carousel-preview>` renders a horizontal scrollable strip of
|
|
12
|
+
thumbnails for `<orio-gallery-carousel>`. Bind both components to the same
|
|
13
|
+
`v-model:active-image` and they stay in sync.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **v-model name is `activeImage`** (same as the Carousel) and the type
|
|
18
|
+
is `string`. Share the same ref between them.
|
|
19
|
+
- **Hidden when `images.length ≤ 1`.** Single-image galleries don't
|
|
20
|
+
render a strip.
|
|
21
|
+
- **Each thumbnail is a `<button>`** with `aria-pressed` (true when
|
|
22
|
+
active) and `aria-label` `"Show image N of M"` for screen readers.
|
|
23
|
+
- **Thumbnails are 3.5rem × 3.5rem** with `object-fit` driven by `fit`
|
|
24
|
+
(default `"cover"`, unlike Carousel's `"contain"` default).
|
|
25
|
+
- **`#image` slot** overrides the default `<img>` render. Receives
|
|
26
|
+
`{ image }`. Same signature as the Carousel slot.
|
|
27
|
+
- **Strip scrolls horizontally** with `overflow-x: auto`. No
|
|
28
|
+
auto-scroll-to-active — clicking a thumb that's offscreen won't
|
|
29
|
+
scroll it into view.
|
|
30
|
+
- **Active thumb gets**: opacity 1, accent border. Inactive: opacity
|
|
31
|
+
0.6 with a hover bump to 0.85.
|
|
32
|
+
|
|
33
|
+
## Gotchas
|
|
34
|
+
|
|
35
|
+
- **No keyboard arrow nav between thumbs.** Tab moves between buttons;
|
|
36
|
+
Enter / Space activates. Add roving-focus if needed.
|
|
37
|
+
- **No auto-scroll on active change.** If the consumer changes
|
|
38
|
+
`activeImage` from elsewhere, the strip doesn't follow — scroll it
|
|
39
|
+
into view yourself via `element.scrollIntoView()`.
|
|
40
|
+
- **Alt is `""`** on thumbnails by default — they're treated as
|
|
41
|
+
decorative because the `<button>` carries the accessible name.
|
|
42
|
+
|
|
43
|
+
## Quick reference
|
|
44
|
+
|
|
45
|
+
See `<orio-gallery-carousel>` USAGE.md.
|
|
46
|
+
|
|
47
|
+
## Related
|
|
48
|
+
|
|
49
|
+
- `<orio-gallery-carousel>` — the main viewer; share the
|
|
50
|
+
`activeImage` model.
|
|
51
|
+
- Public API reference: `docs/components/gallery/`.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Media & misc
|
|
4
|
+
purpose: upload, file picker, drop-to-upload, file input, headless file upload
|
|
5
|
+
short: headless file upload — provides drop-zone state and file-dialog opener via slot props; consumer renders the UI
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# upload — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-upload>` is a **headless** file-upload component. It owns drag-drop
|
|
12
|
+
detection and file-dialog opening; the consumer renders all UI through the
|
|
13
|
+
default slot. There is no built-in look.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **Template is essentially `<div><slot :is-over-drop-zone :open-dialog /></div>`.**
|
|
18
|
+
No styling, no built-in drop hint, no preview list.
|
|
19
|
+
- **Slot props**:
|
|
20
|
+
- `isOverDropZone: boolean` — true while a dragged file is over the
|
|
21
|
+
zone (and the component is not disabled).
|
|
22
|
+
- `openDialog: () => void` — opens the native file picker.
|
|
23
|
+
- **v-model is `File[]`** (default `[]`). Drops and dialog selections
|
|
24
|
+
**append** to it; the array is then sliced to `maxFiles` if set.
|
|
25
|
+
- **`maxFiles`**:
|
|
26
|
+
- `undefined` (default) → unlimited.
|
|
27
|
+
- `> 1` → multi-select mode (drop & dialog).
|
|
28
|
+
- `1` → single-file mode; new selections replace the array (capped to
|
|
29
|
+
length 1 by the slice).
|
|
30
|
+
- **`allowedTypes`** is forwarded as `dataTypes` to `useDropZone`
|
|
31
|
+
(drop filter) and as `accept` (comma-joined) to the native dialog.
|
|
32
|
+
Be explicit — passing MIME-type strings (`"image/png"`) vs.
|
|
33
|
+
extensions (`".png"`) is the consumer's choice.
|
|
34
|
+
- **`disabled`** blocks both drop and `openDialog` calls. `isOverDropZone`
|
|
35
|
+
is also forced `false` while disabled so the slot UI doesn't flash an
|
|
36
|
+
"active" state during a no-op.
|
|
37
|
+
- **The whole template div is the drop zone.** The slot content sits
|
|
38
|
+
inside it; the consumer's hit area equals whatever they render.
|
|
39
|
+
|
|
40
|
+
## Gotchas
|
|
41
|
+
|
|
42
|
+
- **No UI at all by default.** A bare `<orio-upload v-model="files" />`
|
|
43
|
+
renders an empty `<div>` — clicking does nothing. You must provide a
|
|
44
|
+
default slot that calls `openDialog`.
|
|
45
|
+
- **Drops append**, including duplicates. Same-named files are added
|
|
46
|
+
again; dedupe in the consumer if needed.
|
|
47
|
+
- **`maxFiles` only enforces on append**. If the model is pre-populated
|
|
48
|
+
with more files than `maxFiles`, they stick around until the next
|
|
49
|
+
drop / dialog truncates them.
|
|
50
|
+
- **`useFileDialog` uses native input.** It's not styleable. The "dialog"
|
|
51
|
+
is the OS chooser; styling lives on the trigger element you render in
|
|
52
|
+
the slot.
|
|
53
|
+
- **No progress / upload semantics.** This component only collects File
|
|
54
|
+
objects. Uploading them to a server is the consumer's job.
|
|
55
|
+
- **`accept` attribute on the dialog vs. drop filter divergence**: the
|
|
56
|
+
drop filter is enforced by browser drag-drop semantics; the dialog's
|
|
57
|
+
`accept` is a hint, not a hard filter — users can choose any file via
|
|
58
|
+
the chooser depending on OS.
|
|
59
|
+
|
|
60
|
+
## Quick reference
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
const files = ref<File[]>([]);
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<orio-upload
|
|
69
|
+
v-model="files"
|
|
70
|
+
:max-files="5"
|
|
71
|
+
:allowed-types="['image/png', 'image/jpeg']"
|
|
72
|
+
>
|
|
73
|
+
<template #default="{ isOverDropZone, openDialog }">
|
|
74
|
+
<orio-dashed-container
|
|
75
|
+
:icon="isOverDropZone ? 'drop' : 'upload'"
|
|
76
|
+
:text="$t(isOverDropZone ? 'upload.drop' : 'upload.choose')"
|
|
77
|
+
@click="openDialog"
|
|
78
|
+
/>
|
|
79
|
+
</template>
|
|
80
|
+
</orio-upload>
|
|
81
|
+
|
|
82
|
+
<ul>
|
|
83
|
+
<li v-for="(file, index) in files" :key="index">{{ file.name }}</li>
|
|
84
|
+
</ul>
|
|
85
|
+
</template>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Related
|
|
89
|
+
|
|
90
|
+
- `<orio-dashed-container>` — common UI shell for upload tiles.
|
|
91
|
+
- Public API reference: `docs/components/upload.md` (if present).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Media & misc
|
|
4
|
+
purpose: read-only date display, formatted date range, date range view
|
|
5
|
+
short: locale-aware read-only date or date range display; inline `<orio-view-text>` for start/end
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# view/Dates — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-view-dates>` renders a `DateRange` (`{ start, end }`) as inline
|
|
12
|
+
formatted text using the active vue-i18n locale.
|
|
13
|
+
|
|
14
|
+
## Invariants
|
|
15
|
+
|
|
16
|
+
- **`dates` is required** and typed `DateRange` (ISO strings).
|
|
17
|
+
- **Format options**:
|
|
18
|
+
- Default: `{ day: "numeric", month: "short", year: "numeric" }` →
|
|
19
|
+
`"10 Jun 2026"`.
|
|
20
|
+
- `month: true` → omits the day → `"Jun 2026"`. Use for
|
|
21
|
+
month-resolution ranges (subscription periods, etc.).
|
|
22
|
+
- **Uses `formatDate(iso, locale, options)` from `utils/date`**. Output
|
|
23
|
+
follows the locale — `en` vs `uk` will render different month
|
|
24
|
+
abbreviations.
|
|
25
|
+
- **Separator**: literal `" - "` rendered between start and end when
|
|
26
|
+
both are present. No en-dash, no localization.
|
|
27
|
+
- **Renders two `<orio-view-text>`** with `type` and `size` forwarded
|
|
28
|
+
(defaults `type: "italics"`, `size: "small"`).
|
|
29
|
+
- **`* { display: inline }`** on the wrapper forces both view-text
|
|
30
|
+
blocks inline so they read as one sentence.
|
|
31
|
+
|
|
32
|
+
## Gotchas
|
|
33
|
+
|
|
34
|
+
- **Only start, only end, or both**: rendering gracefully handles a
|
|
35
|
+
missing `end` (no separator, no second block). A missing `start` with
|
|
36
|
+
an `end` renders the separator alone — degraded UX.
|
|
37
|
+
- **No relative formatting** (e.g. "yesterday", "3 days ago"). For
|
|
38
|
+
relative output, format in the consumer and pass via
|
|
39
|
+
`<orio-view-text>` instead.
|
|
40
|
+
- **`size` is forwarded to view-text** but the wrapper itself has no
|
|
41
|
+
size. Custom CSS that targets the wrapper won't see a size class.
|
|
42
|
+
|
|
43
|
+
## Quick reference
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
const period = { start: "2026-06-01", end: "2026-06-30" };
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<orio-view-dates :dates="period" />
|
|
52
|
+
|
|
53
|
+
<orio-view-dates
|
|
54
|
+
:dates="{ start: subscriptionStart, end: subscriptionEnd }"
|
|
55
|
+
month
|
|
56
|
+
type="title"
|
|
57
|
+
size="medium"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Related
|
|
63
|
+
|
|
64
|
+
- `<orio-view-text>` — used internally for each end of the range.
|
|
65
|
+
- `<orio-date-range-picker>` — picker that produces `DateRange` values.
|
|
66
|
+
- `utils/date` — `formatDate`, `DateRange` type.
|
|
67
|
+
- Public API reference: `docs/components/view/dates.md` (if present).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: component
|
|
3
|
+
category: Media & misc
|
|
4
|
+
purpose: keyboard bindings hint display, shortcut display, kbd renderer
|
|
5
|
+
short: parses a backtick-delimited shortcut string and renders each key as `<kbd>` with separators inline
|
|
6
|
+
invariants: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# view/KeyBinds — agent-only invariants
|
|
10
|
+
|
|
11
|
+
`<orio-view-key-binds>` parses a backtick-delimited string like
|
|
12
|
+
`` "`Ctrl` + `Z`" `` and renders each backticked token as a `<kbd>`
|
|
13
|
+
element with the surrounding text as separator.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
- **`bind` is the single string prop.** Tokens between backticks (`` `…` ``)
|
|
18
|
+
become `<kbd>` elements; everything else renders as a `.separator`
|
|
19
|
+
`<span>`.
|
|
20
|
+
- **Regex is `/`([^`]+)`/g`** — non-greedy match inside backticks.
|
|
21
|
+
Empty backticks (`` `` ``) and unmatched openings are passed through
|
|
22
|
+
as plain text.
|
|
23
|
+
- **No tokenization beyond backticks.** `"+"`, `" "`, `","`, `"or"`
|
|
24
|
+
between keys all render as plain separator text. Style them via the
|
|
25
|
+
`.separator` class.
|
|
26
|
+
- **Output structure**: one `<span class="keybinds">` wrapper, with
|
|
27
|
+
`<kbd>` and `<span class="separator">` children inline. Wrapper is
|
|
28
|
+
`inline-flex` with 0.2rem gap.
|
|
29
|
+
- **Kbd styling** is fixed: rgba white background tint, small font,
|
|
30
|
+
border. Designed for dark surfaces — over a light background, the
|
|
31
|
+
contrast may be poor; override `kbd` styles via global CSS.
|
|
32
|
+
|
|
33
|
+
## Gotchas
|
|
34
|
+
|
|
35
|
+
- **The string is rendered as-is.** No localization, no key-symbol
|
|
36
|
+
substitution (e.g. `Cmd` does not become `⌘`). Build that mapping in
|
|
37
|
+
the consumer if needed.
|
|
38
|
+
- **No `aria-label`.** Screen readers read each `<kbd>` token aloud
|
|
39
|
+
with the separator text — usually fine for `"Ctrl + Z"`, less great
|
|
40
|
+
for `" or "`-separated alternates.
|
|
41
|
+
- **Mismatched backticks render as text.** `` "`Ctrl + Z" `` (missing
|
|
42
|
+
closing tick) becomes plain text starting from the unmatched
|
|
43
|
+
backtick.
|
|
44
|
+
|
|
45
|
+
## Quick reference
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<template>
|
|
49
|
+
<orio-view-key-binds bind="`Ctrl` + `Z`" />
|
|
50
|
+
<orio-view-key-binds bind="press `Esc` to close" />
|
|
51
|
+
<orio-view-key-binds bind="`Cmd` + `Shift` + `P` or `F1`" />
|
|
52
|
+
</template>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Related
|
|
56
|
+
|
|
57
|
+
- Public API reference: `docs/components/view/key-binds.md` (if
|
|
58
|
+
present).
|