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.
Files changed (72) hide show
  1. package/README.md +76 -1
  2. package/bin/orio-ui.mjs +72 -0
  3. package/dist/agents/ROUTING.md +140 -0
  4. package/dist/agents/component-finder.md +142 -0
  5. package/dist/agents/component-worker.md +152 -0
  6. package/dist/agents/snippet.md +6 -0
  7. package/dist/module.json +1 -1
  8. package/dist/runtime/components/AnimatedContainer.USAGE.md +79 -0
  9. package/dist/runtime/components/Badge.USAGE.md +75 -0
  10. package/dist/runtime/components/Banner.USAGE.md +52 -0
  11. package/dist/runtime/components/Button.USAGE.md +83 -0
  12. package/dist/runtime/components/Button.d.vue.ts +1 -0
  13. package/dist/runtime/components/Button.vue +5 -1
  14. package/dist/runtime/components/Button.vue.d.ts +1 -0
  15. package/dist/runtime/components/Calendar.USAGE.md +8 -0
  16. package/dist/runtime/components/Canvas/USAGE.md +8 -0
  17. package/dist/runtime/components/CheckBox.USAGE.md +63 -0
  18. package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
  19. package/dist/runtime/components/ControlElement.USAGE.md +8 -0
  20. package/dist/runtime/components/ControlElement.d.vue.ts +1 -1
  21. package/dist/runtime/components/ControlElement.vue.d.ts +1 -1
  22. package/dist/runtime/components/DashedContainer.USAGE.md +65 -0
  23. package/dist/runtime/components/EmptyState.USAGE.md +65 -0
  24. package/dist/runtime/components/Form.USAGE.md +102 -0
  25. package/dist/runtime/components/Icon.USAGE.md +61 -0
  26. package/dist/runtime/components/Input.USAGE.md +8 -0
  27. package/dist/runtime/components/ListItem.USAGE.md +84 -0
  28. package/dist/runtime/components/LoadingSpinner.USAGE.md +50 -0
  29. package/dist/runtime/components/LocaleSwitcher.USAGE.md +73 -0
  30. package/dist/runtime/components/Modal.USAGE.md +8 -0
  31. package/dist/runtime/components/NavButton.USAGE.md +80 -0
  32. package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
  33. package/dist/runtime/components/NumberInput/Horizontal.vue +5 -0
  34. package/dist/runtime/components/NumberInput/USAGE.md +74 -0
  35. package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
  36. package/dist/runtime/components/NumberInput/Vertical.vue +9 -1
  37. package/dist/runtime/components/Popover.USAGE.md +103 -0
  38. package/dist/runtime/components/RadioButton.USAGE.md +72 -0
  39. package/dist/runtime/components/Selector.USAGE.md +131 -0
  40. package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
  41. package/dist/runtime/components/Tag.USAGE.md +51 -0
  42. package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
  43. package/dist/runtime/components/Textarea.USAGE.md +72 -0
  44. package/dist/runtime/components/Tooltip.USAGE.md +84 -0
  45. package/dist/runtime/components/ZoomableContainer.USAGE.md +108 -0
  46. package/dist/runtime/components/date/Picker.USAGE.md +8 -0
  47. package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
  48. package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
  49. package/dist/runtime/components/gallery/Carousel.USAGE.md +98 -0
  50. package/dist/runtime/components/gallery/CarouselPreview.USAGE.md +51 -0
  51. package/dist/runtime/components/upload/USAGE.md +91 -0
  52. package/dist/runtime/components/view/Dates.USAGE.md +67 -0
  53. package/dist/runtime/components/view/KeyBinds.USAGE.md +58 -0
  54. package/dist/runtime/components/view/Separator.USAGE.md +57 -0
  55. package/dist/runtime/components/view/Text.USAGE.md +68 -0
  56. package/dist/runtime/composables/useApi.USAGE.md +64 -0
  57. package/dist/runtime/composables/useControlSize.USAGE.md +73 -0
  58. package/dist/runtime/composables/useControlSize.js +12 -0
  59. package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
  60. package/dist/runtime/composables/useFilter.USAGE.md +120 -0
  61. package/dist/runtime/composables/useFuzzySearch.USAGE.md +68 -0
  62. package/dist/runtime/composables/useInertia.USAGE.md +80 -0
  63. package/dist/runtime/composables/useListKeyboard.USAGE.md +97 -0
  64. package/dist/runtime/composables/useModal.USAGE.md +82 -0
  65. package/dist/runtime/composables/usePinchZoom.USAGE.md +95 -0
  66. package/dist/runtime/composables/usePressAndHold.USAGE.md +70 -0
  67. package/dist/runtime/composables/useRovingGrid.USAGE.md +106 -0
  68. package/dist/runtime/composables/useSound.USAGE.md +74 -0
  69. package/dist/runtime/composables/useTheme.USAGE.md +76 -0
  70. package/dist/runtime/composables/useUrlSync.USAGE.md +91 -0
  71. package/dist/runtime/composables/useValidation.USAGE.md +100 -0
  72. package/package.json +12 -2
@@ -0,0 +1,79 @@
1
+ ---
2
+ kind: component
3
+ category: Layout & containers
4
+ purpose: animation wrapper, fade/slide a slot, animated list, mount-stagger layout
5
+ short: flex container that fade-slides its direct children up on mount and exposes a sound `play` callback
6
+ invariants: true
7
+ ---
8
+
9
+ # AnimatedContainer — agent-only invariants
10
+
11
+ `<orio-animated-container>` is a flex layout that runs a fade-in-up CSS
12
+ animation on every direct child when it mounts. It is **not** a Vue
13
+ `<Transition>` — there is no leave animation and reactive state changes do
14
+ not retrigger it.
15
+
16
+ ## Invariants
17
+
18
+ - **Animation fires on mount only.** The `containerFadeInUp` keyframe is a
19
+ plain CSS animation on `.animated-container > *`. To replay, change
20
+ `:key` on the container (or on the child). Re-rendering reactive content
21
+ inside the slot does **not** restart the animation.
22
+ - **Only direct children animate.** The selector is `> *`. Wrapping children
23
+ in an extra `<div>` moves the animation up to the wrapper. Nested deep
24
+ trees do not animate per-node.
25
+ - **Layout is opinionated.** The container is `display: flex; flex-wrap:
26
+ wrap; justify-content: center; gap: 1rem; padding-inline: 1rem;`. Treat it
27
+ as a layout primitive, not a transparent wrapper.
28
+ - **`direction` toggles flex-direction _and_ justification.** `"row"`
29
+ (default) → wrap + centered. `"column"` → `flex-direction: column` +
30
+ `justify-content: flex-start`.
31
+ - **`play` slot prop is `useSound().play`.** The default slot exposes
32
+ `{ play }`. Wire it to `@mouseenter` / `@click` on children for hover or
33
+ tap feedback. There is no prop to disable or swap the sound — it is
34
+ global `useSound`.
35
+
36
+ ## Gotchas
37
+
38
+ - **No leave animation.** Items removed from the slot disappear instantly.
39
+ If you need exit animation, wrap each item in `<Transition>` or use a
40
+ different primitive.
41
+ - **Newly inserted children animate independently.** When the slot's child
42
+ list grows (v-for over a reactive array), each new node animates on its
43
+ own mount — there is no list-coordinated stagger.
44
+ - **Built-in horizontal padding bleeds.** `padding-inline: 1rem` is on the
45
+ container. If the consumer wraps it in a tight column, the inner content
46
+ starts 1rem in. Override with `:deep(.animated-container)` or pad the
47
+ parent.
48
+ - **`direction="column"` does not stretch children.** They still wrap by
49
+ default (`flex-wrap: wrap` is unchanged). Children narrower than the
50
+ container will not fill the row.
51
+
52
+ ## Quick reference
53
+
54
+ ```vue
55
+ <script setup lang="ts">
56
+ import { ref } from "vue";
57
+
58
+ const animationKey = ref(0);
59
+ function replay() {
60
+ animationKey.value += 1;
61
+ }
62
+ </script>
63
+
64
+ <template>
65
+ <orio-button @click="replay">Replay</orio-button>
66
+
67
+ <orio-animated-container :key="animationKey" direction="column" v-slot="{ play }">
68
+ <div v-for="item in items" :key="item.id" @mouseenter="play">
69
+ {{ item.label }}
70
+ </div>
71
+ </orio-animated-container>
72
+ </template>
73
+ ```
74
+
75
+ ## Related
76
+
77
+ - `useSound` composable — the source of the `play` callback. See
78
+ `docs/composables/use-sound.md`.
79
+ - Public API reference: `docs/components/animated-container.md`.
@@ -0,0 +1,75 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: badge, small status pill, notification dot, count indicator, corner badge
5
+ short: small status pill or dot indicator; optionally positioned in the top-right corner of a wrapped element
6
+ invariants: true
7
+ ---
8
+
9
+ # Badge — agent-only invariants
10
+
11
+ `<orio-badge>` renders one of two shapes:
12
+ - A standalone inline status pill / dot.
13
+ - A **positioned** badge anchored to the top-right corner of an element
14
+ passed via the `#wrapping` slot (typical notification-on-icon pattern).
15
+
16
+ ## Invariants
17
+
18
+ - **`#wrapping` slot toggles positioning mode.** When provided, the badge
19
+ is rendered absolutely at `top: 15%; right: 15%; transform: translate(50%, -50%)`
20
+ on top of the wrapped element. Without `#wrapping`, the badge is plain
21
+ inline.
22
+ - **Default slot determines dot vs text mode.** Empty default slot → dot
23
+ badge (0.5rem circle, no padding). Any content → text/number badge.
24
+ - **`variant`**: `"primary"` (default), `"danger"`, `"alert"`, `"grey"`.
25
+ Maps to accent / danger / alert / surface color tokens.
26
+ - **`pill` prop**: switches the border-radius to pill shape; ignored when
27
+ in dot mode.
28
+ - **`hidden` prop**: gates the badge render via `v-if`. The wrapped slot
29
+ still renders when in wrapping mode — only the indicator hides.
30
+ - **No `count` prop.** Use the default slot for the number: `<orio-badge>3</orio-badge>`.
31
+
32
+ ## Gotchas
33
+
34
+ - **No automatic "99+" cap.** Long content (e.g. `1234`) renders fully,
35
+ pushing the corner badge wider. Cap in the consumer.
36
+ - **Positioned badge offsets are percentages of the wrapping element.**
37
+ Tiny wrapped icons may have the badge clip outside the wrapper. The
38
+ wrapper is `position: relative; display: inline-flex` — `overflow: visible`
39
+ is implicit, but parent overflow may clip it.
40
+ - **No interactive behavior.** Click does not bubble specially; it's a
41
+ `<span>`. For removable chips, use `<orio-tag>` instead.
42
+
43
+ ## Quick reference — corner badge on an icon
44
+
45
+ ```vue
46
+ <template>
47
+ <orio-badge variant="danger">
48
+ <template #wrapping>
49
+ <orio-button icon="bell" variant="subdued" />
50
+ </template>
51
+ {{ unreadCount }}
52
+ </orio-badge>
53
+
54
+ <orio-badge variant="alert" :hidden="!hasUpdates">
55
+ <template #wrapping>
56
+ <orio-icon name="refresh" />
57
+ </template>
58
+ </orio-badge>
59
+ </template>
60
+ ```
61
+
62
+ ## Quick reference — inline status pill
63
+
64
+ ```vue
65
+ <template>
66
+ <orio-badge variant="grey" pill>{{ $t("status.draft") }}</orio-badge>
67
+ <orio-badge variant="primary">{{ $t("status.live") }}</orio-badge>
68
+ </template>
69
+ ```
70
+
71
+ ## Related
72
+
73
+ - `<orio-tag>` — chip with text and optional `id` / variant for filters
74
+ and selections.
75
+ - Public API reference: `docs/components/badge.md`.
@@ -0,0 +1,52 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: banner, page-level notice, alert strip, inline notification, info bar
5
+ short: page-level notice strip with danger/alert/success/info variants; default slot for content
6
+ invariants: true
7
+ ---
8
+
9
+ # Banner — agent-only invariants
10
+
11
+ `<orio-banner>` is a plain styled `<div>` for page-level notices. No icon,
12
+ no close button, no auto-dismiss — bring your own.
13
+
14
+ ## Invariants
15
+
16
+ - **`variant`**: `"info"` (default), `"success"`, `"alert"`, `"danger"`.
17
+ Each maps to a soft-background + matching border color from the
18
+ project's color tokens.
19
+ - **Default slot is the entire body.** Text, links, action buttons —
20
+ whatever fits the layout.
21
+ - **Padding (`0.75rem 1rem`) and border-radius (`--border-radius-sm`)
22
+ are fixed.** No size variants.
23
+
24
+ ## Gotchas
25
+
26
+ - **No semantic role / ARIA live region.** The `<div>` has no
27
+ `role="alert"` or `role="status"`. For screen-reader-announced
28
+ notices, add `role="alert"` (urgent) or `role="status"` (polite) via
29
+ `$attrs`.
30
+ - **No close button.** Pair with `v-if` on the consumer side for
31
+ dismissable banners.
32
+ - **No icon prop.** Render `<orio-icon>` inside the slot if needed —
33
+ the banner does not auto-prepend a variant-matching glyph.
34
+
35
+ ## Quick reference
36
+
37
+ ```vue
38
+ <template>
39
+ <orio-banner variant="alert" role="alert" v-if="paymentFailed">
40
+ <strong>{{ $t("billing.failed.title") }}</strong>
41
+ <orio-button variant="subdued" @click="retry">
42
+ {{ $t("billing.failed.retry") }}
43
+ </orio-button>
44
+ </orio-banner>
45
+ </template>
46
+ ```
47
+
48
+ ## Related
49
+
50
+ - `<orio-empty-state>` — for empty-list placeholders.
51
+ - `<orio-tooltip>` — for transient hover hints.
52
+ - Public API reference: `docs/components/banner.md`.
@@ -0,0 +1,83 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: button, primary action, CTA, icon button, action button
5
+ short: primary action button with variants, loading, icon slots, and auto icon-only sizing
6
+ invariants: true
7
+ ---
8
+
9
+ # Button — agent-only invariants
10
+
11
+ `<orio-button>` is the primary action button. Wraps `<orio-control-element>`
12
+ so it can carry a label/error like other form controls, but it is most
13
+ often used standalone.
14
+
15
+ ## Invariants
16
+
17
+ - **`variant`**: `"primary"` (default), `"secondary"`, `"subdued"`. Primary
18
+ is filled; secondary is outline; subdued is bare text. All use the
19
+ global `gradient-hover` class for the hover treatment.
20
+ - **`icon` prop OR `#icon` slot**: pass an icon name (registered in
21
+ `utils/iconRegistry`) OR slot in arbitrary content before the label.
22
+ Slot wins.
23
+ - **`#icon-right` slot**: trailing icon, rendered after the default slot.
24
+ - **`loading` prop**: renders `<orio-loading-spinner>` in place of all
25
+ slot content (icon, label, icon-right are all hidden). Clicks and
26
+ mousedown are blocked while loading.
27
+ - **Icon-only mode is auto-detected.** When an icon is present and the
28
+ default slot is empty, `.icon-only` is applied → `aspect-ratio: 1`,
29
+ `line-height: 0`. No need to pass a prop.
30
+ - **`pill` prop**: `false` by default. When `true`, swaps the button's
31
+ `border-radius` to `var(--border-radius-pill)` (fully rounded),
32
+ overriding the size-driven `--control-radius`. No effect when omitted.
33
+ - **`disabled` blocks `click` and `mousedown` emits.** `mouseup` and
34
+ `mouseleave` always fire (so press-and-hold callers can release
35
+ state).
36
+ - **Emits**: `click`, `mousedown`, `mouseup`, `mouseleave`. Only `click`
37
+ and `mousedown` honor the loading/disabled gates.
38
+ - **Wraps ControlElement** — supports `label`, `error`, `size`, `layout`,
39
+ etc. The control bag is spread on the inner `<button>` along with
40
+ `$attrs`.
41
+
42
+ ## Gotchas
43
+
44
+ - **`$attrs` are duplicated.** Because ControlElement does **not** set
45
+ `inheritAttrs: false` and Button also spreads `$attrs` on the inner
46
+ `<button>`, an attr like `data-test="x"` may appear on both the
47
+ wrapper `<div>` (from ControlElement's root) and the inner `<button>`.
48
+ For attrs that must be unique (e.g. `data-key` for list keying,
49
+ `tabindex` override), bind a plain native `<button>` instead of
50
+ `<orio-button>`.
51
+ - **`type` defaults to `submit`** (native default). Inside an
52
+ `<orio-form>`, every `<orio-button>` will submit unless you pass
53
+ `type="button"`.
54
+ - **Loading hides the icon and the icon-right slot.** No way to keep
55
+ the trailing icon while showing a spinner — render the spinner
56
+ yourself in `#icon-right` if you need that.
57
+ - **No `aria-busy` on loading.** Set it via `$attrs` if you need
58
+ screen-reader signal.
59
+
60
+ ## Quick reference
61
+
62
+ ```vue
63
+ <template>
64
+ <orio-button @click="onSave" :loading="saving">
65
+ {{ $t("common.save") }}
66
+ </orio-button>
67
+
68
+ <orio-button variant="secondary" icon="trash" @click="onDelete">
69
+ {{ $t("common.delete") }}
70
+ </orio-button>
71
+
72
+ <orio-button variant="subdued" icon="close" aria-label="Close" />
73
+
74
+ <orio-button pill icon="plus" aria-label="Add" />
75
+ </template>
76
+ ```
77
+
78
+ ## Related
79
+
80
+ - `<orio-nav-button>` — link-styled button with `active` state.
81
+ - `<orio-loading-spinner>` — used internally when `loading` is true.
82
+ - `<orio-icon>` — used internally for the `icon` prop and slots.
83
+ - Public API reference: `docs/components/button.md`.
@@ -3,6 +3,7 @@ interface Props extends ControlProps {
3
3
  variant?: "primary" | "secondary" | "subdued";
4
4
  icon?: string;
5
5
  loading?: boolean;
6
+ pill?: boolean;
6
7
  }
7
8
  declare var __VLS_13: {}, __VLS_20: {}, __VLS_22: {};
8
9
  type __VLS_Slots = {} & {
@@ -4,6 +4,7 @@ const props = defineProps({
4
4
  variant: { type: String, required: false, default: "primary" },
5
5
  icon: { type: String, required: false },
6
6
  loading: { type: Boolean, required: false },
7
+ pill: { type: Boolean, required: false },
7
8
  appearance: { type: String, required: false },
8
9
  error: { type: [String, null], required: false },
9
10
  group: { type: Boolean, required: false },
@@ -47,7 +48,7 @@ function onMouseleave(event) {
47
48
  <orio-control-element v-slot="{ control }" v-bind="props">
48
49
  <button
49
50
  v-bind="{ ...$attrs, ...control }"
50
- :class="[variant, 'gradient-hover', { 'icon-only': isIconOnly }]"
51
+ :class="[variant, 'gradient-hover', { 'icon-only': isIconOnly, pill }]"
51
52
  @click="click"
52
53
  @mousedown="onMousedown"
53
54
  @mouseup="onMouseup"
@@ -85,6 +86,9 @@ button.icon-only {
85
86
  line-height: 0;
86
87
  aspect-ratio: 1;
87
88
  }
89
+ button.pill {
90
+ border-radius: var(--border-radius-pill);
91
+ }
88
92
  button:disabled, button:disabled:hover {
89
93
  background-color: var(--color-accent-soft-base);
90
94
  color: var(--color-muted);
@@ -3,6 +3,7 @@ interface Props extends ControlProps {
3
3
  variant?: "primary" | "secondary" | "subdued";
4
4
  icon?: string;
5
5
  loading?: boolean;
6
+ pill?: boolean;
6
7
  }
7
8
  declare var __VLS_13: {}, __VLS_20: {}, __VLS_22: {};
8
9
  type __VLS_Slots = {} & {
@@ -1,3 +1,11 @@
1
+ ---
2
+ kind: component
3
+ category: Date
4
+ purpose: month calendar, date grid, day picker UI
5
+ short: month grid with roving-focus keyboard a11y
6
+ invariants: true
7
+ ---
8
+
1
9
  # Calendar — agent-only invariants
2
10
 
3
11
  `<orio-calendar>` is the month-grid primitive. `date/Picker.vue` and
@@ -1,3 +1,11 @@
1
+ ---
2
+ kind: component
3
+ category: Layout & containers
4
+ purpose: canvas, drawing board, whiteboard, sketch, freeform editor, pluggable tools
5
+ short: pannable workspace with pluggable tools and detached toolbar registry
6
+ invariants: true
7
+ ---
8
+
1
9
  # Canvas — agent-only invariants
2
10
 
3
11
  Read this before integrating `<orio-canvas>` into a consumer app. Public API
@@ -0,0 +1,63 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: single checkbox, boolean toggle, opt-in
5
+ short: single boolean checkbox wrapping ControlElement; custom check icon via prop or slot
6
+ invariants: true
7
+ ---
8
+
9
+ # CheckBox — agent-only invariants
10
+
11
+ `<orio-check-box>` is a single boolean checkbox. The native `<input
12
+ type="checkbox">` is visually hidden; the rendered tick lives in a sibling
13
+ `<span class="checkbox-box">`. Read `ControlElement.USAGE.md` first.
14
+
15
+ ## Invariants
16
+
17
+ - **v-model is `boolean`**, not required (renders as unchecked when
18
+ unbound).
19
+ - **Default slot is the label text** rendered to the right of the box.
20
+ - **Default tick is a CSS rotate-45-border checkmark** drawn in
21
+ `::after`, applied when no `checkedIcon` is passed.
22
+ - **`checkedIcon` / `uncheckedIcon` props** swap in `<orio-icon>` glyphs
23
+ for either state. Pass icon names registered in `utils/iconRegistry`.
24
+ - **`#icon` slot** lets you render arbitrary indicator content; receives
25
+ `{ checked }`. Overrides both the default tick and any icon props.
26
+ - **ControlElement is passed `fill`** so the checkbox row fills the wrapper
27
+ width. Label slot of ControlElement is bypassed — the visible label is
28
+ the CheckBox's own default slot, not ControlElement's `label` prop.
29
+ - **`--box-size` defaults to `var(--control-icon-size, 1rem)`**. Override
30
+ the CSS var on the host to resize the box.
31
+
32
+ ## Gotchas
33
+
34
+ - **The native input has `tabindex="-1"`** in the template, but the
35
+ styles target `:focus-visible` on it. Keyboard focus for the checkbox
36
+ may not behave the way an a11y audit expects — confirm tab order
37
+ before shipping critical forms. If you need keyboard-focusable
38
+ checkboxes, override `tabindex` via `$attrs`.
39
+ - **`required` from `ControlProps` flows through**, but native checkbox
40
+ required validation only fires inside a `<form>` that calls
41
+ `reportValidity`.
42
+ - **No indeterminate state.** v-model is strictly boolean; the
43
+ underlying input never gets `indeterminate = true`.
44
+ - **Passing both `checkedIcon` and `#icon` slot** — the slot wins; the
45
+ prop becomes dead code.
46
+
47
+ ## Quick reference
48
+
49
+ ```vue
50
+ <template>
51
+ <orio-check-box v-model="agreed" :error="errors.terms">
52
+ {{ $t("signup.acceptTerms") }}
53
+ </orio-check-box>
54
+
55
+ <orio-check-box v-model="bookmarked" checked-icon="bookmark-filled" unchecked-icon="bookmark" />
56
+ </template>
57
+ ```
58
+
59
+ ## Related
60
+
61
+ - `<orio-checkbox-group>` — multi-value group of checkboxes.
62
+ - `<orio-control-element>` — wrapper; owns label/error/a11y.
63
+ - Public API reference: `docs/components/checkbox.md`.
@@ -0,0 +1,95 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: group of checkboxes, multi-value boolean group, multi-select boolean
5
+ short: group of CheckBox children bound to an `unknown[]` v-model; supports `options` prop or default slot
6
+ invariants: true
7
+ ---
8
+
9
+ # CheckboxGroup — agent-only invariants
10
+
11
+ `<orio-checkbox-group>` wires a list of checkboxes to a single array
12
+ v-model. It uses `<orio-control-element group>` so the wrapper renders as
13
+ a `role="group"` with an `aria-labelledby` label — semantically a fieldset
14
+ group, not a `<fieldset>` element.
15
+
16
+ ## Invariants
17
+
18
+ - **v-model is `unknown[]`** (default `[]`). Each entry is one option's
19
+ `value`. Comparison is strict `===` — primitives or shared references,
20
+ not deep equality.
21
+ - **Two modes**: `options` prop (array of `{ label, value }`) **or** the
22
+ default slot (you render `<orio-check-box>` children yourself). The slot
23
+ wins when present.
24
+ - **In `options` mode**, each rendered checkbox is `appearance="minimal"`
25
+ (hardcoded). To change appearance, switch to the slot.
26
+ - **Toggle replaces the array**: `modelValue.value = modelValue.value.filter(...)`
27
+ / `[...modelValue.value, value]`. Reactive arrays survive; readonly arrays
28
+ break silently.
29
+ - **Wrapper omits `appearance`, `group`, `id` from `ControlProps`.**
30
+ `group` is forced true; `id` is internal.
31
+ - **Defaults**: `layout: "vertical"`, `size: "md"`, `error: null`.
32
+ - **Horizontal layout aligns label top** (`align-items: flex-start`) so the
33
+ label sits next to the first checkbox, not centered against the full
34
+ column.
35
+
36
+ ## Gotchas
37
+
38
+ - **`isChecked` uses `Array.includes` with `===`.** Object option values
39
+ must be the **same reference** as in the model, not a structural match.
40
+ For object values, store ids and resolve to objects in the parent.
41
+ - **No "select all" / "indeterminate parent" affordance.** Build it in the
42
+ consumer if needed.
43
+ - **Label rendering depends on `group` mode in ControlElement** — the
44
+ label becomes a `<span>` with `aria-labelledby` wiring, not a `<legend>`.
45
+ Screen readers announce it as a group label.
46
+ - **`error` is forwarded** to the group wrapper; per-checkbox errors are
47
+ not supported. Surface validation at the group level.
48
+
49
+ ## Quick reference — options prop
50
+
51
+ ```vue
52
+ <script setup lang="ts">
53
+ const interests = defineModel<string[]>({ default: () => [] });
54
+ const options = [
55
+ { label: "Books", value: "books" },
56
+ { label: "Films", value: "films" },
57
+ { label: "Music", value: "music" },
58
+ ];
59
+ </script>
60
+
61
+ <template>
62
+ <orio-checkbox-group
63
+ v-model="interests"
64
+ :options="options"
65
+ :label="$t('profile.interests')"
66
+ />
67
+ </template>
68
+ ```
69
+
70
+ ## Quick reference — slot mode (custom rendering)
71
+
72
+ ```vue
73
+ <template>
74
+ <orio-checkbox-group v-model="features" :label="$t('settings.features')">
75
+ <orio-check-box
76
+ v-for="feature in availableFeatures"
77
+ :key="feature.id"
78
+ :model-value="features.includes(feature.id)"
79
+ checked-icon="star-filled"
80
+ unchecked-icon="star"
81
+ @update:model-value="toggle(feature.id)"
82
+ >
83
+ {{ feature.name }}
84
+ </orio-check-box>
85
+ </orio-checkbox-group>
86
+ </template>
87
+ ```
88
+
89
+ ## Related
90
+
91
+ - `<orio-check-box>` — single checkbox; rendered children inside this
92
+ group.
93
+ - `<orio-control-element>` (`group` mode) — wrapper; provides the group
94
+ label semantics.
95
+ - Public API reference: `docs/components/checkbox-group.md`.
@@ -1,3 +1,11 @@
1
+ ---
2
+ kind: component
3
+ category: Form inputs
4
+ purpose: label + error + a11y wrapper for any form control
5
+ short: label/legend wrapper, owns a11y attrs, exposes the `control` slot prop bag
6
+ invariants: true
7
+ ---
8
+
1
9
  # ControlElement — agent-only invariants
2
10
 
3
11
  `ControlElement` is the wrapper every form input uses (Input, Textarea,
@@ -1,5 +1,5 @@
1
1
  export type ControlLayout = "vertical" | "horizontal";
2
- export type ControlSize = "sm" | "md" | "lg" | "xl";
2
+ export type ControlSize = "xs" | "sm" | "md" | "lg" | "xl";
3
3
  /**
4
4
  * A11y + form attrs that flow from the caller through ControlElement to the
5
5
  * actual interactive element via the `control` slot prop. Never rendered on
@@ -1,5 +1,5 @@
1
1
  export type ControlLayout = "vertical" | "horizontal";
2
- export type ControlSize = "sm" | "md" | "lg" | "xl";
2
+ export type ControlSize = "xs" | "sm" | "md" | "lg" | "xl";
3
3
  /**
4
4
  * A11y + form attrs that flow from the caller through ControlElement to the
5
5
  * actual interactive element via the `control` slot prop. Never rendered on
@@ -0,0 +1,65 @@
1
+ ---
2
+ kind: component
3
+ category: Layout & containers
4
+ purpose: dashed empty/drop zone, add-item card, upload tile, empty state with action
5
+ short: clickable dashed-border tile with icon and label, used for add/upload affordances
6
+ invariants: true
7
+ ---
8
+
9
+ # DashedContainer — agent-only invariants
10
+
11
+ `<orio-dashed-container>` is a self-styled clickable tile that renders an
12
+ optional icon and a label inside a dashed border. It is **not** a generic
13
+ slot wrapper.
14
+
15
+ ## Invariants
16
+
17
+ - **No default slot.** The template only renders `icon` and `text` props.
18
+ Children placed between the tags are dropped. To compose richer content,
19
+ use a different primitive.
20
+ - **Always clickable.** The wrapper has `cursor: pointer` and emits
21
+ `click` unconditionally — even with no listener attached, the tile looks
22
+ interactive. Do not use as a passive container.
23
+ - **`size` only scales the icon.** `small` → 2rem, `medium` → 3rem, `large`
24
+ → 5rem. Padding (2rem), gap (0.5rem), text size, and border are fixed
25
+ regardless of `size`.
26
+ - **`icon` is forwarded to `<orio-icon :name>`.** Must be a name registered
27
+ in `utils/iconRegistry`. Missing names render nothing; check the registry
28
+ before passing a string.
29
+ - **Hover effect comes from the global `gradient-hover` class**, not from
30
+ scoped styles. The dashed border, padding, and layout are scoped; the
31
+ hover gradient is project-global.
32
+
33
+ ## Gotchas
34
+
35
+ - **`text` defaults to English in consumer code.** Project convention
36
+ (see CLAUDE.md / translations note) is to pass an i18n key: `:text="$t('addItem')"`,
37
+ not `text="Add Item"`.
38
+ - **Both `icon` and `text` are optional.** With neither, the tile is an
39
+ empty dashed box that still emits `click`. Confirm at least one is set
40
+ unless that empty look is intentional.
41
+ - **The `<span :size>` shorthand** in the template forwards `size` as a
42
+ DOM attribute on the label. It's not styled — harmless, but visible
43
+ in devtools.
44
+ - **No `disabled` state.** If a consumer needs disabled-looking behaviour,
45
+ wrap or override styles externally; do not rely on a prop.
46
+
47
+ ## Quick reference
48
+
49
+ ```vue
50
+ <template>
51
+ <orio-dashed-container
52
+ icon="plus"
53
+ :text="$t('gallery.addImage')"
54
+ size="medium"
55
+ @click="openPicker"
56
+ />
57
+ </template>
58
+ ```
59
+
60
+ ## Related
61
+
62
+ - `orio-icon` — icon renderer driven by the same `name` string.
63
+ - `orio-upload` — full file-picker widget; prefer it over hand-rolling
64
+ click-to-upload on a `DashedContainer`.
65
+ - Public API reference: `docs/components/dashed-container.md`.