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.
Files changed (102) hide show
  1. package/README.md +78 -3
  2. package/bin/orio-ui.mjs +72 -0
  3. package/dist/agents/ROUTING.md +140 -0
  4. package/dist/agents/component-finder.md +142 -0
  5. package/dist/agents/component-worker.md +152 -0
  6. package/dist/agents/snippet.md +6 -0
  7. package/dist/module.json +1 -1
  8. package/dist/runtime/components/AnimatedContainer.USAGE.md +79 -0
  9. package/dist/runtime/components/Badge.USAGE.md +75 -0
  10. package/dist/runtime/components/Banner.USAGE.md +52 -0
  11. package/dist/runtime/components/Button.USAGE.md +78 -0
  12. package/dist/runtime/components/Button.d.vue.ts +3 -2
  13. package/dist/runtime/components/Button.vue +19 -11
  14. package/dist/runtime/components/Button.vue.d.ts +3 -2
  15. package/dist/runtime/components/Calendar.USAGE.md +59 -0
  16. package/dist/runtime/components/Calendar.vue +254 -87
  17. package/dist/runtime/components/Canvas/USAGE.md +73 -0
  18. package/dist/runtime/components/CheckBox.USAGE.md +63 -0
  19. package/dist/runtime/components/CheckBox.vue +9 -3
  20. package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
  21. package/dist/runtime/components/CheckboxGroup.vue +7 -1
  22. package/dist/runtime/components/ControlElement.USAGE.md +77 -0
  23. package/dist/runtime/components/ControlElement.d.vue.ts +42 -27
  24. package/dist/runtime/components/ControlElement.vue +28 -9
  25. package/dist/runtime/components/ControlElement.vue.d.ts +42 -27
  26. package/dist/runtime/components/DashedContainer.USAGE.md +65 -0
  27. package/dist/runtime/components/EmptyState.USAGE.md +65 -0
  28. package/dist/runtime/components/Form.USAGE.md +102 -0
  29. package/dist/runtime/components/Icon.USAGE.md +61 -0
  30. package/dist/runtime/components/Input.USAGE.md +57 -0
  31. package/dist/runtime/components/Input.vue +13 -3
  32. package/dist/runtime/components/ListItem.USAGE.md +84 -0
  33. package/dist/runtime/components/LoadingSpinner.USAGE.md +50 -0
  34. package/dist/runtime/components/LocaleSwitcher.USAGE.md +73 -0
  35. package/dist/runtime/components/Modal.USAGE.md +72 -0
  36. package/dist/runtime/components/NavButton.USAGE.md +80 -0
  37. package/dist/runtime/components/NavButton.d.vue.ts +0 -1
  38. package/dist/runtime/components/NavButton.vue +9 -5
  39. package/dist/runtime/components/NavButton.vue.d.ts +0 -1
  40. package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
  41. package/dist/runtime/components/NumberInput/Horizontal.vue +7 -2
  42. package/dist/runtime/components/NumberInput/USAGE.md +74 -0
  43. package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
  44. package/dist/runtime/components/NumberInput/Vertical.vue +7 -2
  45. package/dist/runtime/components/NumberInput/index.d.vue.ts +0 -2
  46. package/dist/runtime/components/NumberInput/index.vue +9 -7
  47. package/dist/runtime/components/NumberInput/index.vue.d.ts +0 -2
  48. package/dist/runtime/components/Popover.USAGE.md +103 -0
  49. package/dist/runtime/components/RadioButton.USAGE.md +72 -0
  50. package/dist/runtime/components/RadioButton.d.vue.ts +0 -2
  51. package/dist/runtime/components/RadioButton.vue +9 -4
  52. package/dist/runtime/components/RadioButton.vue.d.ts +0 -2
  53. package/dist/runtime/components/Selector.USAGE.md +131 -0
  54. package/dist/runtime/components/Selector.d.vue.ts +1 -0
  55. package/dist/runtime/components/Selector.vue +10 -4
  56. package/dist/runtime/components/Selector.vue.d.ts +1 -0
  57. package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
  58. package/dist/runtime/components/SwitchButton.d.vue.ts +1 -4
  59. package/dist/runtime/components/SwitchButton.vue +10 -7
  60. package/dist/runtime/components/SwitchButton.vue.d.ts +1 -4
  61. package/dist/runtime/components/Tag.USAGE.md +51 -0
  62. package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
  63. package/dist/runtime/components/TaggableSelector.vue +7 -1
  64. package/dist/runtime/components/Textarea.USAGE.md +72 -0
  65. package/dist/runtime/components/Textarea.vue +13 -3
  66. package/dist/runtime/components/Tooltip.USAGE.md +84 -0
  67. package/dist/runtime/components/ZoomableContainer.USAGE.md +108 -0
  68. package/dist/runtime/components/date/Picker.USAGE.md +52 -0
  69. package/dist/runtime/components/date/Picker.vue +7 -1
  70. package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
  71. package/dist/runtime/components/date/PickerTrigger.vue +9 -3
  72. package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
  73. package/dist/runtime/components/date/RangePicker.vue +7 -1
  74. package/dist/runtime/components/gallery/Carousel.USAGE.md +98 -0
  75. package/dist/runtime/components/gallery/CarouselPreview.USAGE.md +51 -0
  76. package/dist/runtime/components/upload/USAGE.md +91 -0
  77. package/dist/runtime/components/view/Dates.USAGE.md +67 -0
  78. package/dist/runtime/components/view/KeyBinds.USAGE.md +58 -0
  79. package/dist/runtime/components/view/Separator.USAGE.md +57 -0
  80. package/dist/runtime/components/view/Text.USAGE.md +68 -0
  81. package/dist/runtime/composables/useApi.USAGE.md +64 -0
  82. package/dist/runtime/composables/useControlSize.USAGE.md +73 -0
  83. package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
  84. package/dist/runtime/composables/useFilter.USAGE.md +120 -0
  85. package/dist/runtime/composables/useFilter.d.ts +91 -0
  86. package/dist/runtime/composables/useFilter.js +111 -0
  87. package/dist/runtime/composables/useFuzzySearch.USAGE.md +68 -0
  88. package/dist/runtime/composables/useInertia.USAGE.md +80 -0
  89. package/dist/runtime/composables/useListKeyboard.USAGE.md +97 -0
  90. package/dist/runtime/composables/useModal.USAGE.md +82 -0
  91. package/dist/runtime/composables/usePinchZoom.USAGE.md +95 -0
  92. package/dist/runtime/composables/usePressAndHold.USAGE.md +70 -0
  93. package/dist/runtime/composables/useRovingGrid.USAGE.md +106 -0
  94. package/dist/runtime/composables/useRovingGrid.d.ts +35 -0
  95. package/dist/runtime/composables/useRovingGrid.js +115 -0
  96. package/dist/runtime/composables/useSound.USAGE.md +74 -0
  97. package/dist/runtime/composables/useTheme.USAGE.md +76 -0
  98. package/dist/runtime/composables/useUrlSync.USAGE.md +91 -0
  99. package/dist/runtime/composables/useValidation.USAGE.md +100 -0
  100. package/dist/runtime/i18n/en.json +4 -1
  101. package/dist/runtime/i18n/uk.json +4 -1
  102. package/package.json +12 -2
@@ -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/`.
@@ -22,7 +22,13 @@ const props = defineProps({
22
22
  label: { type: String, required: false },
23
23
  layout: { type: String, required: false },
24
24
  size: { type: String, required: false },
25
- fill: { type: Boolean, required: false }
25
+ fill: { type: Boolean, required: false },
26
+ tabindex: { type: [Number, String], required: false },
27
+ focusKey: { type: String, required: false },
28
+ disabled: { type: Boolean, required: false },
29
+ required: { type: Boolean, required: false },
30
+ name: { type: String, required: false },
31
+ ariaLabel: { type: String, required: false }
26
32
  });
27
33
  const range = defineModel({ type: Object, ...{
28
34
  default: () => ({ start: null, end: null })
@@ -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).
@@ -0,0 +1,57 @@
1
+ ---
2
+ kind: component
3
+ category: Media & misc
4
+ purpose: separator, divider, horizontal rule, divider line
5
+ short: horizontal separator line with configurable border style, size in px, and block margin in rem
6
+ invariants: false
7
+ ---
8
+
9
+ # view/Separator — agent-only invariants
10
+
11
+ `<orio-view-separator>` is a horizontal rule rendered as a `<div>` with a
12
+ `border-block-end`. It is **not** an `<hr>` element.
13
+
14
+ ## Invariants
15
+
16
+ - **`style` prop** is the CSS border style: `"solid"` (default),
17
+ `"dotted"`, `"dashed"`, `"double"`, `"groove"`, `"ridge"`.
18
+ - **`size`** (number, default `1`) is the border width in **pixels**.
19
+ - **`margin`** (number, default `1`) is the **rem** spacing above and
20
+ below via `margin-block`. So `margin: 1` → `1rem` top + `1rem`
21
+ bottom.
22
+ - **Color is `var(--color-border)`** — not themable per-instance. Use
23
+ CSS overrides if you need a different color.
24
+ - **Block-direction aware**: `border-block-end` and `margin-block`
25
+ respect the writing mode. In a horizontal writing mode it's a bottom
26
+ border + vertical margins; in vertical writing modes it flips.
27
+
28
+ ## Gotchas
29
+
30
+ - **Renders a `<div>`, not an `<hr>`.** Screen readers may not announce
31
+ a section break. For semantic separation, add `role="separator"` via
32
+ `$attrs`.
33
+ - **`size` is unitless number → px** by template binding. Strings get
34
+ used verbatim (`"2px"` works, but loses the type signal).
35
+ - **No vertical orientation.** For a vertical divider, write a custom
36
+ `<div>` with `border-inline-start` rather than using this component.
37
+
38
+ ## Quick reference
39
+
40
+ ```vue
41
+ <template>
42
+ <p>First section</p>
43
+
44
+ <orio-view-separator />
45
+
46
+ <p>Second section</p>
47
+
48
+ <orio-view-separator style="dashed" :size="2" :margin="2" />
49
+
50
+ <p>Third section</p>
51
+ </template>
52
+ ```
53
+
54
+ ## Related
55
+
56
+ - Public API reference: `docs/components/view/separator.md` (if
57
+ present).
@@ -0,0 +1,68 @@
1
+ ---
2
+ kind: component
3
+ category: Media & misc
4
+ purpose: read-only text display, formatted view, typography primitive, label
5
+ short: typed text primitive (text/title/subtitle/italics) with size, uppercase, line-clamp, and inline icon
6
+ invariants: true
7
+ ---
8
+
9
+ # view/Text — agent-only invariants
10
+
11
+ `<orio-view-text>` is the typography primitive for read-only labels and
12
+ text blocks. Use it instead of bare `<p>` / `<span>` / `<h*>` for inline
13
+ icons, theming, and line-clamping.
14
+
15
+ ## Invariants
16
+
17
+ - **`type`**: `"text"` (default) / `"title"` (bold) / `"subtitle"`
18
+ (semi-bold, muted) / `"italics"` (italic, muted). Affects font-weight,
19
+ style, and color.
20
+ - **`size`**: `"small"` / `"medium"` (default) / `"large"` /
21
+ `"extra-large"`. Maps to `--font-sm` … `--font-xl` tokens.
22
+ - **`uppercase: true`** applies `text-transform: uppercase`.
23
+ - **`icon` prop** renders `<orio-icon>` inline before the text/slot. The
24
+ wrapper is `display: flex; align-items: center; gap: 0.25rem` so the
25
+ icon sits inline with the text.
26
+ - **`lineClamp` (number or string)** enables `-webkit-line-clamp` line
27
+ truncation with ellipsis. Defaults to 1 line when the prop is present
28
+ but unset.
29
+ - **Content sources**: default slot or `v-model:modelValue` string. Slot
30
+ wins. v-model is bound so the component plays nicely with
31
+ `<orio-form>` auto-bind by `name`.
32
+ - **`--view-text-color` CSS var** overrides the type's default color
33
+ without changing the type prop. Useful for accent/error states from
34
+ the consumer.
35
+ - **No `inheritAttrs: false`** — attrs flow to the wrapper `<div>`.
36
+
37
+ ## Gotchas
38
+
39
+ - **The wrapper is a `<div>`, not a heading element.** Title type does
40
+ not produce an `<h1>` / `<h2>` etc. For semantic headings, write
41
+ `<h2><orio-view-text type="title">...</orio-view-text></h2>` or use a
42
+ plain heading element.
43
+ - **`lineClamp` requires multi-line content** to show the ellipsis. A
44
+ single-line value with `lineClamp: 2` simply renders as one line.
45
+ - **`white-space: pre-wrap`** is set globally on the wrapper —
46
+ whitespace and newlines from the source are preserved.
47
+
48
+ ## Quick reference
49
+
50
+ ```vue
51
+ <template>
52
+ <orio-view-text type="title" size="large">
53
+ {{ $t("article.title") }}
54
+ </orio-view-text>
55
+
56
+ <orio-view-text type="subtitle" :line-clamp="3" icon="info">
57
+ {{ description }}
58
+ </orio-view-text>
59
+
60
+ <orio-view-text type="italics" uppercase v-model="badgeLabel" />
61
+ </template>
62
+ ```
63
+
64
+ ## Related
65
+
66
+ - `<orio-view-dates>` — locale-aware date range display built on top.
67
+ - `<orio-empty-state>` — uses this internally for title and description.
68
+ - Public API reference: `docs/components/view/text.md` (if present).
@@ -0,0 +1,64 @@
1
+ ---
2
+ kind: composable
3
+ category: Composables
4
+ purpose: fetch, API client, HTTP request, JSON fetch
5
+ short: thin typed wrapper around ofetch's `$fetch` for GET/POST/PUT/DELETE/PATCH requests
6
+ invariants: false
7
+ ---
8
+
9
+ # useApi — agent-only invariants
10
+
11
+ `useApi` is a typed async function (not a reactive composable) that
12
+ wraps `ofetch`'s `$fetch`. It returns a `Promise<T>` — no loading/error
13
+ refs, no Vue lifecycle.
14
+
15
+ ## Invariants
16
+
17
+ - **Not a Vue composable.** It is a plain typed async function, safe to
18
+ call anywhere — outside `setup`, inside event handlers, in route
19
+ middleware.
20
+ - **Two overloads**: `useApi<T>(url)` for GET, `useApi<T>(url, options)`
21
+ for everything else.
22
+ - **`ApiOptions`** fields:
23
+ - `method`: `"GET"` (default), `"POST"`, `"PUT"`, `"DELETE"`, `"PATCH"`.
24
+ - `body`: `Record<string, unknown>` — JSON object. Not FormData, not
25
+ a string.
26
+ - `query`: `Record<string, unknown>` — appended as query params.
27
+ - `signal`: `AbortSignal` for cancellation.
28
+ - **Return type is `T`** — pass the generic for type safety. Without
29
+ it, you get `unknown`.
30
+
31
+ ## Gotchas
32
+
33
+ - **No retry, no caching, no de-dupe.** Bring `@tanstack/vue-query` or
34
+ similar if you need those.
35
+ - **`body` does not handle non-JSON payloads.** For file uploads,
36
+ use `fetch` directly or extend the composable.
37
+ - **Errors throw.** Wrap calls in try/catch — `ofetch` throws on non-2xx
38
+ responses.
39
+
40
+ ## Quick reference
41
+
42
+ ```ts
43
+ import { useApi } from "../composables/useApi";
44
+
45
+ interface User { id: string; name: string }
46
+
47
+ const user = await useApi<User>("/api/users/123");
48
+
49
+ const created = await useApi<User>("/api/users", {
50
+ method: "POST",
51
+ body: { name: "Vlad" },
52
+ });
53
+
54
+ const controller = new AbortController();
55
+ const results = await useApi<User[]>("/api/users", {
56
+ query: { search: "vl" },
57
+ signal: controller.signal,
58
+ });
59
+ controller.abort(); // cancels the request
60
+ ```
61
+
62
+ ## Related
63
+
64
+ - Public API reference: `docs/composables/use-api.md` (if present).