kanto-ds 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +56 -6
  2. package/dist/cdn/kanto.min.js +6619 -0
  3. package/dist/chunks/defaults-Cpeg13O5.js +68 -0
  4. package/dist/chunks/defaults-Cpeg13O5.js.map +1 -0
  5. package/dist/chunks/{floating-B33g-M_1.js → floating-C9RLa6cZ.js} +2 -2
  6. package/dist/chunks/floating-C9RLa6cZ.js.map +1 -0
  7. package/dist/components/core/kt-icon/kt-icon.js +1 -1
  8. package/dist/components/forms/kt-date-input/kt-date-input.js +1 -1
  9. package/dist/components/forms/kt-date-picker/kt-date-picker.js +1 -1
  10. package/dist/components/forms/kt-input/kt-input.js +1 -1
  11. package/dist/components/forms/kt-input-menu/kt-input-menu.js +1 -1
  12. package/dist/components/forms/kt-multi-select/kt-multi-select.js +1 -1
  13. package/dist/components/forms/kt-select/kt-select.js +1 -1
  14. package/dist/components/overlays/kt-dropdown/kt-dropdown.js +1 -1
  15. package/dist/docs/kt-alert.md +52 -0
  16. package/dist/docs/kt-avatar.md +46 -0
  17. package/dist/docs/kt-badge.md +104 -0
  18. package/dist/docs/kt-breadcrumb.md +53 -0
  19. package/dist/docs/kt-button.md +72 -0
  20. package/dist/docs/kt-calendar.md +98 -0
  21. package/dist/docs/kt-card.md +93 -0
  22. package/dist/docs/kt-chart.md +230 -0
  23. package/dist/docs/kt-checkbox.md +65 -0
  24. package/dist/docs/kt-code.md +88 -0
  25. package/dist/docs/kt-collapsible.md +50 -0
  26. package/dist/docs/kt-confirm-dialog.md +52 -0
  27. package/dist/docs/kt-date-input.md +97 -0
  28. package/dist/docs/kt-date-picker.md +116 -0
  29. package/dist/docs/kt-drag-drop.md +85 -0
  30. package/dist/docs/kt-dropdown.md +65 -0
  31. package/dist/docs/kt-empty-state.md +36 -0
  32. package/dist/docs/kt-form.md +93 -0
  33. package/dist/docs/kt-header.md +100 -0
  34. package/dist/docs/kt-icon.md +68 -0
  35. package/dist/docs/kt-input-menu.md +90 -0
  36. package/dist/docs/kt-input.md +137 -0
  37. package/dist/docs/kt-kbd.md +44 -0
  38. package/dist/docs/kt-label-input.md +54 -0
  39. package/dist/docs/kt-meter.md +79 -0
  40. package/dist/docs/kt-modal.md +62 -0
  41. package/dist/docs/kt-multi-select.md +115 -0
  42. package/dist/docs/kt-page-header.md +53 -0
  43. package/dist/docs/kt-pagination.md +36 -0
  44. package/dist/docs/kt-progress-bar.md +66 -0
  45. package/dist/docs/kt-radio-group.md +73 -0
  46. package/dist/docs/kt-segmented-control.md +70 -0
  47. package/dist/docs/kt-select.md +80 -0
  48. package/dist/docs/kt-side-panel.md +59 -0
  49. package/dist/docs/kt-skeleton.md +46 -0
  50. package/dist/docs/kt-split-button.md +105 -0
  51. package/dist/docs/kt-stat.md +57 -0
  52. package/dist/docs/kt-sub-menu-navigation.md +118 -0
  53. package/dist/docs/kt-table.md +179 -0
  54. package/dist/docs/kt-tabs.md +87 -0
  55. package/dist/docs/kt-textarea.md +59 -0
  56. package/dist/docs/kt-time-input.md +88 -0
  57. package/dist/docs/kt-timeline.md +63 -0
  58. package/dist/docs/kt-toast-container.md +69 -0
  59. package/dist/docs/kt-toast.md +41 -0
  60. package/dist/docs/kt-toggle-button-group.md +67 -0
  61. package/dist/docs/kt-toggle-button.md +52 -0
  62. package/dist/docs/kt-toggle.md +47 -0
  63. package/dist/docs/kt-tooltip.md +68 -0
  64. package/dist/icons/defaults.d.ts +9 -0
  65. package/dist/icons/index.js +1 -1
  66. package/dist/index.js +1 -1
  67. package/dist/internal/floating.d.ts +4 -0
  68. package/dist/tokens/colors.css +5 -2
  69. package/dist/web-types.json +1 -1
  70. package/package.json +29 -6
  71. package/dist/chunks/defaults-CFrjqae2.js +0 -59
  72. package/dist/chunks/defaults-CFrjqae2.js.map +0 -1
  73. package/dist/chunks/floating-B33g-M_1.js.map +0 -1
@@ -0,0 +1,116 @@
1
+ # `<kt-date-picker>`
2
+
3
+ A date field with a calendar, for one day or a period.
4
+
5
+ ```html
6
+ <kt-date-picker label="Due date" name="due" min="2026-01-01"></kt-date-picker>
7
+ <kt-date-picker range label="Report period" value="2026-09-01/2026-09-25"></kt-date-picker>
8
+ ```
9
+
10
+ ## ISO in, the reader's language out
11
+
12
+ The value is ISO 8601 text — what a server, a database and
13
+ `<input type="date">` already speak:
14
+
15
+ - one day: `2026-09-25`
16
+ - a period, with `range`: `2026-09-01/2026-09-25`, the ISO interval
17
+
18
+ What the field shows is the same value formatted for the reader: "25 Sept
19
+ 2026", "1–25 Sept 2026". The calendar's month names, weekday names and first
20
+ day of the week follow the locale too — weeks start on Monday in France, on
21
+ Sunday in the US. The locale is `locale` if set, else the page's `lang`, else
22
+ the browser's language.
23
+
24
+ No time, no time zone: a date picker deals in days, and a day never shifts by
25
+ one because someone opened the form in another zone.
26
+
27
+ ## Typing a date
28
+
29
+ The field takes a date typed the way the reader writes it, on `Enter` or when
30
+ it is left:
31
+
32
+ - in numbers, in the language's order: `25/09/2026` in London, `9/25/2026` in
33
+ New York, `25.09.2026` in Berlin, ISO `2026-09-25` anywhere;
34
+ - with the month's name, short or long: `25 Sept 2026`, `25 septembre`;
35
+ - without the year when it is this one, and with two digits for it — `98` is
36
+ 1998, `30` is 2030;
37
+ - a period as two dates around a dash: `1/9 – 25/9/2026`.
38
+
39
+ What does not read as a date, or falls outside `min`/`max`, puts the field in
40
+ its error state — the message in a tooltip on the alert icon, shown while the
41
+ field has focus, as `<kt-input>` does — and the value it had is kept. The form
42
+ sees a `badInput`. `Escape` puts the text back; emptying the field empties the
43
+ value.
44
+
45
+ ## The calendar
46
+
47
+ The calendar button leads the field and opens the popup, a
48
+ [`<kt-calendar>`](./kt-calendar.md), with the focus in it; a click
49
+ in the text opens it too, leaving the focus to type, and `↓` from the text
50
+ moves into it. Its title opens the year and month panel, so a date years away
51
+ is two clicks. A single date gets a **Today** button under it.
52
+
53
+ `Escape` closes it and puts the focus back in the field — except in the year
54
+ and month panel, where it first goes back to the days. Tabbing out closes it
55
+ too. Days outside `min`/`max` can be reached, so the keyboard never gets stuck
56
+ at a boundary, but not chosen.
57
+
58
+ ## Choosing a period
59
+
60
+ With `range`, the popup leads with the periods a dashboard asks for most —
61
+ today, the last 7 and 30 days, this month and last, this year, each ending
62
+ today — beside two months. A click on a period applies it and closes; the one
63
+ the value matches is marked, and one reaching outside `min`/`max` is disabled.
64
+
65
+ ```js
66
+ picker.presets = [
67
+ { label: 'Q3 2026', value: '2026-07-01/2026-09-30' },
68
+ { label: 'Q4 2026', value: '2026-10-01/2026-12-31' },
69
+ ];
70
+ picker.presets = []; // no column
71
+ ```
72
+
73
+ In the calendar, the first pick sets one end; the band between it and the
74
+ pointer — or the day in focus — previews the period; the second sets the other
75
+ end, in whichever order. `kt-change` fires once, with the whole period.
76
+
77
+ Below 720px wide the popup shows one month, with the periods wrapped above it.
78
+
79
+ ## In a form
80
+
81
+ A form control: it submits the ISO value under `name`. A period submits
82
+ nothing until both ends are chosen. `required` blocks submission while it is
83
+ empty, `error` shows a message, and `form.reset()` puts back the initial
84
+ value.
85
+
86
+ ## API
87
+
88
+ | Property | Attribute | Type | Default |
89
+ | ------------- | ------------- | -------------------------------- | ------------------ |
90
+ | `value` | `value` | `string \| null` | `null` |
91
+ | `range` | `range` | `boolean` | `false` |
92
+ | `min` | `min` | `string` | `''` |
93
+ | `max` | `max` | `string` | `''` |
94
+ | `name` | `name` | `string` | `''` |
95
+ | `label` | `label` | `string` | `''` |
96
+ | `placeholder` | `placeholder` | `string` | from `setStrings` |
97
+ | `locale` | `locale` | `string` | page, then browser |
98
+ | `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
99
+ | `disabled` | `disabled` | `boolean` | `false` |
100
+ | `required` | `required` | `boolean` | `false` |
101
+ | `clearable` | — | `boolean` | `true` |
102
+ | `presets` | — | `KtDatePreset[]` | the six above |
103
+ | `error` | `error` | `string` | `''` |
104
+
105
+ | Event | Detail |
106
+ | ----------- | ---------------------------------------------------------- |
107
+ | `kt-change` | `{ value: string \| null }` — ISO date, interval or `null` |
108
+
109
+ | Part | Description |
110
+ | ---------- | --------------------------------- |
111
+ | `field` | The field |
112
+ | `input` | The text the date is typed into |
113
+ | `trigger` | The button that opens the popup |
114
+ | `panel` | The calendar popup |
115
+ | `presets` | The column of periods, with range |
116
+ | `calendar` | The `<kt-calendar>` inside it |
@@ -0,0 +1,85 @@
1
+ # `<kt-drag-drop>`
2
+
3
+ A file drop zone. Takes a drop or a click, lists what was picked, and lets each
4
+ entry be removed.
5
+
6
+ ```html
7
+ <kt-drag-drop accept="image/*" max-size="2097152" recommended-size="800×400px"></kt-drag-drop>
8
+ ```
9
+
10
+ ```js
11
+ zone.addEventListener('kt-files-change', (e) => console.log(e.detail.files));
12
+ zone.addEventListener('kt-files-rejected', (e) => {
13
+ toast.error(e.detail.reason === 'size' ? 'File too large' : 'Unsupported format');
14
+ });
15
+ ```
16
+
17
+ The element holds the files and reports them. **Uploading is yours** — it makes
18
+ no requests.
19
+
20
+ ## In a form
21
+
22
+ Give it a `name` and it is a form control: a `<form>` submits every held file
23
+ under that name, exactly as `<input type="file" multiple>` would, so a
24
+ `multipart/form-data` post needs no extra code. Without a `name` it submits
25
+ nothing, like the native input. `required` blocks submission until a file is
26
+ held, and `form.reset()` empties it.
27
+
28
+ ```html
29
+ <form method="post" enctype="multipart/form-data">
30
+ <kt-drag-drop name="attachments" required></kt-drag-drop>
31
+ <kt-button type="submit">Send</kt-button>
32
+ </form>
33
+ ```
34
+
35
+ ## Validation
36
+
37
+ `accept` on a native input is advisory: the OS dialog filters by it, but a
38
+ _dropped_ file bypasses it completely. So `<kt-drag-drop>` re-checks every file
39
+ itself, by extension (`.pdf`), by mime prefix (`image/*`) or by exact type, and
40
+ fires `kt-files-rejected` with what it turned away and why.
41
+
42
+ `max-size` is in bytes and has no native equivalent at all.
43
+
44
+ ## Accessibility
45
+
46
+ The zone is `role="button"` with a tab stop and Enter/Space activation, so it
47
+ can be used without a pointer — a drop zone reachable only by dragging is
48
+ unusable for anyone who does not drag. Each file's remove button carries the
49
+ file name in its accessible label, rather than nine identical "Delete"
50
+ buttons.
51
+
52
+ ## API
53
+
54
+ | Property | Attribute | Type | Default |
55
+ | ----------------- | ------------------ | --------- | --------------------- |
56
+ | `heading` | `heading` | `string` | `'Drag and drop or'` |
57
+ | `linkText` | `link-text` | `string` | `'browse your files'` |
58
+ | `recommendedSize` | `recommended-size` | `string` | `''` |
59
+ | `multiple` | `multiple` | `boolean` | `true` |
60
+ | `accept` | `accept` | `string` | `''` |
61
+ | `maxSize` | `max-size` | `number` | `0` (no limit) |
62
+ | `disabled` | `disabled` | `boolean` | `false` |
63
+ | `name` | `name` | `string` | `''` |
64
+ | `required` | `required` | `boolean` | `false` |
65
+
66
+ `heading` rather than `title`: an element already has a `title`, and reusing it
67
+ would drop the prompt into a native tooltip over the whole zone.
68
+
69
+ | Getter | Returns |
70
+ | --------------- | ----------------- |
71
+ | `selectedFiles` | `readonly File[]` |
72
+
73
+ | Method | Description |
74
+ | --------- | ---------------------------------------------- |
75
+ | `clear()` | Empties the selection, fires `kt-files-change` |
76
+
77
+ | Event | Detail |
78
+ | ------------------- | --------------------------------------------- |
79
+ | `kt-files-change` | `{ files: File[] }` — the full current list |
80
+ | `kt-files-rejected` | `{ files: File[], reason: 'type' \| 'size' }` |
81
+
82
+ | Part | Description |
83
+ | ------ | -------------------- |
84
+ | `base` | The dashed drop zone |
85
+ | `list` | The list of files |
@@ -0,0 +1,65 @@
1
+ # `<kt-dropdown>`
2
+
3
+ A panel anchored to a trigger. The generic overlay in the system.
4
+
5
+ ```html
6
+ <kt-dropdown>
7
+ <kt-button slot="trigger" variant="dark" icon="ellipsis" label="Actions"></kt-button>
8
+ </kt-dropdown>
9
+ ```
10
+
11
+ ```js
12
+ menu.options = [
13
+ { id: 'edit', label: 'Edit' },
14
+ { id: 'delete', label: 'Delete' },
15
+ ];
16
+ menu.addEventListener('kt-select', (e) => run(e.detail.value));
17
+ ```
18
+
19
+ Leave `options` empty and slot arbitrary content into `panel` instead — a
20
+ filter form, a colour picker, a preview.
21
+
22
+ **For choosing a value in a form, use `<kt-select>` or `<kt-input-menu>`.** They
23
+ carry the listbox semantics and the keyboard model this element deliberately
24
+ does not assume; a dropdown is a menu of actions.
25
+
26
+ ## Placement
27
+
28
+ The panel goes below the trigger, and flips above it when there is not enough
29
+ room — or, when neither side fits, to whichever has more. Set
30
+ `preferred-placement="top"` to reverse the preference.
31
+
32
+ Placement is recalculated on open and on window resize.
33
+
34
+ Horizontally the panel lines up with the trigger's left edge. `align="end"`
35
+ lines it up with the right edge instead — for a trigger that sits at the right
36
+ of the thing it belongs to, such as the caret of a `<kt-split-button>`.
37
+
38
+ ## API
39
+
40
+ | Property | Attribute | Type | Default |
41
+ | -------------------- | --------------------- | -------------------------- | -------------- |
42
+ | `options` | — | `KtOption[]` | `[]` |
43
+ | `value` | `value` | `string \| number \| null` | `null` |
44
+ | `preferredPlacement` | `preferred-placement` | `'bottom' \| 'top'` | `'bottom'` |
45
+ | `align` | `align` | `'start' \| 'end'` | `'start'` |
46
+ | `disabled` | `disabled` | `boolean` | `false` |
47
+ | `emptyText` | `empty-text` | `string` | `'No results'` |
48
+
49
+ | Method / getter | Description |
50
+ | --------------- | --------------------- |
51
+ | `show()` | Opens the panel |
52
+ | `hide()` | Closes it |
53
+ | `toggle()` | Either |
54
+ | `isOpen` | Whether it is showing |
55
+
56
+ | Event | Detail |
57
+ | ----------- | ------------------- |
58
+ | `kt-select` | `{ value, option }` |
59
+ | `kt-open` | — |
60
+ | `kt-close` | — |
61
+
62
+ | Slot | Description |
63
+ | --------- | --------------------------- |
64
+ | `trigger` | What opens the panel |
65
+ | `panel` | Contents, without `options` |
@@ -0,0 +1,36 @@
1
+ # `<kt-empty-state>`
2
+
3
+ What a screen shows when it has nothing to show.
4
+
5
+ ```html
6
+ <kt-empty-state
7
+ icon="inbox"
8
+ heading="Nothing in your inbox"
9
+ description="New messages will appear here as they arrive."
10
+ >
11
+ <kt-button slot="actions" icon="plus">Compose</kt-button>
12
+ </kt-empty-state>
13
+ ```
14
+
15
+ Worth a component because the empty case is where products are usually
16
+ thinnest, and where they most need to say what happens next. An icon, a
17
+ sentence and an action beat a blank panel every time — and "no results" after a
18
+ search is a different message from "nothing here yet", so the wording is a
19
+ property rather than a default.
20
+
21
+ Use `compact` inside a card or a panel, where the full 48px of padding would
22
+ push everything else off screen.
23
+
24
+ ## API
25
+
26
+ | Property | Attribute | Type | Default |
27
+ | ------------- | ------------- | --------- | --------- |
28
+ | `icon` | `icon` | `string` | `'inbox'` |
29
+ | `heading` | `heading` | `string` | `''` |
30
+ | `description` | `description` | `string` | `''` |
31
+ | `compact` | `compact` | `boolean` | `false` |
32
+
33
+ | Slot | Description |
34
+ | --------- | --------------------------------------------- |
35
+ | _default_ | Extra content between description and actions |
36
+ | `actions` | Buttons |
@@ -0,0 +1,93 @@
1
+ # `<kt-form>`
2
+
3
+ A panel grouping related fields.
4
+
5
+ ```html
6
+ <form>
7
+ <kt-form heading="Identity" description="How we address you.">
8
+ <kt-label-input label="Company" required>
9
+ <kt-input name="company" required></kt-input>
10
+ </kt-label-input>
11
+ <kt-label-input label="Email address" required>
12
+ <kt-input name="email" type="email" required></kt-input>
13
+ </kt-label-input>
14
+
15
+ <kt-button slot="footer" variant="dark" type="reset">Reset</kt-button>
16
+ <kt-button slot="footer" type="submit">Save</kt-button>
17
+ </kt-form>
18
+ </form>
19
+ ```
20
+
21
+ ## The surface
22
+
23
+ The panel sits on `--surface-form`, one step above the page. Fields inside are
24
+ filled at `--surface-field`, one step _below_ it — so they read as wells
25
+ recessed into the panel rather than as boxes floating on it. The relationship
26
+ survives the light theme, because both are roles rather than ramp steps.
27
+
28
+ ## Put it inside your own `<form>`
29
+
30
+ This renders a `<fieldset>`, not a form, and that is deliberate.
31
+
32
+ Kanto fields are form-associated custom elements, and a control finds its form
33
+ by walking **its own tree**. A `<form>` inside this element's shadow root is not
34
+ an ancestor of anything slotted into it, so it would own nothing and
35
+ `new FormData(form)` would come back empty. The `<form>` above stays in charge
36
+ of submission, validation and reset; the panel owns the grouping and the look.
37
+
38
+ Several panels in one form is the usual shape:
39
+
40
+ ```html
41
+ <form>
42
+ <kt-form heading="Identity">…</kt-form>
43
+ <kt-form heading="Subscription">…</kt-form>
44
+ <kt-button type="submit">Save</kt-button>
45
+ </form>
46
+ ```
47
+
48
+ ## A fieldset, so the group has a name
49
+
50
+ The heading is the `<legend>`, which is what gives the group its accessible
51
+ name — a screen reader announces "Identity, group" as focus enters it. Without
52
+ a heading the legend stays in the accessibility tree but is visually hidden,
53
+ rather than being dropped.
54
+
55
+ ## Disabling
56
+
57
+ `disabled` switches off every slotted control. A native `<fieldset disabled>`
58
+ does this for its descendants, but slotted fields are not descendants of the
59
+ fieldset in their own tree — the shadow boundary sits between them — so the
60
+ platform never reaches them and the panel walks them itself.
61
+
62
+ ## Loading
63
+
64
+ `loading` draws placeholder fields in front of the real ones. The fields stay
65
+ mounted and keep their values, so a panel that reloads its options does not
66
+ throw away what the user already typed.
67
+
68
+ ```html
69
+ <kt-form heading="Identity" loading loading-fields="4"></kt-form>
70
+ ```
71
+
72
+ ## API
73
+
74
+ | Property | Attribute | Type | Default |
75
+ | --------------- | ---------------- | --------- | ------- |
76
+ | `heading` | `heading` | `string` | `''` |
77
+ | `description` | `description` | `string` | `''` |
78
+ | `loading` | `loading` | `boolean` | `false` |
79
+ | `loadingFields` | `loading-fields` | `number` | `3` |
80
+ | `disabled` | `disabled` | `boolean` | `false` |
81
+
82
+ | Slot | Description |
83
+ | --------- | -------------------------------------------- |
84
+ | _default_ | The fields |
85
+ | `heading` | Replaces the `heading` attribute, for markup |
86
+ | `footer` | Actions, right-aligned. Hidden when empty. |
87
+
88
+ | Part | Description |
89
+ | -------- | ---------------- |
90
+ | `base` | The `<fieldset>` |
91
+ | `header` | The description |
92
+ | `fields` | The field column |
93
+ | `footer` | The action row |
@@ -0,0 +1,100 @@
1
+ # `<kt-header>`
2
+
3
+ The application header: a banner landmark holding the brand, the primary
4
+ navigation and a row of actions.
5
+
6
+ ```html
7
+ <kt-header sticky>
8
+ <a slot="brand" href="/">KANTO</a>
9
+
10
+ <nav>
11
+ <a href="/docs">Docs</a>
12
+ <a href="/components">Components</a>
13
+ </nav>
14
+
15
+ <kt-button slot="actions" size="small" variant="dark" icon="search">Search</kt-button>
16
+
17
+ <nav slot="menu">
18
+ <a href="/docs">Docs</a>
19
+ <a href="/components">Components</a>
20
+ </nav>
21
+
22
+ <kt-tabs slot="bottom"></kt-tabs>
23
+ </kt-header>
24
+ ```
25
+
26
+ ## Slots
27
+
28
+ | Slot | Where |
29
+ | --------- | ---------------------------------------------------------- |
30
+ | `brand` | Start of the bar. Logo or wordmark. |
31
+ | _default_ | Centre. Primary navigation. |
32
+ | `actions` | End. Stays visible at every width. |
33
+ | `menu` | What the menu button reveals below the breakpoint. |
34
+ | `bottom` | A second row under the bar — sub-navigation, a breadcrumb. |
35
+
36
+ `menu` is a separate slot rather than a reuse of the default one because a node
37
+ can only be assigned to one slot. Put a compact copy of the navigation there —
38
+ usually a plain stacked list rather than the horizontal bar.
39
+
40
+ ## Centring
41
+
42
+ The bar is a three-column grid whose outer columns are equal, so the navigation
43
+ sits in the middle of the **bar** rather than in the middle of whatever the
44
+ brand and the actions left over. With a flex row it lands off-centre by half the
45
+ difference between those two, which is small enough to look like a mistake
46
+ rather than like a choice.
47
+
48
+ Everything in the default slot is centred as one group, so put anything that
49
+ belongs beside the wordmark — a version chip, an environment badge — in the
50
+ `brand` slot rather than in the default one.
51
+
52
+ Set `nav-align="start"` to butt the navigation against the brand instead, for a
53
+ header whose middle column is a search field or a breadcrumb.
54
+
55
+ ## Collapsing
56
+
57
+ Below `--kt-header-breakpoint` (900px) the default slot is hidden and a menu
58
+ button appears; pressing it reveals the `menu` slot as a panel under the bar.
59
+
60
+ The breakpoint is a token rather than a fixed media query because how much
61
+ navigation fits depends entirely on how much navigation you put in — a header
62
+ with two links and one with eight do not want the same threshold.
63
+
64
+ Call `closeMenu()` after routing, or the panel stays open over the new page.
65
+
66
+ ## Sticky and translucent
67
+
68
+ `sticky` pins the header and makes it translucent with a backdrop blur — but
69
+ only where `backdrop-filter` is supported. Without the blur it stays opaque: a
70
+ semi-transparent bar over scrolling content with nothing blurring it is simply
71
+ hard to read.
72
+
73
+ ## API
74
+
75
+ | Property | Attribute | Type | Default |
76
+ | ---------- | ----------- | --------------------- | ---------- |
77
+ | `sticky` | `sticky` | `boolean` | `false` |
78
+ | `bordered` | `bordered` | `boolean` | `true` |
79
+ | `label` | `label` | `string` | `''` |
80
+ | `navAlign` | `nav-align` | `'center' \| 'start'` | `'center'` |
81
+
82
+ | Method / getter | Description |
83
+ | --------------- | ------------------------- |
84
+ | `closeMenu()` | Shuts the collapsed panel |
85
+ | `isMenuOpen` | Whether it is showing |
86
+
87
+ | Event | Detail |
88
+ | ---------------- | ------------------- |
89
+ | `kt-menu-toggle` | `{ open: boolean }` |
90
+
91
+ | CSS property | Default | Description |
92
+ | ------------------------ | ------- | ------------------------------ |
93
+ | `--kt-header-height` | `64px` | Height of the main row |
94
+ | `--kt-header-breakpoint` | `900px` | Where the navigation collapses |
95
+
96
+ | Part | Description |
97
+ | -------- | -------------- |
98
+ | `base` | The `<header>` |
99
+ | `bar` | The main row |
100
+ | `bottom` | The second row |
@@ -0,0 +1,68 @@
1
+ # `<kt-icon>`
2
+
3
+ A [Lucide](https://lucide.dev) icon, resolved from the registry by name.
4
+
5
+ Default 24px, stroke width 2, drawn in `currentColor` — the icon takes the
6
+ colour of the text around it, with no attribute to keep in sync.
7
+
8
+ ```html
9
+ <kt-icon name="search"></kt-icon>
10
+ <kt-icon name="trash-2" size="16"></kt-icon>
11
+ <kt-icon name="circle-alert" style="color: var(--color-danger-base)"></kt-icon>
12
+ ```
13
+
14
+ ## Registering icons
15
+
16
+ Kanto ships the 24 icons its own elements draw and nine a first screen reaches
17
+ for — `plus`, `pencil`, `settings`, `download`, `upload`, `external-link`,
18
+ `filter`, `ellipsis`, `refresh-cw`. Register whatever else the application
19
+ uses, once, at start-up:
20
+
21
+ ```js
22
+ import { Rocket, Wallet } from 'lucide';
23
+ import { registerIcons } from 'kanto-ds/icons';
24
+
25
+ registerIcons({ Rocket, Wallet }); // now <kt-icon name="rocket">
26
+ ```
27
+
28
+ Names normalise to kebab-case, so `Rocket`, `rocket` and `ROCKET` all resolve
29
+ to `rocket`.
30
+
31
+ Prototyping and can afford the weight? Register the lot:
32
+
33
+ ```js
34
+ import * as lucide from 'lucide';
35
+ registerIcons(lucide); // ~2000 icons, do not ship this
36
+ ```
37
+
38
+ ## Accessibility
39
+
40
+ By default the icon is `aria-hidden` — correct for the common case, where it
41
+ sits next to text that already names the action.
42
+
43
+ Give it a `label` only when it carries meaning alone, which makes it
44
+ `role="img"` with that accessible name:
45
+
46
+ ```html
47
+ <!-- text says it; icon is decoration -->
48
+ <kt-button icon="trash-2">Delete</kt-button>
49
+
50
+ <!-- icon says it; it needs a name -->
51
+ <kt-icon name="trash-2" label="Delete"></kt-icon>
52
+ ```
53
+
54
+ ## API
55
+
56
+ | Property | Attribute | Type | Default | Description |
57
+ | ------------- | -------------- | -------- | ------- | --------------------------------- |
58
+ | `name` | `name` | `string` | `''` | Registered icon name, kebab-case. |
59
+ | `size` | `size` | `number` | `24` | Width and height, in pixels. |
60
+ | `strokeWidth` | `stroke-width` | `number` | `2` | SVG stroke width. |
61
+ | `label` | `label` | `string` | `''` | Accessible name. Empty = hidden. |
62
+
63
+ | Part | Description |
64
+ | ----- | ---------------- |
65
+ | `svg` | The `<svg>` root |
66
+
67
+ An unknown name renders an empty box of the right size and warns once in the
68
+ console — a typo shifts no layout while you find it.
@@ -0,0 +1,90 @@
1
+ # `<kt-input-menu>`
2
+
3
+ A combobox: type to narrow the list, then pick.
4
+
5
+ ```html
6
+ <kt-input-menu placeholder="Search a country"></kt-input-menu>
7
+ ```
8
+
9
+ ```js
10
+ menu.options = [
11
+ { id: 'fr', label: 'France' },
12
+ { id: 'be', label: 'Belgium' },
13
+ { id: 'ch', label: 'Switzerland' },
14
+ ];
15
+
16
+ menu.addEventListener('kt-change', (e) => console.log(e.detail.value));
17
+ menu.addEventListener('kt-filter', (e) => console.log(e.detail.query));
18
+ ```
19
+
20
+ ## Against `<kt-select>`
21
+
22
+ Both choose one option from a list. Reach for the combobox once scanning the
23
+ list is slower than typing three letters — roughly twenty options. Below that,
24
+ `<kt-select>` is less work for the user: no typing, and the whole list is
25
+ visible.
26
+
27
+ ## One field, two jobs
28
+
29
+ While the list is open and a query has been typed, the field shows the query.
30
+ Otherwise it shows the chosen option's label. That single field is what makes
31
+ this a combobox rather than a search box that happens to sit above a list.
32
+
33
+ Escape closes and forgets the query, leaving the previous selection intact.
34
+
35
+ ## Server-side filtering
36
+
37
+ `kt-filter` fires on every keystroke, so a large or remote list can be fetched
38
+ instead of filtered locally — assign the results back to `options` and the
39
+ element renders them.
40
+
41
+ ```js
42
+ menu.addEventListener('kt-filter', async (e) => {
43
+ menu.options = await search(e.detail.query);
44
+ });
45
+ ```
46
+
47
+ ## In a form
48
+
49
+ `<kt-input-menu>` is a form control. Inside a `<form>` it submits the chosen
50
+ option's `id` under `name` — never the label, and nothing at all while empty.
51
+ `required` blocks submission until something is chosen, and `form.reset()` puts
52
+ back the initial `value`.
53
+
54
+ ## Keyboard
55
+
56
+ | Key | Does |
57
+ | -------------- | ---------------------------------------------- |
58
+ | `↓` / `↑` | Opens the list, then moves the active option |
59
+ | `Home` / `End` | First / last match |
60
+ | `Enter` | Commits the active option |
61
+ | `Escape` | Closes, forgets the query, keeps the selection |
62
+ | `Tab` | Closes and moves on |
63
+
64
+ The arrows walk the **filtered** list, not the full one.
65
+
66
+ ## API
67
+
68
+ | Property | Attribute | Type | Default |
69
+ | ------------- | ------------- | -------------------------- | --------------- |
70
+ | `options` | — | `KtOption[]` | `[]` |
71
+ | `value` | `value` | `string \| number \| null` | `null` |
72
+ | `placeholder` | `placeholder` | `string` | `''` |
73
+ | `name` | `name` | `string` | `''` |
74
+ | `disabled` | `disabled` | `boolean` | `false` |
75
+ | `required` | `required` | `boolean` | `false` |
76
+ | `error` | `error` | `string` | `''` |
77
+ | `label` | `label` | `string` | `''` |
78
+ | `emptyText` | `empty-text` | `string` | `'No options…'` |
79
+
80
+ | Event | Detail |
81
+ | ----------- | ------------------- |
82
+ | `kt-change` | `{ value, option }` |
83
+ | `kt-filter` | `{ query: string }` |
84
+
85
+ | Part | Description |
86
+ | --------- | -------------------- |
87
+ | `control` | The field |
88
+ | `input` | The native `<input>` |
89
+ | `popup` | The option list |
90
+ | `option` | An option row |