kanto-ds 1.6.1 → 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 +52 -4
- package/dist/cdn/kanto.min.js +6619 -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/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/internal/floating.d.ts +4 -0
- package/dist/tokens/colors.css +5 -2
- package/dist/web-types.json +1 -1
- package/package.json +23 -5
- package/dist/chunks/floating-B33g-M_1.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# `<kt-radio-group>`
|
|
2
|
+
|
|
3
|
+
One choice among a few, all visible at once. The options are `<kt-radio>`
|
|
4
|
+
children.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<kt-radio-group label="Billing" name="billing" value="monthly">
|
|
8
|
+
<kt-radio value="monthly">Monthly</kt-radio>
|
|
9
|
+
<kt-radio value="yearly">Yearly — two months free</kt-radio>
|
|
10
|
+
<kt-radio value="custom" disabled>Custom contract</kt-radio>
|
|
11
|
+
</kt-radio-group>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Radio group, select or segmented control
|
|
15
|
+
|
|
16
|
+
- **Radio group** — few enough options to show them all, and seeing them side
|
|
17
|
+
by side helps the choice. Options can be a sentence long.
|
|
18
|
+
- **`<kt-select>`** — more options than fit, or the choice is minor.
|
|
19
|
+
- **`<kt-segmented-control>`** — two to five short options in a toolbar, such
|
|
20
|
+
as a view switch.
|
|
21
|
+
|
|
22
|
+
## Keyboard
|
|
23
|
+
|
|
24
|
+
Native radios cannot form a group across shadow roots, so this is the ARIA
|
|
25
|
+
radio group pattern — the one a screen reader expects:
|
|
26
|
+
|
|
27
|
+
| Key | Does |
|
|
28
|
+
| --------- | ------------------------------------------------------- |
|
|
29
|
+
| `Tab` | Enters and leaves the group: one tab stop for all of it |
|
|
30
|
+
| `↓` / `→` | Next option, and selects it |
|
|
31
|
+
| `↑` / `←` | Previous option, and selects it |
|
|
32
|
+
| `Space` | Selects the focused option |
|
|
33
|
+
|
|
34
|
+
The arrows wrap at the ends and skip disabled options. The tab stop sits on the
|
|
35
|
+
chosen option, or on the first enabled one while nothing is chosen.
|
|
36
|
+
|
|
37
|
+
## In a form
|
|
38
|
+
|
|
39
|
+
A form control: it submits the chosen `value` under `name`, and nothing while
|
|
40
|
+
none is chosen. `required` blocks submission until an option is chosen, `error`
|
|
41
|
+
shows a message and marks every option, and `form.reset()` puts back the
|
|
42
|
+
initial `value`.
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
`<kt-radio-group>`
|
|
47
|
+
|
|
48
|
+
| Property | Attribute | Type | Default |
|
|
49
|
+
| ------------- | ------------- | ---------------------------- | ------------ |
|
|
50
|
+
| `value` | `value` | `string \| null` | `null` |
|
|
51
|
+
| `name` | `name` | `string` | `''` |
|
|
52
|
+
| `label` | `label` | `string` | `''` |
|
|
53
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
54
|
+
| `required` | `required` | `boolean` | `false` |
|
|
55
|
+
| `orientation` | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` |
|
|
56
|
+
| `error` | `error` | `string` | `''` |
|
|
57
|
+
|
|
58
|
+
`<kt-radio>`
|
|
59
|
+
|
|
60
|
+
| Property | Attribute | Type | Default |
|
|
61
|
+
| ---------- | ---------- | --------- | ------- |
|
|
62
|
+
| `value` | `value` | `string` | `''` |
|
|
63
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
64
|
+
|
|
65
|
+
| Event | Detail |
|
|
66
|
+
| ----------- | --------------------------- |
|
|
67
|
+
| `kt-change` | `{ value: string \| null }` |
|
|
68
|
+
|
|
69
|
+
| Part | Element | Description |
|
|
70
|
+
| -------- | ---------------- | --------------------------- |
|
|
71
|
+
| `base` | `kt-radio-group` | The `role="radiogroup"` box |
|
|
72
|
+
| `base` | `kt-radio` | The `role="radio"` row |
|
|
73
|
+
| `circle` | `kt-radio` | The drawn circle |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# `<kt-segmented-control>`
|
|
2
|
+
|
|
3
|
+
A small set of mutually exclusive choices, drawn as one inset track with the
|
|
4
|
+
selected segment raised out of it.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
control.options = [
|
|
8
|
+
{ value: 'day', label: 'Day' },
|
|
9
|
+
{ value: 'week', label: 'Week' },
|
|
10
|
+
{ value: 'month', label: 'Month' },
|
|
11
|
+
];
|
|
12
|
+
control.value = 'week';
|
|
13
|
+
control.addEventListener('kt-change', (e) => setRange(e.detail.value));
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Two to five options that all fit on screen — a view switch, a date range. Past
|
|
17
|
+
that, `<kt-select>`.
|
|
18
|
+
|
|
19
|
+
## Keyboard
|
|
20
|
+
|
|
21
|
+
It is a radio group, so the whole control is **one tab stop** and the arrows
|
|
22
|
+
move between segments — a five-option control does not add five stops to the
|
|
23
|
+
page.
|
|
24
|
+
|
|
25
|
+
| Key | Does |
|
|
26
|
+
| -------------------- | --------------------------------------- |
|
|
27
|
+
| `←` / `→` | Previous / next segment, and selects it |
|
|
28
|
+
| `↑` / `↓` (vertical) | Same, along the other axis |
|
|
29
|
+
| `Home` / `End` | First / last segment |
|
|
30
|
+
|
|
31
|
+
Arrows both move and select, which is standard radio-group behaviour. Disabled
|
|
32
|
+
segments are skipped, and the selection wraps at the ends.
|
|
33
|
+
|
|
34
|
+
## In a form
|
|
35
|
+
|
|
36
|
+
It is a form control: inside a `<form>` it submits the chosen `value` under
|
|
37
|
+
`name`, `required` blocks submission until a segment is chosen, and
|
|
38
|
+
`form.reset()` puts back the initial value. Use it for a choice the form sends —
|
|
39
|
+
a billing period, a plan — as readily as for a view switch.
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
| Property | Attribute | Type | Default |
|
|
44
|
+
| ------------- | ------------- | -------------------------------- | -------------- |
|
|
45
|
+
| `options` | — | `KtSegmentedOption[]` | `[]` |
|
|
46
|
+
| `value` | `value` | `string \| number \| null` | `null` |
|
|
47
|
+
| `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
48
|
+
| `orientation` | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` |
|
|
49
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
50
|
+
| `label` | `label` | `string` | `''` |
|
|
51
|
+
| `name` | `name` | `string` | `''` |
|
|
52
|
+
| `required` | `required` | `boolean` | `false` |
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface KtSegmentedOption {
|
|
56
|
+
value: string | number;
|
|
57
|
+
label?: string; // omit with an icon for an icon-only segment
|
|
58
|
+
icon?: string;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Event | Detail |
|
|
64
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
65
|
+
| `kt-change` | `{ value, option }` — not fired when the active segment is re-selected |
|
|
66
|
+
|
|
67
|
+
| Part | Description |
|
|
68
|
+
| -------- | ----------- |
|
|
69
|
+
| `base` | The track |
|
|
70
|
+
| `option` | A segment |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# `<kt-select>`
|
|
2
|
+
|
|
3
|
+
A single-choice dropdown.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<kt-select placeholder="Select a region" name="region"></kt-select>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
select.options = [
|
|
11
|
+
{ id: 'ne', label: 'North East' },
|
|
12
|
+
{ id: 'sw', label: 'South West' },
|
|
13
|
+
{ id: 'nw', label: 'North West', disabled: true },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
select.addEventListener('kt-change', (e) => console.log(e.detail.value, e.detail.option));
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`options` is a **property, not an attribute** — it is data, and serialising an
|
|
20
|
+
array of objects through an HTML attribute is a mistake you only make once.
|
|
21
|
+
|
|
22
|
+
## Keyboard
|
|
23
|
+
|
|
24
|
+
This is a real listbox, not a button next to a floating div:
|
|
25
|
+
|
|
26
|
+
| Key | Does |
|
|
27
|
+
| ----------------- | -------------------------------------------- |
|
|
28
|
+
| `↓` / `↑` | Opens the list, then moves the active option |
|
|
29
|
+
| `Home` / `End` | Jumps to the first / last selectable option |
|
|
30
|
+
| `Enter` / `Space` | Opens, then commits the active option |
|
|
31
|
+
| `Escape` | Closes, changing nothing |
|
|
32
|
+
| `Tab` | Closes and moves on |
|
|
33
|
+
|
|
34
|
+
Disabled options are skipped by the arrows rather than merely being
|
|
35
|
+
unclickable, and the trigger carries `aria-activedescendant` so a screen reader
|
|
36
|
+
follows the highlight.
|
|
37
|
+
|
|
38
|
+
## Animation
|
|
39
|
+
|
|
40
|
+
The popup stays in the DOM and animates through `opacity` and `transform`, held
|
|
41
|
+
out of the way with `visibility: hidden`. A popup that unmounts cannot animate
|
|
42
|
+
out; one that only toggles `display` cannot animate at all.
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
| Property | Attribute | Type | Default |
|
|
47
|
+
| ------------- | ------------- | -------------------------------- | --------------- |
|
|
48
|
+
| `options` | — | `KtOption[]` | `[]` |
|
|
49
|
+
| `value` | `value` | `string \| number \| null` | `null` |
|
|
50
|
+
| `placeholder` | `placeholder` | `string` | `'Select'` |
|
|
51
|
+
| `name` | `name` | `string` | `''` |
|
|
52
|
+
| `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
53
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
54
|
+
| `required` | `required` | `boolean` | `false` |
|
|
55
|
+
| `error` | `error` | `string` | `''` |
|
|
56
|
+
| `clearable` | `clearable` | `boolean` | `true` |
|
|
57
|
+
| `label` | `label` | `string` | `''` |
|
|
58
|
+
| `emptyText` | `empty-text` | `string` | `'No options…'` |
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface KtOption {
|
|
62
|
+
id: string | number;
|
|
63
|
+
label?: string; // falls back to String(id)
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Getter | Returns |
|
|
69
|
+
| ---------------- | ----------------------------------- |
|
|
70
|
+
| `selectedOption` | The chosen `KtOption`, or undefined |
|
|
71
|
+
|
|
72
|
+
| Event | Detail |
|
|
73
|
+
| ----------- | --------------------------------------------------------------- |
|
|
74
|
+
| `kt-change` | `{ value: string \| number \| null, option: KtOption \| null }` |
|
|
75
|
+
|
|
76
|
+
| Part | Description |
|
|
77
|
+
| --------- | --------------- |
|
|
78
|
+
| `trigger` | The button |
|
|
79
|
+
| `popup` | The option list |
|
|
80
|
+
| `option` | An option row |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# `<kt-side-panel>`
|
|
2
|
+
|
|
3
|
+
A drawer sliding in from the right, for viewing or editing one record without
|
|
4
|
+
losing the list behind it.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<kt-side-panel open eyebrow="Details" heading="Entity 4812">
|
|
8
|
+
<kt-label-input label="Nom"><kt-input value="Entity 4812"></kt-input></kt-label-input>
|
|
9
|
+
<kt-button slot="footer" full-width>Save</kt-button>
|
|
10
|
+
</kt-side-panel>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Built on `<dialog>`
|
|
14
|
+
|
|
15
|
+
`showModal()` is doing real work here. It puts the panel in the **top layer**,
|
|
16
|
+
above every stacking context — which no z-index can guarantee — traps focus
|
|
17
|
+
inside it, makes the rest of the page inert, and handles Escape.
|
|
18
|
+
|
|
19
|
+
The fixed-position div this replaces did none of that: tabbing walked straight
|
|
20
|
+
out of the panel into the page behind it.
|
|
21
|
+
|
|
22
|
+
## Guarding unsaved changes
|
|
23
|
+
|
|
24
|
+
`kt-close` is cancelable. Cancel it and the panel stays open — including when
|
|
25
|
+
Escape was what triggered it.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
panel.addEventListener('kt-close', (e) => {
|
|
29
|
+
if (!form.dirty) return;
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
confirmDialog.open = true;
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`no-backdrop-close` removes backdrop dismissal. Escape still works: a modal you
|
|
36
|
+
cannot escape from is a trap, and cancelling `kt-close` is the right way to
|
|
37
|
+
guard it.
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
| Property | Attribute | Type | Default |
|
|
42
|
+
| ----------------- | ------------------- | --------- | ----------- |
|
|
43
|
+
| `open` | `open` | `boolean` | `false` |
|
|
44
|
+
| `eyebrow` | `eyebrow` | `string` | `'Details'` |
|
|
45
|
+
| `heading` | `heading` | `string` | `''` |
|
|
46
|
+
| `noBackdropClose` | `no-backdrop-close` | `boolean` | `false` |
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
| ---------------- | ----------------------------------------- |
|
|
50
|
+
| `requestClose()` | Fires `kt-close`, closes unless cancelled |
|
|
51
|
+
|
|
52
|
+
| Event | Description |
|
|
53
|
+
| ---------- | -------------------------------- |
|
|
54
|
+
| `kt-close` | Dismissal requested. Cancelable. |
|
|
55
|
+
|
|
56
|
+
| Slot | Description |
|
|
57
|
+
| --------- | --------------------- |
|
|
58
|
+
| _default_ | Panel body |
|
|
59
|
+
| `footer` | Pinned below the body |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `<kt-skeleton>`
|
|
2
|
+
|
|
3
|
+
A loading placeholder, pulsing between `--surface-raised` and `--surface-hover`
|
|
4
|
+
— the same elevation ramp everything else in Kanto uses, so it reads as part of
|
|
5
|
+
the page rather than as a grey rectangle pasted onto it.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<kt-skeleton count="3"></kt-skeleton>
|
|
9
|
+
<kt-skeleton variant="circle" width="48px" height="48px"></kt-skeleton>
|
|
10
|
+
<kt-skeleton variant="rect" height="180px"></kt-skeleton>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
With `count` above one the last line is drawn at 60% width, which is the shape a
|
|
14
|
+
paragraph actually has. Without it, a skeleton reads as stacked bars rather than
|
|
15
|
+
as text.
|
|
16
|
+
|
|
17
|
+
## Accessibility
|
|
18
|
+
|
|
19
|
+
The element sets `aria-hidden` on itself: it is a picture of content that is not
|
|
20
|
+
there yet, and reading out a dozen empty boxes is worse than silence.
|
|
21
|
+
|
|
22
|
+
Announce the loading state on the region that owns it:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<section aria-busy="true">
|
|
26
|
+
<kt-skeleton count="4"></kt-skeleton>
|
|
27
|
+
</section>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The pulse stops under `prefers-reduced-motion`.
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
| Property | Attribute | Type | Default |
|
|
35
|
+
| ---------- | ---------- | ------------------------------ | -------- |
|
|
36
|
+
| `variant` | `variant` | `'text' \| 'rect' \| 'circle'` | `'text'` |
|
|
37
|
+
| `width` | `width` | `string` (any CSS length) | `''` |
|
|
38
|
+
| `height` | `height` | `string` | `''` |
|
|
39
|
+
| `count` | `count` | `number` | `1` |
|
|
40
|
+
| `animated` | `animated` | `boolean` | `true` |
|
|
41
|
+
|
|
42
|
+
Defaults per variant: text 100% × 14px, rect 100% × 80px, circle 40 × 40px.
|
|
43
|
+
|
|
44
|
+
| Part | Description |
|
|
45
|
+
| ------ | -------------------- |
|
|
46
|
+
| `line` | One placeholder line |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# `<kt-split-button>`
|
|
2
|
+
|
|
3
|
+
One button carrying a menu of related actions. The body runs the action a user
|
|
4
|
+
wants nine times out of ten; the caret opens the rest.
|
|
5
|
+
|
|
6
|
+
```html
|
|
7
|
+
<kt-split-button>Save</kt-split-button>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
save.items = [
|
|
12
|
+
{ id: 'close', label: 'Save and close' },
|
|
13
|
+
{ id: 'copy', label: 'Save a copy' },
|
|
14
|
+
{ id: 'pdf', label: 'Export as PDF', icon: 'file' },
|
|
15
|
+
{ id: 'discard', label: 'Discard changes', variant: 'danger' },
|
|
16
|
+
];
|
|
17
|
+
save.addEventListener('click', () => save());
|
|
18
|
+
save.addEventListener('kt-select', (e) => run(e.detail.value));
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## When not to use it
|
|
22
|
+
|
|
23
|
+
**The rows must be variations on the primary action.** "Save and close" belongs
|
|
24
|
+
under "Save"; "Delete" does not. For an unrelated set of actions there is no
|
|
25
|
+
default worth promoting — use `<kt-dropdown>`, which offers none.
|
|
26
|
+
|
|
27
|
+
**One primary button per screen still holds.** A split button is one button,
|
|
28
|
+
not two, so it counts once.
|
|
29
|
+
|
|
30
|
+
## The two segments
|
|
31
|
+
|
|
32
|
+
A press on the caret never surfaces as a `click` on the host. A single `click`
|
|
33
|
+
listener therefore means the primary action and nothing else — no checking what
|
|
34
|
+
was pressed, no accidental double handling.
|
|
35
|
+
|
|
36
|
+
`variant` and `size` are forwarded to both segments, so the pair always reads as
|
|
37
|
+
one control. The seam between them is a hairline of the page showing through,
|
|
38
|
+
the same way elevation works everywhere else in the system.
|
|
39
|
+
|
|
40
|
+
## Items
|
|
41
|
+
|
|
42
|
+
| Field | Type | |
|
|
43
|
+
| ---------- | ----------------------- | ------------------------------- |
|
|
44
|
+
| `id` | `string \| number` | Carried in `kt-select` |
|
|
45
|
+
| `label` | `string` | Falls back to `id` |
|
|
46
|
+
| `icon` | `string` | Lucide name, kebab-case |
|
|
47
|
+
| `disabled` | `boolean` | Stays visible, cannot be chosen |
|
|
48
|
+
| `variant` | `'default' \| 'danger'` | `danger` tints the row red |
|
|
49
|
+
|
|
50
|
+
## Keyboard
|
|
51
|
+
|
|
52
|
+
| Key | Does |
|
|
53
|
+
| ------------------- | ------------------------------------------- |
|
|
54
|
+
| `Enter` / `Space` | Runs the focused segment or row |
|
|
55
|
+
| `Arrow down` / `up` | Walks the rows, wrapping, skipping disabled |
|
|
56
|
+
| `Home` / `End` | First / last selectable row |
|
|
57
|
+
| `Escape` | Closes the menu |
|
|
58
|
+
|
|
59
|
+
Opening the menu moves focus to the first selectable row. Closing it hands
|
|
60
|
+
focus back to the caret — unless a click outside already took focus elsewhere,
|
|
61
|
+
in which case it is left where the user put it.
|
|
62
|
+
|
|
63
|
+
## Placement
|
|
64
|
+
|
|
65
|
+
The menu hangs off the right edge of the button and flips above it when there is
|
|
66
|
+
not enough room below. `preferred-placement="top"` reverses the preference.
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
| Property | Attribute | Type | Default |
|
|
71
|
+
| -------------------- | --------------------- | -------------------------------- | ---------------- |
|
|
72
|
+
| `items` | — | `KtSplitButtonItem[]` | `[]` |
|
|
73
|
+
| `variant` | `variant` | same ten as `<kt-button>` | `'primary'` |
|
|
74
|
+
| `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
75
|
+
| `disabled` | `disabled` | `boolean` | `false` |
|
|
76
|
+
| `icon` | `icon` | `string` | `''` |
|
|
77
|
+
| `menuLabel` | `menu-label` | `string` | `'More actions'` |
|
|
78
|
+
| `preferredPlacement` | `preferred-placement` | `'bottom' \| 'top'` | `'bottom'` |
|
|
79
|
+
| `emptyText` | `empty-text` | `string` | `'No actions'` |
|
|
80
|
+
|
|
81
|
+
| Method / getter | Description |
|
|
82
|
+
| --------------- | --------------------------- |
|
|
83
|
+
| `show()` | Opens the menu |
|
|
84
|
+
| `hide()` | Closes it |
|
|
85
|
+
| `toggle()` | Either |
|
|
86
|
+
| `isOpen` | Whether it is open |
|
|
87
|
+
| `focus()` | Focuses the primary segment |
|
|
88
|
+
|
|
89
|
+
| Event | Detail |
|
|
90
|
+
| ----------- | --------------------------- |
|
|
91
|
+
| `click` | native — the primary action |
|
|
92
|
+
| `kt-select` | `{ value, item }` |
|
|
93
|
+
| `kt-open` | — |
|
|
94
|
+
| `kt-close` | — |
|
|
95
|
+
|
|
96
|
+
| Slot | Description |
|
|
97
|
+
| --------- | -------------------------- |
|
|
98
|
+
| _default_ | The primary action's label |
|
|
99
|
+
|
|
100
|
+
| Part | Description |
|
|
101
|
+
| -------- | ---------------------------------- |
|
|
102
|
+
| `action` | The primary `<kt-button>` |
|
|
103
|
+
| `caret` | The `<kt-button>` opening the menu |
|
|
104
|
+
| `menu` | The list of rows |
|
|
105
|
+
| `item` | A menu row |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# `<kt-stat>`
|
|
2
|
+
|
|
3
|
+
One headline figure: a label, a value, and how it moved.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<kt-stat label="Revenue" value="$292,342" delta="-3%" trend="down" icon="dollar-sign"></kt-stat>
|
|
7
|
+
<kt-stat label="Customers" value="712" delta="+12%" trend="up" icon="users"></kt-stat>
|
|
8
|
+
<kt-stat label="Churn" value="2.1%" delta="-0.4%" trend="down" inverted></kt-stat>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## The colour comes from the meaning, not the sign
|
|
12
|
+
|
|
13
|
+
This is the mistake every dashboard makes. `-3%` on revenue is bad news; `-0.4%`
|
|
14
|
+
on churn is good news. The sign is identical.
|
|
15
|
+
|
|
16
|
+
So `trend` says which way the number went, and `inverted` says that down is the
|
|
17
|
+
direction you wanted. The badge takes its colour from the two together — never
|
|
18
|
+
from the minus sign.
|
|
19
|
+
|
|
20
|
+
| `trend` | `inverted` | Badge |
|
|
21
|
+
| ------- | ---------- | ------- |
|
|
22
|
+
| `up` | no | success |
|
|
23
|
+
| `down` | no | danger |
|
|
24
|
+
| `up` | yes | danger |
|
|
25
|
+
| `down` | yes | success |
|
|
26
|
+
| `flat` | either | neutral |
|
|
27
|
+
|
|
28
|
+
Leave `trend` off and the badge stays neutral, which is the right default: a
|
|
29
|
+
number with no stated direction should not be coloured as though it had one.
|
|
30
|
+
|
|
31
|
+
## Anatomy
|
|
32
|
+
|
|
33
|
+
The icon sits on a tinted disc rather than bare — at this size a lone glyph
|
|
34
|
+
reads as noise next to the figure. The label is an overline, deliberately
|
|
35
|
+
quieter than the number it introduces.
|
|
36
|
+
|
|
37
|
+
The default slot takes anything that belongs under the value — a sparkline, a
|
|
38
|
+
caption, a breakdown.
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
| Property | Attribute | Type | Default |
|
|
43
|
+
| ---------- | ---------- | -------------------------- | ---------- |
|
|
44
|
+
| `label` | `label` | `string` | `''` |
|
|
45
|
+
| `value` | `value` | `string` | `''` |
|
|
46
|
+
| `delta` | `delta` | `string` | `''` |
|
|
47
|
+
| `trend` | `trend` | `'up' \| 'down' \| 'flat'` | `'flat'` |
|
|
48
|
+
| `inverted` | `inverted` | `boolean` | `false` |
|
|
49
|
+
| `icon` | `icon` | `string` | `''` |
|
|
50
|
+
| `caption` | `caption` | `string` | `''` |
|
|
51
|
+
| `size` | `size` | `'small' \| 'medium'` | `'medium'` |
|
|
52
|
+
| `loading` | `loading` | `boolean` | `false` |
|
|
53
|
+
|
|
54
|
+
| Part | Description |
|
|
55
|
+
| ------- | ----------- |
|
|
56
|
+
| `base` | The tile |
|
|
57
|
+
| `value` | The figure |
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# `<kt-sub-menu-navigation>`
|
|
2
|
+
|
|
3
|
+
The sidebar: uppercase overline section titles over lists of links, to any
|
|
4
|
+
depth. This is the navigation both the Kanto docs shell and the console
|
|
5
|
+
application use.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
nav.sections = [
|
|
9
|
+
{
|
|
10
|
+
title: 'Workspace',
|
|
11
|
+
items: [
|
|
12
|
+
{ label: 'Home', href: '#/home', icon: 'house' },
|
|
13
|
+
{ label: 'Inbox', href: '#/inbox', icon: 'inbox', badge: '4' },
|
|
14
|
+
{
|
|
15
|
+
label: 'Settings',
|
|
16
|
+
icon: 'settings',
|
|
17
|
+
children: [
|
|
18
|
+
{ label: 'General', href: '#/settings/general' },
|
|
19
|
+
{
|
|
20
|
+
label: 'Members',
|
|
21
|
+
children: [{ label: 'Roles', href: '#/settings/members/roles' }],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
nav.activeHref = location.hash;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A section without a `title` renders as a bare list — useful for a trailing group
|
|
32
|
+
of links that needs no heading.
|
|
33
|
+
|
|
34
|
+
## Nesting
|
|
35
|
+
|
|
36
|
+
The recursion is the point. Every product grows a third level eventually —
|
|
37
|
+
Settings holds Members holds Roles — and the usual answer is to hand-roll that
|
|
38
|
+
one branch beside the component, which is how two navigation styles end up on
|
|
39
|
+
one screen. Here a branch renders exactly like the level above it: same row,
|
|
40
|
+
same states, indented by its depth, with a chevron instead of an href.
|
|
41
|
+
|
|
42
|
+
An item with `children` is a `<button aria-expanded>`, not a link, because it
|
|
43
|
+
goes nowhere. Give a branch `open: true` to start it expanded.
|
|
44
|
+
|
|
45
|
+
**A branch holding the current page opens itself**, whatever was clicked, and
|
|
46
|
+
refuses to fold while it still does — arriving on a page the sidebar does not
|
|
47
|
+
show is worse than losing a fold. It is marked `within` rather than `active`:
|
|
48
|
+
the current page is below it, not it.
|
|
49
|
+
|
|
50
|
+
## Active state
|
|
51
|
+
|
|
52
|
+
Set `activeHref` and the matching link is highlighted, marked
|
|
53
|
+
`aria-current="page"` and given an accent bar. The bar matters: it is what
|
|
54
|
+
survives a forced-colours mode, where the tint does not. An item can also force
|
|
55
|
+
the state with `active: true`, for routes that do not map to a single href.
|
|
56
|
+
|
|
57
|
+
## In a sidebar
|
|
58
|
+
|
|
59
|
+
`flush` drops the card background, padding and radius, for a full-height
|
|
60
|
+
sidebar that already owns its surface. `collapsed` reduces every row to its
|
|
61
|
+
icon; labels, badges and open branches are hidden rather than clipped, because
|
|
62
|
+
a label sliced in half at 48px reads as a bug rather than as a narrow sidebar.
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<kt-sub-menu-navigation flush collapsed></kt-sub-menu-navigation>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Client-side routing
|
|
69
|
+
|
|
70
|
+
Same contract as `<kt-breadcrumb>`: `kt-navigate` is cancelable, and modified
|
|
71
|
+
clicks are left to the browser.
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
nav.addEventListener('kt-navigate', (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
router.push(e.detail.item.href);
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
| Property | Attribute | Type | Default |
|
|
83
|
+
| ------------ | ------------- | ---------------- | ------------------------ |
|
|
84
|
+
| `sections` | — | `KtNavSection[]` | `[]` |
|
|
85
|
+
| `activeHref` | `active-href` | `string` | `''` |
|
|
86
|
+
| `label` | `label` | `string` | `'Secondary navigation'` |
|
|
87
|
+
| `flush` | `flush` | `boolean` | `false` |
|
|
88
|
+
| `collapsed` | `collapsed` | `boolean` | `false` |
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
interface KtNavSection {
|
|
92
|
+
title?: string;
|
|
93
|
+
items: KtNavItem[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface KtNavItem {
|
|
97
|
+
label: string;
|
|
98
|
+
href?: string;
|
|
99
|
+
icon?: string; // lucide name; conventionally only the top level carries one
|
|
100
|
+
badge?: string; // a count or a short status, right-aligned
|
|
101
|
+
active?: boolean;
|
|
102
|
+
disabled?: boolean;
|
|
103
|
+
children?: KtNavItem[]; // any depth
|
|
104
|
+
open?: boolean; // expanded on first render
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Event | Detail |
|
|
109
|
+
| ------------- | ----------------------- |
|
|
110
|
+
| `kt-navigate` | `{ item }` — cancelable |
|
|
111
|
+
| `kt-toggle` | `{ item, open }` |
|
|
112
|
+
|
|
113
|
+
| Part | Description |
|
|
114
|
+
| --------- | -------------------------------------- |
|
|
115
|
+
| `nav` | The `<nav>` |
|
|
116
|
+
| `section` | A section |
|
|
117
|
+
| `title` | A section title |
|
|
118
|
+
| `item` | A navigation link or branch, any depth |
|