@peektravel/app-utilities 0.8.2 → 0.9.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/dist/index.cjs +56 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -46
- package/dist/index.d.ts +46 -46
- package/dist/index.js +56 -47
- package/dist/index.js.map +1 -1
- package/llms.txt +4 -3
- package/package.json +1 -2
- package/docs/ui.md +0 -1175
package/docs/ui.md
DELETED
|
@@ -1,1175 +0,0 @@
|
|
|
1
|
-
# Odyssey Web Components — `@peektravel/app-utilities/ui`
|
|
2
|
-
|
|
3
|
-
AI reference for the Odyssey UI components shipped under the `/ui` subpath. This
|
|
4
|
-
is the **guide**; the authoritative, always-current contract is
|
|
5
|
-
`dist/ui/index.d.ts` — read its TSDoc via go-to-definition before relying on any
|
|
6
|
-
shape here. Document only what this file describes; never invent attributes,
|
|
7
|
-
properties, or events.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 1. Overview
|
|
12
|
-
|
|
13
|
-
- **Framework-agnostic Custom Elements** (`<ody-*>`). Work in plain HTML, React,
|
|
14
|
-
Vue, Angular, Svelte — anything that renders DOM.
|
|
15
|
-
- **Light DOM** (no Shadow DOM). The shipped `odyssey.css` global classes style
|
|
16
|
-
them; consuming apps can override with the same selectors.
|
|
17
|
-
- **Dependency-free.** Native `Date`, `Intl`, `fetch`/`navigator.clipboard`,
|
|
18
|
-
`customElements`. No date or popup libraries.
|
|
19
|
-
- **Ported from the Peek Odyssey design system** (originally an Ember addon).
|
|
20
|
-
- Importing the module **registers** every element as a side effect; the named
|
|
21
|
-
exports (classes + types) are only for subclassing or type-checking.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 2. Setup
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
// Registers every <ody-*> element as a side effect (do this once, app entry):
|
|
29
|
-
import '@peektravel/app-utilities/ui';
|
|
30
|
-
// Design tokens (CSS custom properties — colours, fonts, z-index):
|
|
31
|
-
import '@peektravel/app-utilities/ui/tokens.css';
|
|
32
|
-
// Component styles:
|
|
33
|
-
import '@peektravel/app-utilities/ui/odyssey.css';
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
CSS can also be linked instead of imported:
|
|
37
|
-
|
|
38
|
-
```html
|
|
39
|
-
<link rel="stylesheet" href="…/@peektravel/app-utilities/ui/tokens.css" />
|
|
40
|
-
<link rel="stylesheet" href="…/@peektravel/app-utilities/ui/odyssey.css" />
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Registration is **idempotent** — importing the module more than once (or from
|
|
44
|
-
more than one bundle) re-registers nothing and never throws, so lazy or
|
|
45
|
-
duplicated imports are safe.
|
|
46
|
-
|
|
47
|
-
If you register the bundle **lazily** (e.g. a dynamic `import()` to dodge SSR /
|
|
48
|
-
hydration mismatches), two things stop un-upgraded elements from flashing as
|
|
49
|
-
their raw text content (`<ody-button>Save</ody-button>` briefly showing the bare
|
|
50
|
-
word "Save"):
|
|
51
|
-
|
|
52
|
-
- `odyssey.css` ships a `:not(:defined) { visibility: hidden }` guard, so every
|
|
53
|
-
`<ody-*>` stays invisible — layout preserved, no reflow — until it upgrades.
|
|
54
|
-
Load the CSS **eagerly** so this applies before first paint. It has zero
|
|
55
|
-
specificity, so you can override it for a deliberate fallback.
|
|
56
|
-
- `whenOdysseyReady()` resolves once every imported element has upgraded, so you
|
|
57
|
-
can gate your first render on it:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import { whenOdysseyReady } from '@peektravel/app-utilities/ui';
|
|
61
|
-
await whenOdysseyReady();
|
|
62
|
-
// …now render your <ody-*> tree
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
A *failed* dynamic import rejects at the `import()` call site — handle load
|
|
66
|
-
failures there; `whenOdysseyReady()` reflects only the elements that did
|
|
67
|
-
register. `registeredTags()` returns the registered tag list if you need to
|
|
68
|
-
introspect it.
|
|
69
|
-
|
|
70
|
-
Named exports for advanced use:
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
import {
|
|
74
|
-
toast, registerTranslation, registerIcon,
|
|
75
|
-
iconNames, iconSvg, brandIconNames, brandIconSvg,
|
|
76
|
-
OdyTable, type OdyTableColumn, // …classes + types
|
|
77
|
-
} from '@peektravel/app-utilities/ui';
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 2.1 Spacing & layout tokens
|
|
81
|
-
|
|
82
|
-
`tokens.css` publishes the canonical spacing scale and the two page widths.
|
|
83
|
-
Reach for these instead of magic numbers so spacing stays consistent across
|
|
84
|
-
apps even for one-off gaps:
|
|
85
|
-
|
|
86
|
-
| Token | Value | Use for |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
| `--gap8` | `8px` | tight gaps |
|
|
89
|
-
| `--gap16` | `16px` | the default gap between elements |
|
|
90
|
-
| `--gap24` | `24px` | section-level spacing / page gutters |
|
|
91
|
-
| `--gap32` | `32px` | large section spacing |
|
|
92
|
-
| `--layout-page-width-narrow` | `868px` | the narrow settings-iframe width (design for this first) |
|
|
93
|
-
| `--layout-page-width-wide` | `1310px` | the wide settings-iframe width |
|
|
94
|
-
|
|
95
|
-
```css
|
|
96
|
-
.my-card { padding: var(--gap24); gap: var(--gap16); }
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The layout primitives already bake these in: `ody-app-page-container` uses the
|
|
100
|
-
gap tokens for its responsive gutter, and `ody-section-*` / `ody-horizontal-divider`
|
|
101
|
-
expose them through `gap-size` / `spacing` attributes.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## 3. Core conventions (read this — it is how all components work)
|
|
106
|
-
|
|
107
|
-
### 3.1 Scalar config = HTML attributes
|
|
108
|
-
Strings and booleans are **attributes**. Boolean attributes are "on" when
|
|
109
|
-
present (and not literally `="false"`):
|
|
110
|
-
|
|
111
|
-
```html
|
|
112
|
-
<ody-button variant="primary" size="small" disabled>Save</ody-button>
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### 3.2 Rich data = JS properties (cannot be attributes)
|
|
116
|
-
Arrays, objects, and functions are set on the **element object**, not as
|
|
117
|
-
attributes:
|
|
118
|
-
|
|
119
|
-
```js
|
|
120
|
-
const table = document.querySelector('ody-table');
|
|
121
|
-
table.columns = [{ key: 'name', label: 'Name' }]; // array
|
|
122
|
-
table.data = [{ name: 'Alice' }]; // array
|
|
123
|
-
datepicker.isDateDisallowed = (d) => d.getDay() === 0; // function
|
|
124
|
-
datepicker.formatDate = (d) => d.toDateString(); // function
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Several composite components also accept a **JSON-string attribute fallback** so
|
|
128
|
-
they work declaratively in plain HTML:
|
|
129
|
-
|
|
130
|
-
| Component | JS property | JSON attribute fallback |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| `ody-dropdown-single` / `ody-dropdown-multi` / `ody-checkbox-group` | `el.options = [...]` | `options='[{"value":"a","label":"A"}]'` |
|
|
133
|
-
| `ody-tabs` | — | `tabs='[{"id":"a","label":"A"}]'` |
|
|
134
|
-
| `ody-toggle-button` | — | `options='[{"value":"a","label":"A"}]'` |
|
|
135
|
-
| `ody-radio-button-group` | `el.options` (getter) | `options='[{"value":"a","label":"A"}]'` |
|
|
136
|
-
| `ody-datepicker` | — | `presets='[{"label":"…","value":"…"}]'` |
|
|
137
|
-
| `ody-table` | `el.columns`, `el.data` | none — JS only |
|
|
138
|
-
|
|
139
|
-
### 3.3 Output = `CustomEvent` (read `event.detail`)
|
|
140
|
-
Components emit bubbling `CustomEvent`s. Listen with `addEventListener`; read
|
|
141
|
-
data from `event.detail`. Event names are **custom** (e.g. `change`,
|
|
142
|
-
`sort-change`) — see each component.
|
|
143
|
-
|
|
144
|
-
```js
|
|
145
|
-
el.addEventListener('change', (e) => console.log(e.detail.value));
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 3.4 Content = light-DOM children (the default slot)
|
|
149
|
-
Most components render your child nodes into an internal `[data-ody-slot]`
|
|
150
|
-
placeholder. So the element's children become its body/label content:
|
|
151
|
-
|
|
152
|
-
```html
|
|
153
|
-
<ody-button>This text is the label</ody-button>
|
|
154
|
-
<ody-alert heading="Saved">This is the body content.</ody-alert>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Although your children are physically relocated into that placeholder, the host
|
|
158
|
-
element forwards the standard child-mutation calls
|
|
159
|
-
(`appendChild` / `insertBefore` / `removeChild` / `replaceChild`) to the slot.
|
|
160
|
-
So a framework reconciler that adds, removes, reorders, or swaps a **direct**
|
|
161
|
-
child of a slotting element operates on the slot correctly — conditional,
|
|
162
|
-
keyed, and `.map()`-ed children all reconcile without a
|
|
163
|
-
`NotFoundError: … not a child of this node`. You do **not** need to wrap dynamic
|
|
164
|
-
children in a single stable `<div>`.
|
|
165
|
-
|
|
166
|
-
### 3.5 Framework usage
|
|
167
|
-
|
|
168
|
-
**Vanilla HTML / JS** — declarative; set rich data and add listeners via the
|
|
169
|
-
element reference.
|
|
170
|
-
|
|
171
|
-
**React < 19** does not set DOM properties or bind custom events from JSX
|
|
172
|
-
attributes. Use lowercase tag names, a `ref` to set properties, and
|
|
173
|
-
`addEventListener` for events:
|
|
174
|
-
|
|
175
|
-
```jsx
|
|
176
|
-
function Guests({ columns, rows, onSort }) {
|
|
177
|
-
const ref = useRef(null);
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
const el = ref.current;
|
|
180
|
-
el.columns = columns; // JS property
|
|
181
|
-
el.data = rows; // JS property
|
|
182
|
-
const handler = (e) => onSort(e.detail); // { key, direction }
|
|
183
|
-
el.addEventListener('sort-change', handler);
|
|
184
|
-
return () => el.removeEventListener('sort-change', handler);
|
|
185
|
-
}, [columns, rows, onSort]);
|
|
186
|
-
return <ody-table ref={ref} selectable sortable />;
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
**React 19** sets JSX props on a custom element as DOM **properties**
|
|
191
|
-
(`el.searchable = true`), not attributes. The components are built for this: every
|
|
192
|
-
reflected/read-only accessor accepts assignment, so passing props directly works
|
|
193
|
-
and does **not** throw the `Cannot set property … which has only a getter` error
|
|
194
|
-
a getter-only property would otherwise raise. Concretely:
|
|
195
|
-
|
|
196
|
-
- **Boolean props reflect to the attribute.** `searchable`, `disabled`, etc.
|
|
197
|
-
take effect as attribute presence:
|
|
198
|
-
|
|
199
|
-
```jsx
|
|
200
|
-
<ody-dropdown-single searchable options={items} value={value} />
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
- **Rich-data props are real properties.** `options` (dropdowns, checkbox-group,
|
|
204
|
-
radio-button-group, toggle-button), `presets` (datepicker), and `data` /
|
|
205
|
-
`columns` / `rows` (table) are true getter+setter properties. Pass an **array**
|
|
206
|
-
directly (`options={items}`); a JSON **string** is also accepted for
|
|
207
|
-
backwards-compatibility. They are the source of truth and are **not** reflected
|
|
208
|
-
back onto a DOM attribute (so no giant JSON attributes).
|
|
209
|
-
|
|
210
|
-
- **Controlled `value` never fights the caret.** The text/number fields
|
|
211
|
-
(`ody-input`, `ody-inline-input`, `ody-search-input`, `ody-money-input`,
|
|
212
|
-
`ody-percentage-input`) reflect an externally-set `value` into the live control
|
|
213
|
-
in place, and the selection controls update their checked state in place — so a
|
|
214
|
-
controlled binding keeps focus. `ody-checkbox-group`'s `value` **attribute** is
|
|
215
|
-
a JSON array (comma-safe); its `.value` **property** and the `change` detail are
|
|
216
|
-
a `string[]`.
|
|
217
|
-
|
|
218
|
-
- **Read-only state accessors ignore assignment.** Props that mirror live
|
|
219
|
-
internal state (`isOpen`, `isVisible`) are inert to writes — they are not
|
|
220
|
-
controlled by re-render. Drive them imperatively through a `ref`
|
|
221
|
-
(`ref.current.openPopover()` / `.show()`) and observe changes via the
|
|
222
|
-
component's `CustomEvent`s.
|
|
223
|
-
|
|
224
|
-
TypeScript still types these accessors as read-only (the setters are a runtime
|
|
225
|
-
safety net for framework-driven assignment), so `el.isOpen = true` in TS code is
|
|
226
|
-
flagged — assign only via JSX props / refs as above.
|
|
227
|
-
|
|
228
|
-
**Vue / Angular** support custom elements natively: bind properties with
|
|
229
|
-
`:prop` / `[prop]` and events with `@event` / `(event)`. (Configure Vue's
|
|
230
|
-
`compilerOptions.isCustomElement` / Angular's `CUSTOM_ELEMENTS_SCHEMA`.)
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## 4. When to use what
|
|
235
|
-
|
|
236
|
-
| Need | Use |
|
|
237
|
-
|---|---|
|
|
238
|
-
| Action / submit | `ody-button`; primary + menu → `ody-split-button` |
|
|
239
|
-
| Single choice from a list | `ody-dropdown-single` (or native `<select>`) |
|
|
240
|
-
| Multiple choices from a list | `ody-dropdown-multi` (or native `<select multiple>`) |
|
|
241
|
-
| Mutually-exclusive segmented control | `ody-toggle-button` or `ody-radio-button-group` |
|
|
242
|
-
| Several independent toggles | `ody-checkbox-group` (with optional select-all) |
|
|
243
|
-
| Single boolean | `ody-checkbox` |
|
|
244
|
-
| Free text / multi-line | `ody-input` (`textarea`), compact → `ody-inline-input` |
|
|
245
|
-
| Search field | `ody-search-input` |
|
|
246
|
-
| Currency amount | `ody-money-input` |
|
|
247
|
-
| Percentage 0–100 | `ody-percentage-input` |
|
|
248
|
-
| Date / date range | `ody-datepicker` |
|
|
249
|
-
| Tabular data, sorting, selection | `ody-table` |
|
|
250
|
-
| Tab navigation | `ody-tabs` |
|
|
251
|
-
| Transient feedback | `toast()` (+ `ody-toast-host`) |
|
|
252
|
-
| Confirm / blocking dialog | `ody-modal` |
|
|
253
|
-
| Slide-in side detail panel | `ody-panel` |
|
|
254
|
-
| Click-to-open floating menu | `ody-popover` |
|
|
255
|
-
| Hover/focus hint | `ody-tooltip` |
|
|
256
|
-
| Status / contextual message | `ody-alert`, `ody-message`, `ody-status-dot`, `ody-check-in-status` |
|
|
257
|
-
| Labels / pills | `ody-tag` |
|
|
258
|
-
| Empty / error / no-results view | `ody-empty-state` |
|
|
259
|
-
| KPI figures | `ody-stat-summary` + `ody-stat` |
|
|
260
|
-
| Expand/collapse | `ody-accordion`, `ody-collapsible`, `ody-collapsible-section` |
|
|
261
|
-
| Full-page wrapper for an **app settings UI** | `ody-app-page-container` (required — see below; `ody-page-container` is the deprecated full-bleed predecessor) |
|
|
262
|
-
| Master/detail page layout | `ody-two-column` |
|
|
263
|
-
| Row/column section layout | `ody-section-rows` / `ody-section-columns` |
|
|
264
|
-
| Progress | `ody-loading-bar`, `ody-loading-spinner` |
|
|
265
|
-
| Themeable icon | `ody-icon`; brand/logo art → `ody-brand-icon` |
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## 5. Component catalog
|
|
270
|
-
|
|
271
|
-
Conventions below: **Attributes** are HTML attributes; **Properties (JS)** must
|
|
272
|
-
be set on the element object; **Events** bubble and are read via `event.detail`;
|
|
273
|
-
**Slots / content** describes what child nodes do. Defaults are noted.
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
### Display tier
|
|
278
|
-
|
|
279
|
-
#### `<ody-icon>`
|
|
280
|
-
**Use when** you need a themeable inline SVG that inherits text colour.
|
|
281
|
-
**Attributes**
|
|
282
|
-
- `name` — icon name (see `iconNames()`); unknown names render nothing.
|
|
283
|
-
- `size` — `extra-small` | `mid-small` | `small` | `base` | `medium` | `large` | `free` (default `base`).
|
|
284
|
-
- `disabled` — boolean; dims the icon.
|
|
285
|
-
**Example**
|
|
286
|
-
```html
|
|
287
|
-
<ody-icon name="search" size="medium"></ody-icon>
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
#### `<ody-button>`
|
|
291
|
-
**Use when** triggering an action. Label is the child content.
|
|
292
|
-
**Attributes**
|
|
293
|
-
- `variant` — `primary` | `secondary` | `ghost` | `tertiary` | `danger` (default `primary`).
|
|
294
|
-
- `appearance` — `interaction` | `success` | `danger` | `grey` (default `interaction`).
|
|
295
|
-
- `size` — `base` | `small` (default `base`).
|
|
296
|
-
- `type` — `button` | `submit` (default `button`).
|
|
297
|
-
- `left-icon` / `right-icon` — icon names beside the label.
|
|
298
|
-
- `loading`, `icon-only`, `active`, `disabled`, `icon-rotate` — boolean flags.
|
|
299
|
-
**Slots / content** — child nodes are the label.
|
|
300
|
-
**Example**
|
|
301
|
-
```html
|
|
302
|
-
<ody-button variant="primary" left-icon="plus">Add item</ody-button>
|
|
303
|
-
<ody-button icon-only left-icon="search" aria-label="Search"></ody-button>
|
|
304
|
-
```
|
|
305
|
-
*(No custom event — listen for native `click`.)*
|
|
306
|
-
|
|
307
|
-
#### `<ody-tag>`
|
|
308
|
-
**Use when** showing a status pill / label. Label is the child content.
|
|
309
|
-
**Attributes**
|
|
310
|
-
- `variant` — `primary` (filled) | `secondary` (outline) (default `primary`).
|
|
311
|
-
- `color` — `default` | `info` | `success` | `warning` | `danger` | `teal` | `lime` | `purple` | `pink` | `turquoise` | `yellow` | `orange` | `rose` | `blue` (default `default`).
|
|
312
|
-
- `size` — `base` | `small` (default `base`).
|
|
313
|
-
- `icon` — optional leading icon name.
|
|
314
|
-
- `count` — optional trailing count.
|
|
315
|
-
**Example**
|
|
316
|
-
```html
|
|
317
|
-
<ody-tag color="success" icon="check">Confirmed</ody-tag>
|
|
318
|
-
<ody-tag variant="secondary" color="purple" count="4">Tasks</ody-tag>
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### `<ody-alert>`
|
|
322
|
-
**Use when** showing a contextual message box. Body is the child content.
|
|
323
|
-
**Attributes**
|
|
324
|
-
- `variant` — `info` | `success` | `warning` | `danger` (default `info`).
|
|
325
|
-
- `heading` — optional bold heading beside the icon.
|
|
326
|
-
**Slots / content** — child nodes are the body.
|
|
327
|
-
**Example**
|
|
328
|
-
```html
|
|
329
|
-
<ody-alert variant="warning" heading="Careful">This cannot be undone.</ody-alert>
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
#### `<ody-card>`
|
|
333
|
-
**Use when** grouping content in a bordered container. Reach for the plain
|
|
334
|
-
`no-bar` card by default; add a left accent bar only when the bar's colour
|
|
335
|
-
carries meaning (e.g. status).
|
|
336
|
-
**Attributes**
|
|
337
|
-
- `no-bar` — boolean; omits the accent bar, rendering a plain bordered
|
|
338
|
-
container. **This is the default card to use.**
|
|
339
|
-
- `bar-color` — CSS colour for the accent bar (default neutral-200). Ignored
|
|
340
|
-
when `no-bar` is set.
|
|
341
|
-
- `clickable` — boolean; adds pointer affordance (handle `click` yourself).
|
|
342
|
-
**Slots / content** — child nodes are the card content.
|
|
343
|
-
**Example**
|
|
344
|
-
```html
|
|
345
|
-
<!-- Default: plain bordered card -->
|
|
346
|
-
<ody-card no-bar><p>Card body</p></ody-card>
|
|
347
|
-
|
|
348
|
-
<!-- Accent bar when the colour is meaningful -->
|
|
349
|
-
<ody-card bar-color="var(--color-interaction-300)"><p>Card body</p></ody-card>
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
#### `<ody-horizontal-divider>`
|
|
353
|
-
**Use when** you need a 1px horizontal rule that separates content. Unlike the
|
|
354
|
-
deprecated `<ody-divider>`, it carries its own vertical rhythm
|
|
355
|
-
(`margin-block: var(--gap16)`) so you don't have to add spacing around it.
|
|
356
|
-
**Attributes**
|
|
357
|
-
- `spacing` — `tight` (`--gap8`) | `none` (`0`); default is the normal `--gap16`.
|
|
358
|
-
```html
|
|
359
|
-
<ody-horizontal-divider></ody-horizontal-divider>
|
|
360
|
-
<ody-horizontal-divider spacing="tight"></ody-horizontal-divider>
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
#### `<ody-divider>` _(deprecated — use `<ody-horizontal-divider>`)_
|
|
364
|
-
**Use when** you need a 1px horizontal rule. No attributes, no content.
|
|
365
|
-
**Deprecated:** renders with **zero** margin, so every caller has to add its own
|
|
366
|
-
spacing. Prefer `<ody-horizontal-divider>`. Still works and unchanged.
|
|
367
|
-
```html
|
|
368
|
-
<ody-divider></ody-divider>
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
#### `<ody-status-dot>`
|
|
372
|
-
**Use when** showing a coloured dot beside a label. Label is the child content.
|
|
373
|
-
**Attributes**
|
|
374
|
-
- `color` — `green` | `blue` | `orange` (default `green`).
|
|
375
|
-
**Example**
|
|
376
|
-
```html
|
|
377
|
-
<ody-status-dot color="blue">Pending</ody-status-dot>
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
#### `<ody-message>`
|
|
381
|
-
**Use when** showing a small inline status message. Text is the child content.
|
|
382
|
-
**Attributes**
|
|
383
|
-
- `icon` — optional leading icon name.
|
|
384
|
-
**Example**
|
|
385
|
-
```html
|
|
386
|
-
<ody-message icon="info">Saved as draft.</ody-message>
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
#### `<ody-loading-spinner>`
|
|
390
|
-
**Use when** indicating indeterminate loading. Optional label is the child content.
|
|
391
|
-
**Attributes**
|
|
392
|
-
- `size` — `small` | `base` | `large` (default `base`).
|
|
393
|
-
**Example**
|
|
394
|
-
```html
|
|
395
|
-
<ody-loading-spinner size="large">Loading…</ody-loading-spinner>
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
#### `<ody-loading-bar>`
|
|
399
|
-
**Use when** showing determinate progress.
|
|
400
|
-
**Attributes**
|
|
401
|
-
- `value` — fill percentage, clamped 0–100 (default `0`).
|
|
402
|
-
- `color` — CSS colour for the fill (default interaction-300).
|
|
403
|
-
- `label` — optional caption shown above the bar with the percentage.
|
|
404
|
-
**Example**
|
|
405
|
-
```html
|
|
406
|
-
<ody-loading-bar value="35" label="Uploading photos"></ody-loading-bar>
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
### Layout / structure tier
|
|
412
|
-
|
|
413
|
-
#### `<ody-empty-state>`
|
|
414
|
-
**Use when** rendering an empty / error / no-results placeholder. Child content
|
|
415
|
-
(e.g. an action button) renders below the messages.
|
|
416
|
-
**Attributes**
|
|
417
|
-
- `variant` — `default` | `error` | `no-results` | `no-search` | `not-authorized` (default `default`); selects a fallback icon.
|
|
418
|
-
- `img-src` / `img-alt` — render a custom image instead of the icon.
|
|
419
|
-
- `icon` — override the illustration with a named icon.
|
|
420
|
-
- `label` — bold primary message.
|
|
421
|
-
- `caption` — secondary message.
|
|
422
|
-
**Example**
|
|
423
|
-
```html
|
|
424
|
-
<ody-empty-state variant="no-results" label="No results" caption="Try another search.">
|
|
425
|
-
<ody-button>Clear filters</ody-button>
|
|
426
|
-
</ody-empty-state>
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
#### `<ody-breadcrumb>` + `<ody-breadcrumb-item>`
|
|
430
|
-
**Use when** showing a breadcrumb trail. Compose items as children.
|
|
431
|
-
`ody-breadcrumb` has no attributes (renders the `<nav><ol>` chrome).
|
|
432
|
-
`ody-breadcrumb-item` **Attributes**: `current` — boolean; marks the active page
|
|
433
|
-
(`aria-current="page"`). Each item's child content is the crumb label/link.
|
|
434
|
-
**Example**
|
|
435
|
-
```html
|
|
436
|
-
<ody-breadcrumb>
|
|
437
|
-
<ody-breadcrumb-item><a href="/">Home</a></ody-breadcrumb-item>
|
|
438
|
-
<ody-breadcrumb-item current>Details</ody-breadcrumb-item>
|
|
439
|
-
</ody-breadcrumb>
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
#### `<ody-stat-summary>` + `<ody-stat>` + `<ody-stat-summary-detail>` + `<ody-stat-detail>`
|
|
443
|
-
**Use when** displaying a row of KPI figures with an optional detail strip.
|
|
444
|
-
- `ody-stat-summary` — no attributes; wraps `ody-stat` children.
|
|
445
|
-
- `ody-stat` **Attributes**: `label`, `value` (falls back to child content if omitted), `sub`, `tone` (`default` | `success` | `warning`).
|
|
446
|
-
- `ody-stat-summary-detail` — no attributes; the divider + detail strip; place as a **sibling immediately after** the stat summary; wraps `ody-stat-detail` children.
|
|
447
|
-
- `ody-stat-detail` **Attributes**: `value` (bold value appended after the child-content label).
|
|
448
|
-
**Example**
|
|
449
|
-
```html
|
|
450
|
-
<ody-stat-summary>
|
|
451
|
-
<ody-stat label="Revenue" value="$12,400" sub="last 30 days"></ody-stat>
|
|
452
|
-
<ody-stat label="Bookings" value="312" tone="success"></ody-stat>
|
|
453
|
-
</ody-stat-summary>
|
|
454
|
-
<ody-stat-summary-detail>
|
|
455
|
-
<ody-stat-detail value="$1,030">Avg / booking</ody-stat-detail>
|
|
456
|
-
</ody-stat-summary-detail>
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
#### `<ody-inline-list>` + `<ody-inline-list-item>`
|
|
460
|
-
**Use when** rendering a horizontal separated list. Compose items as children.
|
|
461
|
-
- `ody-inline-list` **Attributes**: `separator` (`line` | `bullet`, default `line`), `gap` (px between items, default `12`).
|
|
462
|
-
- `ody-inline-list-item` — no attributes; child content is the item.
|
|
463
|
-
**Example**
|
|
464
|
-
```html
|
|
465
|
-
<ody-inline-list separator="bullet" gap="8">
|
|
466
|
-
<ody-inline-list-item>Draft</ody-inline-list-item>
|
|
467
|
-
<ody-inline-list-item>Updated 3d ago</ody-inline-list-item>
|
|
468
|
-
</ody-inline-list>
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
#### `<ody-list-item>`
|
|
472
|
-
**Use when** a clickable list row with a trailing chevron. Row content is the
|
|
473
|
-
child content.
|
|
474
|
-
**Attributes**
|
|
475
|
-
- `item-id` — id emitted in the `select` event.
|
|
476
|
-
- `active-item-id` — when equal to `item-id`, the row renders active.
|
|
477
|
-
- `active` — boolean; force the active style.
|
|
478
|
-
**Events**
|
|
479
|
-
- `select` → `{ itemId }` — on click or Enter/Space.
|
|
480
|
-
**Example**
|
|
481
|
-
```html
|
|
482
|
-
<ody-list-item item-id="1" active-item-id="1"><strong>Sunset Kayak</strong></ody-list-item>
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
#### `<ody-product-indicator>`
|
|
486
|
-
**Use when** showing a product name with a coloured bar and optional detail.
|
|
487
|
-
Extra content (e.g. a tag) is the child content.
|
|
488
|
-
**Attributes**
|
|
489
|
-
- `name` — product name (bold).
|
|
490
|
-
- `detail` — secondary detail line.
|
|
491
|
-
- `bar-color` — CSS colour for the bar (default neutral-400).
|
|
492
|
-
- `text-color` — CSS colour for the name.
|
|
493
|
-
- `size` — `base` | `small` (default `base`).
|
|
494
|
-
- `indicator-id` — emitted in `select` (when `clickable`).
|
|
495
|
-
- `clickable` — boolean; makes the whole indicator a button.
|
|
496
|
-
**Events**
|
|
497
|
-
- `select` → `{ id }` — only when `clickable`.
|
|
498
|
-
**Example**
|
|
499
|
-
```html
|
|
500
|
-
<ody-product-indicator name="Sunset Kayak Tour" detail="2 hours · Max 12"
|
|
501
|
-
bar-color="var(--color-turquoise-300)"></ody-product-indicator>
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
#### `<ody-toggle-button>`
|
|
505
|
-
**Use when** a segmented control of mutually-exclusive options.
|
|
506
|
-
**Attributes**
|
|
507
|
-
- `options` — JSON array of `{ value, label?, leftIcon?, rightIcon?, iconOnly?, disabled? }`.
|
|
508
|
-
- `selected` — the currently-selected option value (reflected on change).
|
|
509
|
-
- `size` — `base` | `small` (default `base`).
|
|
510
|
-
- `disabled` — boolean; disables the whole group.
|
|
511
|
-
**Events**
|
|
512
|
-
- `change` → `{ value }`.
|
|
513
|
-
**Example**
|
|
514
|
-
```html
|
|
515
|
-
<ody-toggle-button selected="week"
|
|
516
|
-
options='[{"value":"day","label":"Day"},{"value":"week","label":"Week"}]'></ody-toggle-button>
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
#### `<ody-section-columns>` + `<ody-section-column-item>`
|
|
520
|
-
**Use when** laying out children in a row.
|
|
521
|
-
- `ody-section-columns` **Attributes**: `gap-size` (`8` | `16` | `24` | `32` px, default `16`), `vertical-align` (`center` | `top` | `bottom`, default `bottom`).
|
|
522
|
-
- `ody-section-column-item` **Attributes** (boolean): `no-grow`, `individual-scroll`, `stretch`, `with-padding`. Child content is the column body.
|
|
523
|
-
**Example**
|
|
524
|
-
```html
|
|
525
|
-
<ody-section-columns gap-size="24" vertical-align="top">
|
|
526
|
-
<ody-section-column-item stretch>Left</ody-section-column-item>
|
|
527
|
-
<ody-section-column-item no-grow>Right</ody-section-column-item>
|
|
528
|
-
</ody-section-columns>
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
#### `<ody-section-rows>` + `<ody-section-row-item>`
|
|
532
|
-
**Use when** stacking children vertically with optional row headers.
|
|
533
|
-
- `ody-section-rows` **Attributes**: `gap-size` (`8` | `16` | `24` | `32` px, default `16`).
|
|
534
|
-
- `ody-section-row-item` **Attributes**: `title`, `description`, `description-icon` (icon name), `no-divider` (boolean). Child content is the row body.
|
|
535
|
-
**Example**
|
|
536
|
-
```html
|
|
537
|
-
<ody-section-rows gap-size="16">
|
|
538
|
-
<ody-section-row-item title="General" description="Basic details" description-icon="info">
|
|
539
|
-
Row body
|
|
540
|
-
</ody-section-row-item>
|
|
541
|
-
<ody-section-row-item title="Advanced" no-divider>More</ody-section-row-item>
|
|
542
|
-
</ody-section-rows>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
#### `<ody-two-column>` + parts
|
|
546
|
-
**Use when** a master/detail layout. The secondary panel shows only when
|
|
547
|
-
`secondary-open` is present.
|
|
548
|
-
- `ody-two-column` **Attributes**: `secondary-open` (boolean).
|
|
549
|
-
- `ody-two-column-main` — no attributes; primary content area.
|
|
550
|
-
- `ody-two-column-secondary` — no attributes; detail panel (hidden unless parent open).
|
|
551
|
-
- `ody-two-column-secondary-header` **Attributes**: `title`. **Events**: `close` (clicking the close button).
|
|
552
|
-
**Example**
|
|
553
|
-
```html
|
|
554
|
-
<ody-two-column secondary-open>
|
|
555
|
-
<ody-two-column-main>…list…</ody-two-column-main>
|
|
556
|
-
<ody-two-column-secondary>
|
|
557
|
-
<ody-two-column-secondary-header title="Details"></ody-two-column-secondary-header>
|
|
558
|
-
…detail…
|
|
559
|
-
</ody-two-column-secondary>
|
|
560
|
-
</ody-two-column>
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
#### `<ody-app-page-container>`
|
|
564
|
-
> [!IMPORTANT]
|
|
565
|
-
> **Every app settings UI must wrap its content in `<ody-app-page-container>`.**
|
|
566
|
-
> It is the standard responsive page wrapper that keeps the settings page sized
|
|
567
|
-
> to the two widths the settings host iframe renders at — **868px** (narrow) and
|
|
568
|
-
> **1310px** (wide).
|
|
569
|
-
>
|
|
570
|
-
> **Design for 868px first — it is the default.** Optimise every settings layout
|
|
571
|
-
> for the narrow 868px width, then make sure it *also* renders correctly when the
|
|
572
|
-
> host expands it to 1310px (e.g. progressively widen or add columns; never
|
|
573
|
-
> assume the extra width is present). Do **not** design 1310px-first and let it
|
|
574
|
-
> degrade — 868px is the baseline that must always look right.
|
|
575
|
-
|
|
576
|
-
**Use when** building the top-level layout of an app settings page (or any
|
|
577
|
-
full-page UI embedded in the settings host).
|
|
578
|
-
The container is `width: 100%`, so it fills whichever of the two widths the
|
|
579
|
-
parent iframe gives it; a `max-width` of `1310px` caps and centres it if it is
|
|
580
|
-
ever embedded somewhere wider. Unlike the deprecated `<ody-page-container>`, it
|
|
581
|
-
ships a **default responsive gutter** — `var(--gap24)` at the wide width,
|
|
582
|
-
tightening to `var(--gap16)` at/below 868px — so you no longer wrap the page in
|
|
583
|
-
a padded `<div>`. It also establishes a CSS **container context** named
|
|
584
|
-
`ody-page`, so the content inside can adapt between the two widths with container
|
|
585
|
-
queries instead of viewport media queries — treat 868px as the base styles and
|
|
586
|
-
use a container query to *enhance* for the wider view.
|
|
587
|
-
**Attributes**
|
|
588
|
-
- `flush` — drop the default gutter and render edge-to-edge, for the rare layout
|
|
589
|
-
that truly wants full-bleed content.
|
|
590
|
-
**Slots / content** — child nodes are the page content.
|
|
591
|
-
**Example**
|
|
592
|
-
```html
|
|
593
|
-
<ody-app-page-container>
|
|
594
|
-
<ody-section-rows gap-size="24">
|
|
595
|
-
…settings UI…
|
|
596
|
-
</ody-section-rows>
|
|
597
|
-
</ody-app-page-container>
|
|
598
|
-
```
|
|
599
|
-
```css
|
|
600
|
-
/* Base styles target the default 868px width… */
|
|
601
|
-
.my-settings-grid { grid-template-columns: 1fr; }
|
|
602
|
-
/* …then progressively enhance for the expanded 1310px view. */
|
|
603
|
-
@container ody-page (min-width: 1310px) {
|
|
604
|
-
.my-settings-grid { grid-template-columns: 1fr 1fr; }
|
|
605
|
-
}
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
#### `<ody-page-container>` _(deprecated — use `<ody-app-page-container>`)_
|
|
609
|
-
**Deprecated:** the original page wrapper is **full-bleed** (edge-to-edge, no
|
|
610
|
-
side gutters), so every settings UI had to add its own edge padding. Prefer
|
|
611
|
-
`<ody-app-page-container>`, which bakes in a responsive gutter (with a `flush`
|
|
612
|
-
opt-out) while keeping the same widths and `ody-page` container context. This
|
|
613
|
-
element still works and is unchanged; migrate by renaming the tag (add `flush`
|
|
614
|
-
if you genuinely relied on the full-bleed behaviour).
|
|
615
|
-
**Attributes** — none; the width is driven entirely by the parent.
|
|
616
|
-
|
|
617
|
-
#### `<ody-collapsible-section>` + `<ody-collapsible-collapsed>` + `<ody-collapsible-content>`
|
|
618
|
-
**Use when** a titled section that expands/collapses on header click.
|
|
619
|
-
- `ody-collapsible-section` **Attributes**: `header-text`, `secondary-header-text`, `icon` (icon name), `expanded` (boolean; reflected). **Events**: `expanded` → `{ expanded }`.
|
|
620
|
-
- `ody-collapsible-collapsed` — teaser shown only while collapsed.
|
|
621
|
-
- `ody-collapsible-content` — body shown only while expanded.
|
|
622
|
-
**Example**
|
|
623
|
-
```html
|
|
624
|
-
<ody-collapsible-section header-text="Booking details" secondary-header-text="(3 items)" icon="info">
|
|
625
|
-
<ody-collapsible-collapsed>Tap to see more</ody-collapsible-collapsed>
|
|
626
|
-
<ody-collapsible-content>…full content…</ody-collapsible-content>
|
|
627
|
-
</ody-collapsible-section>
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
### Form inputs
|
|
633
|
-
|
|
634
|
-
All text inputs render a native control in the light DOM (form-friendly) and
|
|
635
|
-
expose a JS `value` property mirroring the `value` attribute.
|
|
636
|
-
|
|
637
|
-
#### `<ody-input>`
|
|
638
|
-
**Use when** a standard text field (or multi-line textarea).
|
|
639
|
-
**Attributes**
|
|
640
|
-
- `label`, `placeholder`, `value`, `caption`.
|
|
641
|
-
- `size` — `base` | `small` (default `base`).
|
|
642
|
-
- `icon` — leading icon name.
|
|
643
|
-
- `warning` / `info` — message text; also sets the state ring.
|
|
644
|
-
- `maxlength` — when set, shows a character counter.
|
|
645
|
-
- `textarea` — boolean; render a `<textarea>`.
|
|
646
|
-
- `readonly`, `disabled`, `full-width`, `max-content` — boolean flags.
|
|
647
|
-
- `no-clear` — boolean; suppress the clear button.
|
|
648
|
-
**Properties (JS)** — `value: string` (get/set).
|
|
649
|
-
**Events**
|
|
650
|
-
- `input` → `{ value }` — every keystroke.
|
|
651
|
-
- `change` → `{ value }` — on native change / clear.
|
|
652
|
-
**Example**
|
|
653
|
-
```html
|
|
654
|
-
<ody-input label="Name" placeholder="Full name" maxlength="40"></ody-input>
|
|
655
|
-
<ody-input textarea label="Notes" full-width></ody-input>
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
#### `<ody-inline-input>`
|
|
659
|
-
**Use when** the compact, borderless inline variant of `ody-input` (e.g. inside
|
|
660
|
-
tables/forms). Same attributes, properties, and events as `ody-input`.
|
|
661
|
-
**Example**
|
|
662
|
-
```html
|
|
663
|
-
<ody-inline-input label="Qty" value="2" size="small"></ody-inline-input>
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
#### `<ody-search-input>`
|
|
667
|
-
**Use when** a search field with a leading search icon.
|
|
668
|
-
**Attributes**
|
|
669
|
-
- `value`, `placeholder` (default `"Search"`).
|
|
670
|
-
- `size` — `base` | `small` (default `base`).
|
|
671
|
-
- `info-message` — neutral helper caption.
|
|
672
|
-
- `warning-message` — warning caption + ring.
|
|
673
|
-
- `disabled` — boolean.
|
|
674
|
-
**Properties (JS)** — `value: string`.
|
|
675
|
-
**Events** — `input` → `{ value }`; `change` → `{ value }`.
|
|
676
|
-
**Example**
|
|
677
|
-
```html
|
|
678
|
-
<ody-search-input placeholder="Search bookings"></ody-search-input>
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
#### `<ody-money-input>`
|
|
682
|
-
**Use when** entering a currency amount. On blur the value is sanitised, clamped
|
|
683
|
-
to `max-amount`, and rounded to the currency precision (JPY → 0 decimals, else 2).
|
|
684
|
-
**Attributes**
|
|
685
|
-
- `value` — amount string.
|
|
686
|
-
- `currency` — ISO code for symbol + precision (default `USD`).
|
|
687
|
-
- `max-amount` — optional upper clamp applied on blur.
|
|
688
|
-
- `label`, `size`, `warning`.
|
|
689
|
-
- `readonly`, `disabled` — boolean.
|
|
690
|
-
**Properties (JS)** — `value: string` (get/set); `precision: number` (read-only).
|
|
691
|
-
**Events** — `input` → `{ value }` (per keystroke); `change` → `{ value }` (on blur, after formatting).
|
|
692
|
-
**Example**
|
|
693
|
-
```html
|
|
694
|
-
<ody-money-input label="Price" currency="USD" value="49.99" max-amount="500"></ody-money-input>
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
#### `<ody-percentage-input>`
|
|
698
|
-
**Use when** entering a percentage 0–100 with a trailing `%`. Invalid keystrokes
|
|
699
|
-
are blocked; on blur the value is clamped/normalised.
|
|
700
|
-
**Attributes**
|
|
701
|
-
- `value`, `label`, `size`, `warning`.
|
|
702
|
-
- `readonly`, `disabled` — boolean.
|
|
703
|
-
**Properties (JS)** — `value: string`.
|
|
704
|
-
**Events** — `input` → `{ value }` (per keystroke); `change` → `{ value }` (on blur, after clamping).
|
|
705
|
-
**Example**
|
|
706
|
-
```html
|
|
707
|
-
<ody-percentage-input label="Discount" value="10"></ody-percentage-input>
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
#### `<ody-checkbox>`
|
|
711
|
-
**Use when** a single boolean toggle. Optional label and extra child content.
|
|
712
|
-
**Attributes**
|
|
713
|
-
- `label` — text beside the box.
|
|
714
|
-
- `size` — `base` | `small` (default `small`).
|
|
715
|
-
- `checked`, `indeterminate`, `disabled` — boolean (`checked` is reflected).
|
|
716
|
-
**Properties (JS)** — `checked: boolean`; `value: boolean` (alias of `checked`).
|
|
717
|
-
**Events** — `change` → `{ checked, value }` (both boolean, equal).
|
|
718
|
-
**Example**
|
|
719
|
-
```html
|
|
720
|
-
<ody-checkbox label="I agree" checked></ody-checkbox>
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
#### `<ody-radio-button-group>`
|
|
724
|
-
**Use when** mutually-exclusive radio options.
|
|
725
|
-
**Attributes**
|
|
726
|
-
- `options` — JSON array of `{ label, value }`.
|
|
727
|
-
- `value` — selected option value.
|
|
728
|
-
- `size` — `base` | `small` (default `base`).
|
|
729
|
-
- `disabled` — boolean; applies to every option.
|
|
730
|
-
**Properties (JS)** — `value: string`; `options: OdyRadioOption[]` (read-only getter, parses the attribute).
|
|
731
|
-
**Events** — `change` → `{ value }`.
|
|
732
|
-
**Example**
|
|
733
|
-
```html
|
|
734
|
-
<ody-radio-button-group value="m"
|
|
735
|
-
options='[{"label":"Monthly","value":"m"},{"label":"Yearly","value":"y"}]'></ody-radio-button-group>
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
#### `<ody-checkbox-group>`
|
|
739
|
-
**Use when** several toggles, optionally with a "select all" parent (checked
|
|
740
|
-
when all selected, indeterminate when some).
|
|
741
|
-
**Attributes**
|
|
742
|
-
- `options` — JSON array of `{ label, value }` (also a JS property, below).
|
|
743
|
-
- `value` — comma-separated selected values.
|
|
744
|
-
- `select-all-label` — when present, renders the parent select-all row.
|
|
745
|
-
- `size` — `base` | `small` (default `small`).
|
|
746
|
-
- `disabled` — boolean.
|
|
747
|
-
**Properties (JS)** — `value: string[]` (get returns array, set takes array); `options: OdyCheckboxOption[]` (get parses the attribute, set writes the JSON attribute — assign `el.options = [...]` as with `<ody-dropdown-*>`).
|
|
748
|
-
**Events** — `change` → `{ value: string[] }`.
|
|
749
|
-
**Example**
|
|
750
|
-
```html
|
|
751
|
-
<ody-checkbox-group select-all-label="All toppings" value="cheese,olives"
|
|
752
|
-
options='[{"label":"Cheese","value":"cheese"},{"label":"Olives","value":"olives"},{"label":"Basil","value":"basil"}]'></ody-checkbox-group>
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
---
|
|
756
|
-
|
|
757
|
-
### Interactive
|
|
758
|
-
|
|
759
|
-
#### `<ody-accordion>`
|
|
760
|
-
**Use when** an expand/collapse panel with a header. Body is the child content.
|
|
761
|
-
**Attributes**
|
|
762
|
-
- `title` — header label text.
|
|
763
|
-
- `open` — boolean; expanded state (reflected, toggled on header click).
|
|
764
|
-
- `sticky` — boolean; pins the header while scrolling.
|
|
765
|
-
**Events** — `toggle` → `{ open }`.
|
|
766
|
-
**Example**
|
|
767
|
-
```html
|
|
768
|
-
<ody-accordion title="Itinerary" open><p>Day 1…</p></ody-accordion>
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
#### `<ody-collapsible>`
|
|
772
|
-
**Use when** a lightweight inline collapsible. Body is the child content.
|
|
773
|
-
**Attributes**
|
|
774
|
-
- `label` — header label text.
|
|
775
|
-
- `open` — boolean (reflected, toggled on header click).
|
|
776
|
-
**Events** — `toggle` → `{ open }`.
|
|
777
|
-
**Example**
|
|
778
|
-
```html
|
|
779
|
-
<ody-collapsible label="Show more"><p>Hidden details…</p></ody-collapsible>
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
#### `<ody-tabs>`
|
|
783
|
-
**Use when** tab navigation. Renders only the tab list; you swap the panels.
|
|
784
|
-
**Attributes**
|
|
785
|
-
- `tabs` — JSON array of `{ id, label, disabled? }`.
|
|
786
|
-
- `active` — id of the active tab (reflected, defaults to the first tab).
|
|
787
|
-
- `size` — `base` | `small` (default `base`).
|
|
788
|
-
- `position` — `top` | `left` (default `top`).
|
|
789
|
-
**Events** — `change` → `{ id }`.
|
|
790
|
-
**Example**
|
|
791
|
-
```html
|
|
792
|
-
<ody-tabs active="overview"
|
|
793
|
-
tabs='[{"id":"overview","label":"Overview"},{"id":"notes","label":"Notes","disabled":true}]'></ody-tabs>
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
#### `<ody-copy-button>`
|
|
797
|
-
**Use when** copying text to the clipboard with transient success feedback.
|
|
798
|
-
**Attributes**
|
|
799
|
-
- `value` — text copied to the clipboard.
|
|
800
|
-
- `label` — optional label (icon-only when omitted).
|
|
801
|
-
- `success-duration` — ms the success/error state shows (default `1200`).
|
|
802
|
-
**Events** — `copy` → `{ value, ok }` (`ok: boolean` — `false` if the clipboard API is unavailable or write failed).
|
|
803
|
-
**Example**
|
|
804
|
-
```html
|
|
805
|
-
<ody-copy-button value="ABC-123" label="Copy code"></ody-copy-button>
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
#### `<ody-check-in-status>`
|
|
809
|
-
**Use when** showing a fulfillment/check-in status with an icon + label.
|
|
810
|
-
**Attributes**
|
|
811
|
-
- `status` — `IN_PROGRESS` | `NO_SHOW` | `NONE` | `OVERDUE` | `RESERVED` | `RETURNED` (default `RESERVED`); selects icon, colour, and default (localized) label.
|
|
812
|
-
- `label` — overrides the status's default label.
|
|
813
|
-
- `optional-text` — secondary line below the label.
|
|
814
|
-
**Example**
|
|
815
|
-
```html
|
|
816
|
-
<ody-check-in-status status="RETURNED" optional-text="2:30 PM"></ody-check-in-status>
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
#### `<ody-option>`
|
|
820
|
-
**Use when** a selectable menu/list row (e.g. inside a popover menu). Label is
|
|
821
|
-
the child content.
|
|
822
|
-
**Attributes**
|
|
823
|
-
- `selected` — boolean; marks selected.
|
|
824
|
-
- `disabled` — boolean; disables interaction.
|
|
825
|
-
- `sub-menu` — boolean; shows a trailing sub-menu trigger.
|
|
826
|
-
- `trigger-icon` — icon name for the sub-menu trigger (default `chevron-right`).
|
|
827
|
-
**Events**
|
|
828
|
-
- `select` — clicking the row (no detail).
|
|
829
|
-
- `submenu` — clicking the sub-menu trigger (no detail).
|
|
830
|
-
**Example**
|
|
831
|
-
```html
|
|
832
|
-
<ody-option>Duplicate</ody-option>
|
|
833
|
-
<ody-option sub-menu>Move to…</ody-option>
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
#### `<ody-split-button>`
|
|
837
|
-
**Use when** a primary action joined to a dropdown toggle. Panel content is the
|
|
838
|
-
child content.
|
|
839
|
-
**Attributes**
|
|
840
|
-
- `text` — primary button label.
|
|
841
|
-
- `variant` — button variant (default `secondary`).
|
|
842
|
-
- `size` — `base` | `small` (default `base`).
|
|
843
|
-
- `icon` — toggle icon name (default `chevron-down`).
|
|
844
|
-
- `open` — boolean; whether the panel is shown (reflected).
|
|
845
|
-
**Events**
|
|
846
|
-
- `primary` — clicking the main button (no detail).
|
|
847
|
-
- `toggle` → `{ open }` — clicking the dropdown toggle.
|
|
848
|
-
**Example**
|
|
849
|
-
```html
|
|
850
|
-
<ody-split-button text="Save" variant="primary">
|
|
851
|
-
<ody-option>Save and close</ody-option>
|
|
852
|
-
</ody-split-button>
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
#### `<ody-table-header>`
|
|
856
|
-
**Use when** a single sortable column header (standalone; for full tables use
|
|
857
|
-
`ody-table`). Clicking cycles none → desc → asc → none.
|
|
858
|
-
**Attributes**
|
|
859
|
-
- `column-name` — header text.
|
|
860
|
-
- `column-id` — passed through in the `sort` event.
|
|
861
|
-
- `direction` — `UNSET` | `ASC` | `DESC` (reflected, default `UNSET`).
|
|
862
|
-
- `static` — boolean; non-interactive header, no sort affordance.
|
|
863
|
-
- `rounded-left` / `rounded-right` — boolean; round the matching corners.
|
|
864
|
-
**Events** — `sort` → `{ direction, columnId }` (`direction` is `UNSET` | `ASC` | `DESC`).
|
|
865
|
-
**Example**
|
|
866
|
-
```html
|
|
867
|
-
<ody-table-header column-name="Date" column-id="date" direction="ASC"></ody-table-header>
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
---
|
|
871
|
-
|
|
872
|
-
### Overlays
|
|
873
|
-
|
|
874
|
-
All overlay panels are portaled to `document.body` so they escape clipping
|
|
875
|
-
ancestors.
|
|
876
|
-
|
|
877
|
-
#### `<ody-modal>`
|
|
878
|
-
**Use when** a blocking dialog. Body is the child content.
|
|
879
|
-
**Attributes**
|
|
880
|
-
- `open` — boolean; shown when present (mirrors `open()` / `close()`).
|
|
881
|
-
- `heading` — optional title.
|
|
882
|
-
- `size` — `base` | `small` | `medium` | `large` | `full` (default `base`).
|
|
883
|
-
- `icon` — optional named icon before the heading.
|
|
884
|
-
**Properties / methods (JS)** — `open()`, `close()`.
|
|
885
|
-
**Events** — `close` (clicking the backdrop, the close button, or pressing Escape).
|
|
886
|
-
**Example**
|
|
887
|
-
```html
|
|
888
|
-
<ody-modal id="m" heading="Confirm"><p>Are you sure?</p></ody-modal>
|
|
889
|
-
<script>document.getElementById('m').open();</script>
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
#### `<ody-popover>`
|
|
893
|
-
**Use when** a click-toggled floating panel anchored to a trigger.
|
|
894
|
-
**Attributes**
|
|
895
|
-
- `for` — id of an external trigger element (overrides a slotted trigger).
|
|
896
|
-
- `placement` — `top` | `bottom` | `left` | `right` (default `bottom`).
|
|
897
|
-
**Properties / methods (JS)** — `isOpen` (read-only), `open()`, `close()`, `toggle()`.
|
|
898
|
-
**Events** — `open`, `close` (no detail).
|
|
899
|
-
**Slots / content** — the **trigger** is the child marked `[data-ody-popover-trigger]` (else the first element child unless `for` is set); the **content** is the child marked `[data-ody-popover-content]` (else the remaining children).
|
|
900
|
-
**Example**
|
|
901
|
-
```html
|
|
902
|
-
<ody-popover placement="bottom">
|
|
903
|
-
<ody-button data-ody-popover-trigger variant="secondary">Menu</ody-button>
|
|
904
|
-
<div data-ody-popover-content><ody-option>Edit</ody-option></div>
|
|
905
|
-
</ody-popover>
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
#### `<ody-tooltip>`
|
|
909
|
-
**Use when** a hover/focus hint anchored to a trigger.
|
|
910
|
-
**Attributes**
|
|
911
|
-
- `text` — the tooltip text (alternative to slotted content).
|
|
912
|
-
- `placement` — `top` | `bottom` | `left` | `right` (default `top`).
|
|
913
|
-
- `for` — id of an external trigger element.
|
|
914
|
-
**Properties / methods (JS)** — `isVisible` (read-only), `show()`, `hide()`.
|
|
915
|
-
**Slots / content** — the **trigger** is `for` (external) or the first slotted child; tooltip text is `text` or extra slotted children.
|
|
916
|
-
**Example**
|
|
917
|
-
```html
|
|
918
|
-
<ody-tooltip text="Helpful hint" placement="top">
|
|
919
|
-
<ody-button icon-only left-icon="info" aria-label="Info"></ody-button>
|
|
920
|
-
</ody-tooltip>
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
#### `<ody-panel>`
|
|
924
|
-
**Use when** a side panel that slides in from the right. Body is the child content.
|
|
925
|
-
**Attributes**
|
|
926
|
-
- `open` — boolean; toggles the slide-in (mirrors `open()` / `close()`).
|
|
927
|
-
- `heading` — optional panel title.
|
|
928
|
-
- `full-height` — boolean; spans the full viewport height.
|
|
929
|
-
**Properties / methods (JS)** — `open()`, `close()`.
|
|
930
|
-
**Events** — `close` (clicking the overlay or the close button).
|
|
931
|
-
**Example**
|
|
932
|
-
```html
|
|
933
|
-
<ody-panel id="p" heading="Details"><p>Body…</p></ody-panel>
|
|
934
|
-
<script>document.getElementById('p').open();</script>
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
#### `<ody-toast-host>` + `toast()`
|
|
938
|
-
**Use when** transient feedback. You normally never write `<ody-toast-host>`
|
|
939
|
-
yourself — call the exported `toast()` function, which lazily creates and
|
|
940
|
-
portals a singleton host.
|
|
941
|
-
**`toast(message, options?)`** — `options: { variant?, title?, timeout? }`:
|
|
942
|
-
- `variant` — `info` | `success` | `warning` | `danger` (default `info`).
|
|
943
|
-
- `title` — optional bold title above the message.
|
|
944
|
-
- `timeout` — auto-dismiss ms; `0` disables auto-dismiss (default `4000`).
|
|
945
|
-
Returns `{ dismiss: () => void }` to remove the toast early.
|
|
946
|
-
`ody-toast-host` has a `push(message, options)` method with the same signature.
|
|
947
|
-
**Example**
|
|
948
|
-
```js
|
|
949
|
-
import { toast } from '@peektravel/app-utilities/ui';
|
|
950
|
-
const t = toast('Saved successfully', { variant: 'success', title: 'Done' });
|
|
951
|
-
// t.dismiss();
|
|
952
|
-
```
|
|
953
|
-
|
|
954
|
-
---
|
|
955
|
-
|
|
956
|
-
### Data & selection
|
|
957
|
-
|
|
958
|
-
#### `<ody-dropdown-single>`
|
|
959
|
-
**Use when** single choice from a list (select-only combobox, WAI-ARIA pattern).
|
|
960
|
-
**Attributes**
|
|
961
|
-
- `value` — selected option value (reflected; also a JS property).
|
|
962
|
-
- `options` — JSON array fallback (`[{"value":"a","label":"A","disabled":false}]`).
|
|
963
|
-
- `placeholder` — text when nothing is selected.
|
|
964
|
-
- `label` / `aria-label` — accessible name for the trigger.
|
|
965
|
-
- `search-placeholder` — placeholder for the filter input when `searchable`.
|
|
966
|
-
- `disabled` — boolean.
|
|
967
|
-
- `searchable` — boolean; renders a filter input atop the list.
|
|
968
|
-
**Properties (JS)** — `value: string`; `options: OdySelectOption[]` (`{ value, label, disabled? }`); `disabled: boolean` (read-only).
|
|
969
|
-
**Events** — `change` → `{ value: string }` (user selection only; **not** on programmatic set).
|
|
970
|
-
**Keyboard** — ArrowDown/Up (or Enter/Space) open + move; Home/End jump; Enter selects + closes; Escape closes; Tab closes; printable chars typeahead.
|
|
971
|
-
**Example**
|
|
972
|
-
```html
|
|
973
|
-
<ody-dropdown-single label="Fruit" placeholder="Pick a fruit" searchable
|
|
974
|
-
options='[{"value":"apple","label":"Apple"},{"value":"cherry","label":"Cherry","disabled":true}]'></ody-dropdown-single>
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
#### `<ody-dropdown-multi>`
|
|
978
|
-
**Use when** multiple choices from a list (multi-select listbox combobox). The
|
|
979
|
-
trigger shows selected options as removable chips.
|
|
980
|
-
**Attributes** — same set as `ody-dropdown-single`; `value` is comma-delimited.
|
|
981
|
-
**Properties (JS)** — `value: string[]` (get returns array, set takes array); `options: OdySelectOption[]`; `disabled` (read-only).
|
|
982
|
-
**Events** — `change` → `{ value: string[] }` (user selection only; **not** on programmatic set). Toggling an option keeps the panel open.
|
|
983
|
-
**Keyboard** — ArrowDown/Up move; Home/End jump; Space/Enter toggles; Escape/Tab close.
|
|
984
|
-
**Example**
|
|
985
|
-
```html
|
|
986
|
-
<ody-dropdown-multi label="Toppings" placeholder="Choose toppings" searchable value="cheese"
|
|
987
|
-
options='[{"value":"cheese","label":"Cheese"},{"value":"olives","label":"Olives"}]'></ody-dropdown-multi>
|
|
988
|
-
```
|
|
989
|
-
|
|
990
|
-
#### `<ody-datepicker>`
|
|
991
|
-
**Use when** picking a date or date range. Dependency-free calendar (native
|
|
992
|
-
`Date`/`Intl`). The displayed text is localized; the `value` attribute and
|
|
993
|
-
`change` payload always stay machine-readable ISO `yyyy-mm-dd`.
|
|
994
|
-
**Attributes**
|
|
995
|
-
- `value` — `yyyy-mm-dd` (single), or `yyyy-mm-dd/yyyy-mm-dd` (range mode).
|
|
996
|
-
- `min` / `max` — inclusive bounds `yyyy-mm-dd`.
|
|
997
|
-
- `range` — boolean; enables start/end range selection.
|
|
998
|
-
- `first-day-of-week` — `0`–`6` (0 = Sunday, default `0`).
|
|
999
|
-
- `placeholder` — trigger text when no value.
|
|
1000
|
-
- `presets` — JSON array of `{ label, value }` shortcut buttons (`value` is single or range form).
|
|
1001
|
-
- `display-format` — `short` | `medium` | `long` | `full` (default `medium`); how the trigger label is shown (via `Intl.DateTimeFormat` for the element's `lang`).
|
|
1002
|
-
**Properties (JS)**
|
|
1003
|
-
- `isDateDisallowed?: (date: Date) => boolean` — disable arbitrary days.
|
|
1004
|
-
- `formatDate?: (date: Date) => string` — full control of the trigger label (wins over `display-format`).
|
|
1005
|
-
- `isOpen` (read-only); methods `openPopover()`, `closePopover()`.
|
|
1006
|
-
**Events** — `change` on **user** selection only (never on programmatic set):
|
|
1007
|
-
- single mode → `{ value }` (ISO).
|
|
1008
|
-
- range mode → `{ value, start, end }` (all ISO; `value` is `start/end`).
|
|
1009
|
-
**Example**
|
|
1010
|
-
```html
|
|
1011
|
-
<ody-datepicker value="2026-06-15" display-format="full"></ody-datepicker>
|
|
1012
|
-
<ody-datepicker range value="2026-06-10/2026-07-04" min="2026-04-01" max="2026-09-30"
|
|
1013
|
-
presets='[{"label":"This month","value":"2026-06-01/2026-06-30"}]'></ody-datepicker>
|
|
1014
|
-
```
|
|
1015
|
-
```js
|
|
1016
|
-
const dp = document.querySelector('ody-datepicker');
|
|
1017
|
-
dp.isDateDisallowed = (d) => d.getDay() === 0; // disable Sundays
|
|
1018
|
-
dp.formatDate = (d) => d.toLocaleDateString('en-GB'); // custom label
|
|
1019
|
-
```
|
|
1020
|
-
|
|
1021
|
-
#### `<ody-table>`
|
|
1022
|
-
**Use when** tabular data with sortable headers and optional checkbox selection.
|
|
1023
|
-
Rich data is supplied through **JS properties** only.
|
|
1024
|
-
**Properties (JS)**
|
|
1025
|
-
- `columns: OdyTableColumn[]` — `{ key, label, sortable?, align?, render? }`. If omitted, columns auto-generate from the keys of the first data row. `align` is `left` | `center` | `right` (default `left`). `render(cellEl, row)` populates a custom cell element.
|
|
1026
|
-
- `data: OdyTableRow[]` (alias `rows`) — row objects (`Record<string, unknown>`).
|
|
1027
|
-
- `selected: OdyTableRow[]` — getter of currently selected rows; setter replaces the selection.
|
|
1028
|
-
**Attributes**
|
|
1029
|
-
- `selectable` — boolean; adds a leading checkbox column with header select-all (indeterminate when partial).
|
|
1030
|
-
- `sortable` — boolean; globally enables sorting (per-column opt-in via `column.sortable`).
|
|
1031
|
-
- `sort-key` — the column key currently sorted (reflected).
|
|
1032
|
-
- `sort-direction` — `ascending` | `descending` (reflected).
|
|
1033
|
-
- `sticky-header` — boolean; pins the header while the body scrolls.
|
|
1034
|
-
**Events** (all bubble)
|
|
1035
|
-
- `sort-change` → `{ key, direction }` — `direction` is `ascending` | `descending` | `null` (cleared).
|
|
1036
|
-
- `selection-change` → `{ selected: OdyTableRow[] }`.
|
|
1037
|
-
- `row-click` → `{ row, index }`.
|
|
1038
|
-
**Example**
|
|
1039
|
-
```html
|
|
1040
|
-
<ody-table id="t" selectable sortable sticky-header></ody-table>
|
|
1041
|
-
```
|
|
1042
|
-
```js
|
|
1043
|
-
const t = document.getElementById('t');
|
|
1044
|
-
t.columns = [
|
|
1045
|
-
{ key: 'name', label: 'Name', sortable: true },
|
|
1046
|
-
{ key: 'age', label: 'Age', sortable: true, align: 'right' },
|
|
1047
|
-
];
|
|
1048
|
-
t.data = [{ name: 'Alice', age: 42 }, { name: 'Bob', age: 25 }];
|
|
1049
|
-
t.addEventListener('sort-change', (e) => console.log(e.detail)); // { key, direction }
|
|
1050
|
-
t.addEventListener('selection-change', (e) => console.log(e.detail.selected));
|
|
1051
|
-
```
|
|
1052
|
-
|
|
1053
|
-
---
|
|
1054
|
-
|
|
1055
|
-
## 6. Icons
|
|
1056
|
-
|
|
1057
|
-
Two separate libraries.
|
|
1058
|
-
|
|
1059
|
-
### Themeable icons — `<ody-icon>` (inherit `currentColor`)
|
|
1060
|
-
Use for UI glyphs that should follow text colour. See `<ody-icon>` above for the
|
|
1061
|
-
element. Programmatic API (named exports):
|
|
1062
|
-
|
|
1063
|
-
| Function | Purpose |
|
|
1064
|
-
|---|---|
|
|
1065
|
-
| `iconNames(): string[]` | All registered themeable names, sorted (discover at runtime). |
|
|
1066
|
-
| `hasIcon(name): boolean` | Whether a name is known. |
|
|
1067
|
-
| `iconSvg(name, className?): string` | Raw `<svg>` string; unknown names → empty `<svg>`. |
|
|
1068
|
-
| `renderIconSvg(icon, className?): string` | Build `<svg>` from `OdyIconData` (`{ viewBox, body }`). |
|
|
1069
|
-
| `registerIcon(name, innerSvg, viewBox?)` | Register/override an icon (`viewBox` default `'0 0 24 24'`). |
|
|
1070
|
-
|
|
1071
|
-
```js
|
|
1072
|
-
import { registerIcon, iconNames } from '@peektravel/app-utilities/ui';
|
|
1073
|
-
registerIcon('star', '<path d="M12 2l3 7h7l-5.5 4 2 7L12 16l-6.5 4 2-7L2 9h7z"/>');
|
|
1074
|
-
console.log(iconNames()); // discover available names
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
### Brand / fixed-colour icons — `<ody-brand-icon>` (keep baked-in colours)
|
|
1078
|
-
Use for logos, illustrations, and status art that must keep their original
|
|
1079
|
-
colours/gradients (do **not** follow `currentColor`).
|
|
1080
|
-
`<ody-brand-icon>` **Attributes**: `name` (see `brandIconNames()`; unknown
|
|
1081
|
-
renders nothing), `size` (same scale as `ody-icon`, default `base`; `free` sizes
|
|
1082
|
-
from the surrounding box).
|
|
1083
|
-
|
|
1084
|
-
| Function | Purpose |
|
|
1085
|
-
|---|---|
|
|
1086
|
-
| `brandIconNames(): string[]` | All brand icon names, sorted. |
|
|
1087
|
-
| `hasBrandIcon(name): boolean` | Whether a name is known. |
|
|
1088
|
-
| `brandIconSvg(name, className?): string` | Raw `<svg>` string; unknown → empty `<svg>`. |
|
|
1089
|
-
|
|
1090
|
-
```html
|
|
1091
|
-
<ody-brand-icon name="empty-state-default" size="free"></ody-brand-icon>
|
|
1092
|
-
```
|
|
1093
|
-
|
|
1094
|
-
**Which to use** — text-coloured glyph → themeable (`ody-icon` / `iconSvg`);
|
|
1095
|
-
multi-colour logo or illustration → brand (`ody-brand-icon` / `brandIconSvg`).
|
|
1096
|
-
|
|
1097
|
-
---
|
|
1098
|
-
|
|
1099
|
-
## 7. Localization
|
|
1100
|
-
|
|
1101
|
-
Built-in strings (aria-labels, default placeholders, status labels — **not**
|
|
1102
|
-
your content) are localized.
|
|
1103
|
-
|
|
1104
|
-
**Register bundles** once:
|
|
1105
|
-
```ts
|
|
1106
|
-
import { registerTranslation } from '@peektravel/app-utilities/ui';
|
|
1107
|
-
registerTranslation('es', { close: 'Cerrar', clear: 'Borrar', search: 'Buscar' });
|
|
1108
|
-
```
|
|
1109
|
-
Re-registering merges into the existing bundle and re-renders mounted components.
|
|
1110
|
-
|
|
1111
|
-
**Language resolution** — a component reads the nearest ancestor `lang`
|
|
1112
|
-
attribute (so `<html lang="es">` localizes everything), falling back region →
|
|
1113
|
-
base language → English. A `<html lang>` change re-renders automatically.
|
|
1114
|
-
|
|
1115
|
-
**Per-instance override** — an attribute wins over the registry for that
|
|
1116
|
-
instance, e.g. `close-label`, `clear-label`, `search-placeholder`,
|
|
1117
|
-
`previous-month-label`, `next-month-label`, `open-submenu-label`,
|
|
1118
|
-
`toggle-menu-label`:
|
|
1119
|
-
```html
|
|
1120
|
-
<ody-panel close-label="Cerrar" heading="Detalles">…</ody-panel>
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
**Term keys** (`OdyTerms`): `close`, `clear`, `search`, `noOptions`,
|
|
1124
|
-
`previousMonth`, `nextMonth`, `selectDate`, `openSubMenu`, `toggleMenu`,
|
|
1125
|
-
`breadcrumb`, `radioGroup`, `toggleGroup`, `checkInInProgress`, `checkInNoShow`,
|
|
1126
|
-
`checkInOverdue`, `checkInReserved`, `checkInReturned`, `checkInNone`.
|
|
1127
|
-
|
|
1128
|
-
**Datepicker** — calendar weekday/month names and the trigger display are
|
|
1129
|
-
`Intl`-localized for the resolved `lang` (controlled by `display-format` /
|
|
1130
|
-
`formatDate`); the `value` and `change` payload stay ISO `yyyy-mm-dd`.
|
|
1131
|
-
|
|
1132
|
-
---
|
|
1133
|
-
|
|
1134
|
-
## 8. Theming (CSS custom properties)
|
|
1135
|
-
|
|
1136
|
-
`tokens.css` defines the design tokens used by the components; override them in
|
|
1137
|
-
your own CSS to retheme. Categories:
|
|
1138
|
-
|
|
1139
|
-
- **Colours** — `--color-<name>-<shade>`. Semantic: `interaction`, `info`,
|
|
1140
|
-
`success`, `warning`, `danger` (shades `100`–`400`). Neutrals:
|
|
1141
|
-
`--color-neutral-0/50/100/200/250/300/350/400`. Accents: `blue`, `green`,
|
|
1142
|
-
`teal`, `lime`, `purple`, `pink`, `rose`, `turquoise`, `yellow`, `orange`,
|
|
1143
|
-
`orange-alt` (typically shades `200`/`300`). Plus `--color-box-shadow`.
|
|
1144
|
-
- **Typography** — `--ody-font-family`, `--ody-line-height`,
|
|
1145
|
-
`--ody-line-height-150`, and weights `--ody-font-weight-thin` …
|
|
1146
|
-
`--ody-font-weight-black` (`extra-light`, `light`, `normal`, `medium`,
|
|
1147
|
-
`semi-bold`, `bold`, `extra-bold`).
|
|
1148
|
-
- **Layout / elevation** — `--layout-top-bar-height`,
|
|
1149
|
-
`--layout-breadcrumb-height`, `--ody-shadow-base`, `--ody-overlay`.
|
|
1150
|
-
- **Z-index** — `--ody-z-general`, `--ody-z-side-nav`, `--ody-z-top-bar`,
|
|
1151
|
-
`--ody-z-modal`, `--ody-z-over-modal`, `--ody-z-tooltip`, `--ody-z-wormhole`.
|
|
1152
|
-
|
|
1153
|
-
```css
|
|
1154
|
-
:root { --color-interaction-300: #0066ff; --ody-font-family: 'Inter', sans-serif; }
|
|
1155
|
-
```
|
|
1156
|
-
|
|
1157
|
-
Many components also accept colours inline via attributes (`bar-color`,
|
|
1158
|
-
`text-color`, `color`) — pass any CSS colour, including a token reference:
|
|
1159
|
-
```html
|
|
1160
|
-
<ody-card bar-color="var(--color-success-300)">…</ody-card>
|
|
1161
|
-
```
|
|
1162
|
-
|
|
1163
|
-
---
|
|
1164
|
-
|
|
1165
|
-
## 9. Not included (and what to use instead)
|
|
1166
|
-
|
|
1167
|
-
These Odyssey components were **not** ported:
|
|
1168
|
-
|
|
1169
|
-
| Not ported | Use instead |
|
|
1170
|
-
|---|---|
|
|
1171
|
-
| `nested-multi-select` | `ody-dropdown-multi` for flat lists; for grouping, compose multiple dropdowns or build with `ody-checkbox-group` + `ody-collapsible-section`. |
|
|
1172
|
-
| `location-autocomplete` | A native `<input>` wired to your own geocoding/autocomplete; render results with `ody-option` rows in an `ody-popover`. |
|
|
1173
|
-
| `filter-menu` / `filter-menu-single` | `ody-popover` (trigger + panel) containing `ody-checkbox-group` (multi) or `ody-radio-button-group` / `ody-dropdown-single` (single). |
|
|
1174
|
-
| `accordion-checkbox` | `ody-collapsible-section` (or `ody-accordion`) with an `ody-checkbox-group` inside. |
|
|
1175
|
-
| `datepicker-with-presets` | `ody-datepicker` with the `presets` attribute (presets are built in). |
|