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.
- package/README.md +56 -6
- package/dist/cdn/kanto.min.js +6619 -0
- package/dist/chunks/defaults-Cpeg13O5.js +68 -0
- package/dist/chunks/defaults-Cpeg13O5.js.map +1 -0
- package/dist/chunks/{floating-B33g-M_1.js → floating-C9RLa6cZ.js} +2 -2
- package/dist/chunks/floating-C9RLa6cZ.js.map +1 -0
- package/dist/components/core/kt-icon/kt-icon.js +1 -1
- package/dist/components/forms/kt-date-input/kt-date-input.js +1 -1
- package/dist/components/forms/kt-date-picker/kt-date-picker.js +1 -1
- package/dist/components/forms/kt-input/kt-input.js +1 -1
- package/dist/components/forms/kt-input-menu/kt-input-menu.js +1 -1
- package/dist/components/forms/kt-multi-select/kt-multi-select.js +1 -1
- package/dist/components/forms/kt-select/kt-select.js +1 -1
- package/dist/components/overlays/kt-dropdown/kt-dropdown.js +1 -1
- package/dist/docs/kt-alert.md +52 -0
- package/dist/docs/kt-avatar.md +46 -0
- package/dist/docs/kt-badge.md +104 -0
- package/dist/docs/kt-breadcrumb.md +53 -0
- package/dist/docs/kt-button.md +72 -0
- package/dist/docs/kt-calendar.md +98 -0
- package/dist/docs/kt-card.md +93 -0
- package/dist/docs/kt-chart.md +230 -0
- package/dist/docs/kt-checkbox.md +65 -0
- package/dist/docs/kt-code.md +88 -0
- package/dist/docs/kt-collapsible.md +50 -0
- package/dist/docs/kt-confirm-dialog.md +52 -0
- package/dist/docs/kt-date-input.md +97 -0
- package/dist/docs/kt-date-picker.md +116 -0
- package/dist/docs/kt-drag-drop.md +85 -0
- package/dist/docs/kt-dropdown.md +65 -0
- package/dist/docs/kt-empty-state.md +36 -0
- package/dist/docs/kt-form.md +93 -0
- package/dist/docs/kt-header.md +100 -0
- package/dist/docs/kt-icon.md +68 -0
- package/dist/docs/kt-input-menu.md +90 -0
- package/dist/docs/kt-input.md +137 -0
- package/dist/docs/kt-kbd.md +44 -0
- package/dist/docs/kt-label-input.md +54 -0
- package/dist/docs/kt-meter.md +79 -0
- package/dist/docs/kt-modal.md +62 -0
- package/dist/docs/kt-multi-select.md +115 -0
- package/dist/docs/kt-page-header.md +53 -0
- package/dist/docs/kt-pagination.md +36 -0
- package/dist/docs/kt-progress-bar.md +66 -0
- package/dist/docs/kt-radio-group.md +73 -0
- package/dist/docs/kt-segmented-control.md +70 -0
- package/dist/docs/kt-select.md +80 -0
- package/dist/docs/kt-side-panel.md +59 -0
- package/dist/docs/kt-skeleton.md +46 -0
- package/dist/docs/kt-split-button.md +105 -0
- package/dist/docs/kt-stat.md +57 -0
- package/dist/docs/kt-sub-menu-navigation.md +118 -0
- package/dist/docs/kt-table.md +179 -0
- package/dist/docs/kt-tabs.md +87 -0
- package/dist/docs/kt-textarea.md +59 -0
- package/dist/docs/kt-time-input.md +88 -0
- package/dist/docs/kt-timeline.md +63 -0
- package/dist/docs/kt-toast-container.md +69 -0
- package/dist/docs/kt-toast.md +41 -0
- package/dist/docs/kt-toggle-button-group.md +67 -0
- package/dist/docs/kt-toggle-button.md +52 -0
- package/dist/docs/kt-toggle.md +47 -0
- package/dist/docs/kt-tooltip.md +68 -0
- package/dist/icons/defaults.d.ts +9 -0
- package/dist/icons/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/floating.d.ts +4 -0
- package/dist/tokens/colors.css +5 -2
- package/dist/web-types.json +1 -1
- package/package.json +29 -6
- package/dist/chunks/defaults-CFrjqae2.js +0 -59
- package/dist/chunks/defaults-CFrjqae2.js.map +0 -1
- 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 |
|