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.
Files changed (65) hide show
  1. package/README.md +52 -4
  2. package/dist/cdn/kanto.min.js +6619 -0
  3. package/dist/chunks/{floating-B33g-M_1.js → floating-C9RLa6cZ.js} +2 -2
  4. package/dist/chunks/floating-C9RLa6cZ.js.map +1 -0
  5. package/dist/components/forms/kt-date-input/kt-date-input.js +1 -1
  6. package/dist/components/forms/kt-date-picker/kt-date-picker.js +1 -1
  7. package/dist/components/forms/kt-input/kt-input.js +1 -1
  8. package/dist/components/forms/kt-input-menu/kt-input-menu.js +1 -1
  9. package/dist/components/forms/kt-multi-select/kt-multi-select.js +1 -1
  10. package/dist/components/forms/kt-select/kt-select.js +1 -1
  11. package/dist/components/overlays/kt-dropdown/kt-dropdown.js +1 -1
  12. package/dist/docs/kt-alert.md +52 -0
  13. package/dist/docs/kt-avatar.md +46 -0
  14. package/dist/docs/kt-badge.md +104 -0
  15. package/dist/docs/kt-breadcrumb.md +53 -0
  16. package/dist/docs/kt-button.md +72 -0
  17. package/dist/docs/kt-calendar.md +98 -0
  18. package/dist/docs/kt-card.md +93 -0
  19. package/dist/docs/kt-chart.md +230 -0
  20. package/dist/docs/kt-checkbox.md +65 -0
  21. package/dist/docs/kt-code.md +88 -0
  22. package/dist/docs/kt-collapsible.md +50 -0
  23. package/dist/docs/kt-confirm-dialog.md +52 -0
  24. package/dist/docs/kt-date-input.md +97 -0
  25. package/dist/docs/kt-date-picker.md +116 -0
  26. package/dist/docs/kt-drag-drop.md +85 -0
  27. package/dist/docs/kt-dropdown.md +65 -0
  28. package/dist/docs/kt-empty-state.md +36 -0
  29. package/dist/docs/kt-form.md +93 -0
  30. package/dist/docs/kt-header.md +100 -0
  31. package/dist/docs/kt-icon.md +68 -0
  32. package/dist/docs/kt-input-menu.md +90 -0
  33. package/dist/docs/kt-input.md +137 -0
  34. package/dist/docs/kt-kbd.md +44 -0
  35. package/dist/docs/kt-label-input.md +54 -0
  36. package/dist/docs/kt-meter.md +79 -0
  37. package/dist/docs/kt-modal.md +62 -0
  38. package/dist/docs/kt-multi-select.md +115 -0
  39. package/dist/docs/kt-page-header.md +53 -0
  40. package/dist/docs/kt-pagination.md +36 -0
  41. package/dist/docs/kt-progress-bar.md +66 -0
  42. package/dist/docs/kt-radio-group.md +73 -0
  43. package/dist/docs/kt-segmented-control.md +70 -0
  44. package/dist/docs/kt-select.md +80 -0
  45. package/dist/docs/kt-side-panel.md +59 -0
  46. package/dist/docs/kt-skeleton.md +46 -0
  47. package/dist/docs/kt-split-button.md +105 -0
  48. package/dist/docs/kt-stat.md +57 -0
  49. package/dist/docs/kt-sub-menu-navigation.md +118 -0
  50. package/dist/docs/kt-table.md +179 -0
  51. package/dist/docs/kt-tabs.md +87 -0
  52. package/dist/docs/kt-textarea.md +59 -0
  53. package/dist/docs/kt-time-input.md +88 -0
  54. package/dist/docs/kt-timeline.md +63 -0
  55. package/dist/docs/kt-toast-container.md +69 -0
  56. package/dist/docs/kt-toast.md +41 -0
  57. package/dist/docs/kt-toggle-button-group.md +67 -0
  58. package/dist/docs/kt-toggle-button.md +52 -0
  59. package/dist/docs/kt-toggle.md +47 -0
  60. package/dist/docs/kt-tooltip.md +68 -0
  61. package/dist/internal/floating.d.ts +4 -0
  62. package/dist/tokens/colors.css +5 -2
  63. package/dist/web-types.json +1 -1
  64. package/package.json +23 -5
  65. package/dist/chunks/floating-B33g-M_1.js.map +0 -1
@@ -3,7 +3,7 @@ import { p as r } from "./position-Cx_ZH5zh.js";
3
3
  const p = n`
4
4
  .floating {
5
5
  position: fixed;
6
- inset: auto;
6
+ inset: 0 auto auto 0;
7
7
  display: block;
8
8
  /* As wide as what it holds, wherever it is placed: a box sized from its
9
9
  position would change size when moved, and be moved again. */
@@ -60,4 +60,4 @@ export {
60
60
  d as F,
61
61
  p as f
62
62
  };
63
- //# sourceMappingURL=floating-B33g-M_1.js.map
63
+ //# sourceMappingURL=floating-C9RLa6cZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floating-C9RLa6cZ.js","sources":["../../src/internal/floating.ts"],"sourcesContent":["import { css, type ReactiveController, type ReactiveControllerHost } from 'lit';\nimport { placeFloating, type Align, type Side } from './position.js';\n\n/**\n * A panel that opens from a trigger — a select's list, a date picker's\n * calendar, a menu — drawn in the top layer and placed from the trigger.\n *\n * A panel positioned inside its element was cut off by any ancestor with\n * `overflow: hidden`: a table cell, a side panel, an app's scrolling main\n * area. In the top layer, nothing clips it. It opens on the side asked for, or\n * the other one when that has no room, and follows the trigger as the page\n * scrolls.\n */\n\nexport interface FloatingOptions {\n /** The panel, which carries `popover=\"manual\"` and the `floating` class. */\n readonly panel: () => HTMLElement | null | undefined;\n /** What the panel opens from. */\n readonly anchor: () => Element | null | undefined;\n /** The side it prefers. */\n readonly side?: () => Side;\n /** Which edge of the anchor it lines up with; its start by default. */\n readonly align?: () => Align;\n /** Sizes the panel to the anchor's width — a list under its field. */\n readonly matchWidth?: boolean;\n /** Told the side it opened on, for the direction of its entrance. */\n readonly onPlace?: (side: Side) => void;\n}\n\n/**\n * The panel's own styles. Closed, it keeps the display and the hidden,\n * animatable state its element gives it, so it can fade out after it leaves\n * the top layer: `display` here only undoes the popover's `display: none`, and\n * a panel's own `display` still wins.\n *\n * Until it is first placed it sits top left. With auto insets a closed panel\n * sat where the flow put it — beside a right-hand trigger, past the edge of\n * the page, which then scrolled sideways.\n */\nexport const floatingStyles = css`\n .floating {\n position: fixed;\n inset: 0 auto auto 0;\n display: block;\n /* As wide as what it holds, wherever it is placed: a box sized from its\n position would change size when moved, and be moved again. */\n width: max-content;\n margin: 0;\n color: inherit;\n /* Set apart from the page by the theme: a lighter surface in the dark,\n an edge and a soft shadow in the light. */\n border: var(--border-width) solid var(--border-popover);\n box-shadow: var(--shadow-popover);\n }\n`;\n\nexport class FloatingController implements ReactiveController {\n private open = false;\n private frame = 0;\n /** Places the panel again when its size changes — content drawn after it opened. */\n private resizeObserver: ResizeObserver | undefined;\n\n constructor(\n host: ReactiveControllerHost,\n private readonly options: FloatingOptions,\n ) {\n host.addController(this);\n }\n\n /**\n * Call from the host's `updated()` with whether the panel is open. While it\n * is, every update re-places it: its content — a filtered list — can change\n * its size.\n */\n sync(open: boolean): void {\n // First, and whatever the panel: one rendered only while open is already\n // gone when it closes, and its listeners must go with it.\n if (open !== this.open) {\n this.open = open;\n const method = open ? 'addEventListener' : 'removeEventListener';\n window[method]('scroll', this.follow, { capture: true, passive: true });\n window[method]('resize', this.follow);\n }\n\n const panel = this.options.panel();\n // Where popovers are missing — a test DOM — the panel stays where it is.\n if (!panel || typeof panel.showPopover !== 'function') return;\n\n if (open) {\n if (!panel.matches(':popover-open')) panel.showPopover();\n this.place();\n // A panel measured at opening may grow once what it holds has drawn —\n // a calendar is an element of its own — and must be placed again.\n if (!this.resizeObserver && typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(this.follow);\n this.resizeObserver.observe(panel);\n }\n } else {\n this.unobserve();\n if (panel.matches(':popover-open')) panel.hidePopover();\n }\n }\n\n private unobserve(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n }\n\n hostDisconnected(): void {\n this.sync(false);\n this.unobserve();\n cancelAnimationFrame(this.frame);\n }\n\n private follow = (): void => {\n cancelAnimationFrame(this.frame);\n this.frame = requestAnimationFrame(() => this.place());\n };\n\n private place(): void {\n const panel = this.options.panel();\n const anchor = this.options.anchor()?.getBoundingClientRect();\n if (!panel || !anchor) return;\n\n if (this.options.matchWidth) panel.style.width = `${anchor.width}px`;\n // Untransformed size: the entrance animation scales and slides the panel.\n const { x, y, placement } = placeFloating(\n anchor,\n { width: panel.offsetWidth, height: panel.offsetHeight },\n { width: window.innerWidth, height: window.innerHeight },\n this.options.side?.() ?? 'bottom',\n this.options.align?.() ?? 'start',\n );\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\n this.options.onPlace?.(placement);\n }\n}\n"],"names":["floatingStyles","css","FloatingController","host","options","open","method","panel","anchor","x","y","placement","placeFloating"],"mappings":";;AAuCO,MAAMA,IAAiBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBvB,MAAMC,EAAiD;AAAA,EAM5D,YACEC,GACiBC,GACjB;AADiB,SAAA,UAAAA,GAPnB,KAAQ,OAAO,IACf,KAAQ,QAAQ,GAwDhB,KAAQ,SAAS,MAAY;AAC3B,2BAAqB,KAAK,KAAK,GAC/B,KAAK,QAAQ,sBAAsB,MAAM,KAAK,OAAO;AAAA,IACvD,GAnDED,EAAK,cAAc,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAKE,GAAqB;AAGxB,QAAIA,MAAS,KAAK,MAAM;AACtB,WAAK,OAAOA;AACZ,YAAMC,IAASD,IAAO,qBAAqB;AAC3C,aAAOC,CAAM,EAAE,UAAU,KAAK,QAAQ,EAAE,SAAS,IAAM,SAAS,IAAM,GACtE,OAAOA,CAAM,EAAE,UAAU,KAAK,MAAM;AAAA,IACtC;AAEA,UAAMC,IAAQ,KAAK,QAAQ,MAAA;AAE3B,IAAI,CAACA,KAAS,OAAOA,EAAM,eAAgB,eAEvCF,KACGE,EAAM,QAAQ,eAAe,OAAS,YAAA,GAC3C,KAAK,MAAA,GAGD,CAAC,KAAK,kBAAkB,OAAO,iBAAmB,QACpD,KAAK,iBAAiB,IAAI,eAAe,KAAK,MAAM,GACpD,KAAK,eAAe,QAAQA,CAAK,OAGnC,KAAK,UAAA,GACDA,EAAM,QAAQ,eAAe,OAAS,YAAA;AAAA,EAE9C;AAAA,EAEQ,YAAkB;AACxB,SAAK,gBAAgB,WAAA,GACrB,KAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,mBAAyB;AACvB,SAAK,KAAK,EAAK,GACf,KAAK,UAAA,GACL,qBAAqB,KAAK,KAAK;AAAA,EACjC;AAAA,EAOQ,QAAc;AACpB,UAAMA,IAAQ,KAAK,QAAQ,MAAA,GACrBC,IAAS,KAAK,QAAQ,OAAA,GAAU,sBAAA;AACtC,QAAI,CAACD,KAAS,CAACC,EAAQ;AAEvB,IAAI,KAAK,QAAQ,eAAYD,EAAM,MAAM,QAAQ,GAAGC,EAAO,KAAK;AAEhE,UAAM,EAAE,GAAAC,GAAG,GAAAC,GAAG,WAAAC,EAAA,IAAcC;AAAA,MAC1BJ;AAAA,MACA,EAAE,OAAOD,EAAM,aAAa,QAAQA,EAAM,aAAA;AAAA,MAC1C,EAAE,OAAO,OAAO,YAAY,QAAQ,OAAO,YAAA;AAAA,MAC3C,KAAK,QAAQ,OAAA,KAAY;AAAA,MACzB,KAAK,QAAQ,aAAa;AAAA,IAAA;AAE5B,IAAAA,EAAM,MAAM,OAAO,GAAGE,CAAC,MACvBF,EAAM,MAAM,MAAM,GAAGG,CAAC,MACtB,KAAK,QAAQ,UAAUC,CAAS;AAAA,EAClC;AACF;"}
@@ -3,7 +3,7 @@ import { property as w, state as x, query as $ } from "lit/decorators.js";
3
3
  import { classMap as C } from "lit/directives/class-map.js";
4
4
  import { d as P } from "../../../chunks/kt-element-T0VbIBNp.js";
5
5
  import { u as S, t as I } from "../../../chunks/events-r51msXNf.js";
6
- import { F as q, f as E } from "../../../chunks/floating-B33g-M_1.js";
6
+ import { F as q, f as E } from "../../../chunks/floating-C9RLa6cZ.js";
7
7
  import { K as f } from "../../../chunks/segmented-field-BBXR4XFL.js";
8
8
  import { d as y, p as u, t as v, c as b, f as Y } from "../../../chunks/kt-calendar-CtwOby1R.js";
9
9
  import { d as K, e as L, t as M } from "../../../chunks/typed-date-CPAUNHzr.js";
@@ -5,7 +5,7 @@ import { live as O } from "lit/directives/live.js";
5
5
  import { K as D, d as R } from "../../../chunks/kt-element-T0VbIBNp.js";
6
6
  import { u as q, t as E, e as v } from "../../../chunks/events-r51msXNf.js";
7
7
  import { a as B, s as F, b as L } from "../../../chunks/form-control-B74_EHtG.js";
8
- import { F as z, f as K } from "../../../chunks/floating-B33g-M_1.js";
8
+ import { F as z, f as K } from "../../../chunks/floating-C9RLa6cZ.js";
9
9
  import { r as M, d as _ } from "../../../chunks/locale-C5xLkGx5.js";
10
10
  import { s as f } from "../../../chunks/index-TWJzttLX.js";
11
11
  import { a as P, p as u, c as g, b as w, t as m, e as k, f as C, g as V, h as I, d as W } from "../../../chunks/kt-calendar-CtwOby1R.js";
@@ -5,7 +5,7 @@ import { live as w } from "lit/directives/live.js";
5
5
  import { K as g, d as $ } from "../../../chunks/kt-element-T0VbIBNp.js";
6
6
  import { t as C, e as c } from "../../../chunks/events-r51msXNf.js";
7
7
  import { a as S, s as b, b as E } from "../../../chunks/form-control-B74_EHtG.js";
8
- import { F as O, f as z } from "../../../chunks/floating-B33g-M_1.js";
8
+ import { F as O, f as z } from "../../../chunks/floating-C9RLa6cZ.js";
9
9
  import { s as u } from "../../../chunks/index-TWJzttLX.js";
10
10
  import "../../core/kt-icon/kt-icon.js";
11
11
  import "../../feedback/kt-tooltip/kt-tooltip.js";
@@ -6,7 +6,7 @@ import { K as m, d as $ } from "../../../chunks/kt-element-T0VbIBNp.js";
6
6
  import { u as I, t as y, e as f } from "../../../chunks/events-r51msXNf.js";
7
7
  import { a as D, s as q, b as O } from "../../../chunks/form-control-B74_EHtG.js";
8
8
  import { a as S, b as C, o as g, f as v, l as P, n as E } from "../../../chunks/listbox-LsUEyGhQ.js";
9
- import { F as L, f as A } from "../../../chunks/floating-B33g-M_1.js";
9
+ import { F as L, f as A } from "../../../chunks/floating-C9RLa6cZ.js";
10
10
  import { s as l } from "../../../chunks/index-TWJzttLX.js";
11
11
  import "../../core/kt-icon/kt-icon.js";
12
12
  var V = Object.defineProperty, i = (u, t, o, r) => {
@@ -6,7 +6,7 @@ import { K as $, d as P } from "../../../chunks/kt-element-T0VbIBNp.js";
6
6
  import { u as C, t as I, e as D } from "../../../chunks/events-r51msXNf.js";
7
7
  import { a as L, s as z, b as B } from "../../../chunks/form-control-B74_EHtG.js";
8
8
  import { a as T, b as F, f as y, l as V, n as _, o as w } from "../../../chunks/listbox-LsUEyGhQ.js";
9
- import { F as A, f as W } from "../../../chunks/floating-B33g-M_1.js";
9
+ import { F as A, f as W } from "../../../chunks/floating-C9RLa6cZ.js";
10
10
  import { s as p } from "../../../chunks/index-TWJzttLX.js";
11
11
  import "../../core/kt-icon/kt-icon.js";
12
12
  var K = Object.defineProperty, r = (m, t, i, e) => {
@@ -5,7 +5,7 @@ import { K as v, d as w } from "../../../chunks/kt-element-T0VbIBNp.js";
5
5
  import { u as k, t as g, e as m } from "../../../chunks/events-r51msXNf.js";
6
6
  import { a as $, s as I, b as D } from "../../../chunks/form-control-B74_EHtG.js";
7
7
  import { a as S, f as b, l as L, n as C, o as y } from "../../../chunks/listbox-LsUEyGhQ.js";
8
- import { F as E, f as P } from "../../../chunks/floating-B33g-M_1.js";
8
+ import { F as E, f as P } from "../../../chunks/floating-C9RLa6cZ.js";
9
9
  import { s as l } from "../../../chunks/index-TWJzttLX.js";
10
10
  import "../../core/kt-icon/kt-icon.js";
11
11
  var q = Object.defineProperty, i = (c, e, o, a) => {
@@ -4,7 +4,7 @@ import { classMap as u } from "lit/directives/class-map.js";
4
4
  import { K as c, d as b } from "../../../chunks/kt-element-T0VbIBNp.js";
5
5
  import { t as m, e as d } from "../../../chunks/events-r51msXNf.js";
6
6
  import { o as x } from "../../../chunks/listbox-LsUEyGhQ.js";
7
- import { F as w, f as k } from "../../../chunks/floating-B33g-M_1.js";
7
+ import { F as w, f as k } from "../../../chunks/floating-C9RLa6cZ.js";
8
8
  import { s as $ } from "../../../chunks/index-TWJzttLX.js";
9
9
  var P = Object.defineProperty, r = (i, t, s, a) => {
10
10
  for (var o = void 0, l = i.length - 1, g; l >= 0; l--)
@@ -0,0 +1,52 @@
1
+ # `<kt-alert>`
2
+
3
+ A message that stays on the page.
4
+
5
+ ```html
6
+ <kt-alert
7
+ variant="warning"
8
+ heading="Storage almost full"
9
+ description="You are using 88% of your quota."
10
+ dismissible
11
+ >
12
+ <kt-button slot="actions" size="small" variant="warning">Upgrade</kt-button>
13
+ </kt-alert>
14
+ ```
15
+
16
+ ## Against a toast
17
+
18
+ A **toast** is transient and interrupts; it belongs to the moment. An **alert**
19
+ sits in the layout and waits; it belongs to the page.
20
+
21
+ A form that failed to save, a trial about to expire, a consent notice — all
22
+ alerts. "Saved", "Copied", "3 rows deleted" — all toasts. Getting this backwards
23
+ is how important messages disappear after four seconds.
24
+
25
+ ## Colour and urgency
26
+
27
+ The whole block takes the variant's tint and border, so the message's
28
+ temperature is legible before a word is read. `danger` is `aria-live="assertive"`
29
+ and interrupts the screen reader; everything else is polite and waits its turn.
30
+
31
+ `dismissible` adds the button and fires `kt-close` — **removing the alert is
32
+ yours**, because only the caller knows whether it should come back.
33
+
34
+ ## API
35
+
36
+ | Property | Attribute | Type | Default |
37
+ | ------------- | ------------- | ----------------------------------------------------------- | -------- |
38
+ | `variant` | `variant` | `'info' \| 'success' \| 'warning' \| 'danger' \| 'neutral'` | `'info'` |
39
+ | `heading` | `heading` | `string` | `''` |
40
+ | `description` | `description` | `string` | `''` |
41
+ | `dismissible` | `dismissible` | `boolean` | `false` |
42
+ | `noIcon` | `no-icon` | `boolean` | `false` |
43
+ | `icon` | `icon` | `string` | `''` |
44
+
45
+ | Slot | Description |
46
+ | --------- | ------------------------------------ |
47
+ | _default_ | Body, when `description` is not used |
48
+ | `actions` | Buttons, at the end |
49
+
50
+ | Event | Description |
51
+ | ---------- | ------------------------------ |
52
+ | `kt-close` | The dismiss button was pressed |
@@ -0,0 +1,46 @@
1
+ # `<kt-avatar>`
2
+
3
+ A person or an organisation, as a picture or as their initials.
4
+
5
+ ```html
6
+ <kt-avatar name="Benjamin Canac"></kt-avatar>
7
+ <kt-avatar name="Emma Davis" src="/emma.jpg" status="online"></kt-avatar>
8
+ <kt-avatar name="Kanto Studio" square size="large"></kt-avatar>
9
+ ```
10
+
11
+ ## The fallback is the main path
12
+
13
+ Most rows in most products have no photo, so the initials are what actually
14
+ renders. They are derived from the name — first and last word, or the first two
15
+ letters of a single word — and an email address is read as one, so
16
+ `emma.davis@example.com` becomes `ED`.
17
+
18
+ The colour is picked from the name, which means **the same person is the same
19
+ colour on every screen**. That is not decoration: it lets the eye find a row
20
+ before it has read it. Override it with `color` when the entity has a brand of
21
+ its own.
22
+
23
+ A photo that fails to load falls back to the initials rather than to a broken
24
+ image. Changing `src` clears that failure, so a retry is a property assignment.
25
+
26
+ ## API
27
+
28
+ | Property | Attribute | Type | Default |
29
+ | -------- | --------- | ------------------------------------------- | ---------- |
30
+ | `name` | `name` | `string` | `''` |
31
+ | `src` | `src` | `string` | `''` |
32
+ | `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
33
+ | `square` | `square` | `boolean` | `false` |
34
+ | `status` | `status` | `'online' \| 'away' \| 'busy' \| 'offline'` | — |
35
+ | `color` | `color` | `string` | `''` |
36
+
37
+ Sizes are 24 / 32 / 48px, and everything inside scales from
38
+ `--kt-avatar-size`, so overriding that one property resizes the whole thing.
39
+
40
+ | Part | Description |
41
+ | ------- | ----------- |
42
+ | `base` | The circle |
43
+ | `image` | The `<img>` |
44
+
45
+ `initialsOf(name)` is exported too, for the cases where you need the same
46
+ letters somewhere else.
@@ -0,0 +1,104 @@
1
+ # `<kt-badge>`
2
+
3
+ A compact label: a pill `tag`, a monospaced `code` token, a `category` tinted
4
+ with a colour of your choosing, or a `count`.
5
+
6
+ ```html
7
+ <kt-badge>Design systems</kt-badge>
8
+ <kt-badge tone="success">Active</kt-badge>
9
+ <kt-badge variant="code">--surface-card</kt-badge>
10
+ <kt-badge variant="category" color="#3987e5">Infrastructure</kt-badge>
11
+ <kt-badge variant="count" tone="danger" max="99">128</kt-badge>
12
+ <kt-badge icon="user" removable>Assigned to me</kt-badge>
13
+ ```
14
+
15
+ ## Shape and colour are separate
16
+
17
+ `variant` is the **shape** — what kind of thing this is. `tone` is the
18
+ **colour** — what it means. They are separate properties because they answer
19
+ different questions: a tag can be neutral or dangerous, and so can a count.
20
+ Folding them into one list would give you nine variants and still no way to say
21
+ "a tag, but this one is a warning".
22
+
23
+ | `variant` | What it is |
24
+ | ---------- | ---------------------------------------------------- |
25
+ | `tag` | The default. A pill: a label, a filter, a topic. |
26
+ | `code` | A monospaced token — a property name, a flag, a key. |
27
+ | `category` | Tinted with `color`, for a palette the data owns. |
28
+ | `count` | A number pinned to something else. |
29
+
30
+ `count` is the one shape with no border. A number on a nav item is a mark on
31
+ the thing it counts, not a label with an outline of its own — and at that size
32
+ a border is most of what you see.
33
+
34
+ `tone` uses the system's tinted pair throughout: the colour behind at low
35
+ opacity, the colour itself as the ink. The same pair a secondary button, a
36
+ danger button and an avatar wear, so a badge beside any of them reads as one
37
+ family.
38
+
39
+ ## Categories
40
+
41
+ `category` takes any CSS colour and fills at 20% of it:
42
+
43
+ ```html
44
+ <kt-badge variant="category" color="var(--color-success-base)">Delivered</kt-badge>
45
+ <kt-badge variant="category" color="rgb(57, 135, 229)">Infrastructure</kt-badge>
46
+ ```
47
+
48
+ The mixing is `color-mix`, which is honest for every notation. The build this
49
+ replaced concatenated a `"33"` onto the string and produced garbage for
50
+ `rgb()` and for named colours.
51
+
52
+ Reach for `tone` when the meaning is one of the system's six states, and
53
+ `category` when the palette belongs to the data — a project colour, a label the
54
+ user picked.
55
+
56
+ ## Counting
57
+
58
+ `max` caps a numeric label: `max="99"` on `128` shows `99+`. A non-numeric
59
+ label is left alone, so `max` is safe to set from a template that does not know
60
+ what it will be given.
61
+
62
+ ## Interaction
63
+
64
+ Neither `clickable` nor `removable` is on by default, because most badges are
65
+ labels and a label that looks pressable is a lie.
66
+
67
+ `clickable` makes it a real control — `role="button"`, a tab stop, Enter and
68
+ Space — and fires `kt-badge-click`. `removable` adds a remove button that fires
69
+ a cancelable `kt-remove`; cancel it and remove the badge yourself once the
70
+ server agrees. A badge that is both does not report the click when the remove
71
+ button is what was pressed.
72
+
73
+ ## API
74
+
75
+ | Property | Attribute | Type | Default |
76
+ | ----------- | ----------- | ------------------------------------------------------------------------ | ----------- |
77
+ | `variant` | `variant` | `'tag' \| 'code' \| 'category' \| 'count'` | `'tag'` |
78
+ | `tone` | `tone` | `'neutral' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'` | `'neutral'` |
79
+ | `size` | `size` | `'small' \| 'medium'` | `'medium'` |
80
+ | `label` | `label` | `string` | `''` |
81
+ | `color` | `color` | `string` — any CSS colour, for `category` | `''` |
82
+ | `icon` | `icon` | `string` — lucide name, before the text | `''` |
83
+ | `clickable` | `clickable` | `boolean` | `false` |
84
+ | `removable` | `removable` | `boolean` | `false` |
85
+ | `max` | `max` | `number` | — |
86
+ | `error` | `error` | `boolean` — shorthand for `tone="danger"` | `false` |
87
+
88
+ | Event | Detail |
89
+ | ---------------- | ------------ |
90
+ | `kt-badge-click` | — cancelable |
91
+ | `kt-remove` | — cancelable |
92
+
93
+ | Slot | Description |
94
+ | --------- | -------------------------------- |
95
+ | _default_ | The text. Falls back to `label`. |
96
+
97
+ | CSS property | Description |
98
+ | ------------------ | ------------------------------------ |
99
+ | `--kt-badge-color` | Category hue; the fill is it at 20%. |
100
+
101
+ | Part | Description |
102
+ | -------- | ----------------------------------- |
103
+ | `base` | The container |
104
+ | `remove` | The remove button, when `removable` |
@@ -0,0 +1,53 @@
1
+ # `<kt-breadcrumb>`
2
+
3
+ A trail showing where the current page sits.
4
+
5
+ ```js
6
+ trail.items = [
7
+ { label: 'Home', href: '/' },
8
+ { label: 'Entities', href: '/entites' },
9
+ { label: 'Entity 4812' },
10
+ ];
11
+ ```
12
+
13
+ The last entry is the current page: rendered as text with `aria-current="page"`,
14
+ not as a link. A breadcrumb whose final item links to the page you are already
15
+ on is a dead control.
16
+
17
+ ## Client-side routing
18
+
19
+ `kt-navigate` is cancelable. Cancel it and the browser will not follow the
20
+ href; leave it alone and the link behaves like a link.
21
+
22
+ ```js
23
+ trail.addEventListener('kt-navigate', (e) => {
24
+ e.preventDefault();
25
+ router.push(e.detail.item.href);
26
+ });
27
+ ```
28
+
29
+ Modified clicks — ⌘, Ctrl, Shift, middle button — are never intercepted. The
30
+ user is asking the browser for a new tab, and the element stays out of it.
31
+
32
+ ## API
33
+
34
+ | Property | Attribute | Type | Default |
35
+ | -------- | --------- | -------------------- | -------------- |
36
+ | `items` | — | `KtBreadcrumbItem[]` | `[]` |
37
+ | `label` | `label` | `string` | `"Breadcrumb"` |
38
+
39
+ ```ts
40
+ interface KtBreadcrumbItem {
41
+ label: string;
42
+ href?: string;
43
+ }
44
+ ```
45
+
46
+ | Event | Detail |
47
+ | ------------- | ------------------------------ |
48
+ | `kt-navigate` | `{ item, index }` — cancelable |
49
+
50
+ | Part | Description |
51
+ | ------ | ------------- |
52
+ | `nav` | The `<nav>` |
53
+ | `item` | A trail entry |
@@ -0,0 +1,72 @@
1
+ # `<kt-button>`
2
+
3
+ The Kanto action button. Ten variants, three sizes, an optional Lucide icon.
4
+
5
+ ```html
6
+ <kt-button icon="plus">New entity</kt-button>
7
+ <kt-button variant="secondary" icon="refresh-cw">Refresh</kt-button>
8
+ <kt-button variant="danger">Delete</kt-button>
9
+ <kt-button variant="text">View full history</kt-button>
10
+ ```
11
+
12
+ ## Choosing a variant
13
+
14
+ | Variant | Use for |
15
+ | -------------------------- | --------------------------------------------------------------- |
16
+ | `primary` | The one action a screen is about. At most one per view. |
17
+ | `secondary` | Supporting actions. Soft indigo tint. |
18
+ | `secondary-no-bg` | The same, with no resting background — for toolbars. |
19
+ | `dark` | Neutral actions that must not compete for attention. |
20
+ | `danger` | Destructive, but recoverable. Tinted red. |
21
+ | `delete` | Irreversible. The only solid red in the system — use sparingly. |
22
+ | `warning` `info` `success` | Status-carrying actions inside a matching context. |
23
+ | `text` | Inline, link-like. No padding, no height. |
24
+
25
+ ## Icon-only
26
+
27
+ An `icon` with no slotted text collapses the button to a square. It then has no
28
+ accessible name of its own, so `label` is required:
29
+
30
+ ```html
31
+ <kt-button icon="x" label="Close" variant="secondary-no-bg"></kt-button>
32
+ ```
33
+
34
+ ## Forms
35
+
36
+ A button inside a shadow root is invisible to the enclosing form, so
37
+ `type="submit"` would normally do nothing. `<kt-button>` bridges that: it runs
38
+ the form's constraint validation, fires a real `submit` event, and carries
39
+ `name` / `value` into the submission.
40
+
41
+ ```html
42
+ <form>
43
+ <kt-input name="email" type="email" required></kt-input>
44
+ <kt-button type="submit">Send</kt-button>
45
+ </form>
46
+ ```
47
+
48
+ ## API
49
+
50
+ | Property | Attribute | Type | Default |
51
+ | -------------- | --------------- | --------------------------------- | ----------- |
52
+ | `variant` | `variant` | see table above | `'primary'` |
53
+ | `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
54
+ | `disabled` | `disabled` | `boolean` | `false` |
55
+ | `type` | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` |
56
+ | `icon` | `icon` | `string` | `''` |
57
+ | `iconPosition` | `icon-position` | `'left' \| 'right'` | `'left'` |
58
+ | `label` | `label` | `string` | `''` |
59
+ | `fullWidth` | `full-width` | `boolean` | `false` |
60
+ | `name` | `name` | `string` | `''` |
61
+ | `value` | `value` | `string` | `''` |
62
+
63
+ | Slot | Description |
64
+ | --------- | ---------------- |
65
+ | _default_ | The button label |
66
+
67
+ | Part | Description |
68
+ | -------- | --------------------- |
69
+ | `button` | The native `<button>` |
70
+ | `icon` | The icon, when set |
71
+
72
+ Emits no custom events — listen for `click` as on any button.
@@ -0,0 +1,98 @@
1
+ # `<kt-calendar>`
2
+
3
+ A month calendar to pick a day or a period from, shown in the page.
4
+
5
+ ```html
6
+ <kt-calendar value="2026-09-25"></kt-calendar>
7
+ <kt-calendar range value="2026-09-07/2026-09-18" min="2026-01-01"></kt-calendar>
8
+ ```
9
+
10
+ It is the calendar `<kt-date-picker>` opens in its popup. Use it on its own
11
+ when the calendar _is_ the interface — a booking page, a dashboard's date
12
+ filter, a planning view — and `<kt-date-picker>` when it is only a way to fill
13
+ a field.
14
+
15
+ ## A month and a year in two clicks
16
+
17
+ The title — "September 2026" — opens one panel: a scrolling list of years
18
+ beside the twelve months of the one chosen. Pick the year, then the month, and
19
+ the days are back, the day of the month kept: from 25 September, choosing March
20
+ 1987 lands on 25 March 1987. The list opens on the chosen year, centred, and
21
+ reaches from `min` to `max` — or, without them, 120 years back and 50 ahead,
22
+ birthdays to plans. Typing four digits in the list jumps to that year. Months
23
+ wholly outside `min`/`max` are disabled.
24
+
25
+ The arrows beside the title page a month at a time; they are the only thing
26
+ that does.
27
+
28
+ ## Two months
29
+
30
+ `months="2"` shows two months side by side — what `<kt-date-picker range>`
31
+ does, so a period across a month's end is two clicks. Each grid holds its own
32
+ days only, so none shows twice, and the keyboard moves across both, paging
33
+ only once it leaves the second.
34
+
35
+ ## Keyboard
36
+
37
+ In the days, the WAI-ARIA date grid pattern:
38
+
39
+ | Key | Does |
40
+ | ---------------------------- | ------------------------------- |
41
+ | `←` / `→` | Previous / next day |
42
+ | `↑` / `↓` | Same day, previous / next week |
43
+ | `Home` / `End` | First / last day of the week |
44
+ | `Page Up` / `Page Down` | Same day, previous / next month |
45
+ | `Shift` + `Page Up` / `Down` | Same day, previous / next year |
46
+ | `Enter` / `Space` | Choose the day in focus |
47
+
48
+ In the year list:
49
+
50
+ | Key | Does |
51
+ | ----------------------- | -------------------------------------------------------------------- |
52
+ | `↑` / `↓` | Previous / next year |
53
+ | `Page Up` / `Page Down` | Ten years back / ahead |
54
+ | `Home` / `End` | First / last year offered |
55
+ | Four digits | That year |
56
+ | `Enter`, `Tab` | On to the months |
57
+ | `Escape` | Back to the days — and no further, so an enclosing dialog stays open |
58
+
59
+ In the months: the arrows, three to a row; `Page Up`/`Page Down` by year;
60
+ `Enter` to choose; `Escape` back to the days.
61
+
62
+ Focus follows every switch, so the keys keep working without a click in
63
+ between.
64
+
65
+ ## ISO values and locales
66
+
67
+ As for `<kt-date-picker>`: the value is ISO 8601 text — `2026-09-25`, or
68
+ `2026-09-07/2026-09-18` with `range` — and names, the first day of the week
69
+ and every accessible label follow `locale`, else the page's `lang`, else the
70
+ browser's language. A period takes two picks in either order and fires
71
+ `kt-change` once, with the whole period.
72
+
73
+ `<kt-calendar>` is not a form control; inside a form, use `<kt-date-picker>`.
74
+
75
+ ## API
76
+
77
+ | Property | Attribute | Type | Default |
78
+ | -------- | --------- | ---------------- | ------------------ |
79
+ | `value` | `value` | `string \| null` | `null` |
80
+ | `range` | `range` | `boolean` | `false` |
81
+ | `min` | `min` | `string` | `''` |
82
+ | `max` | `max` | `string` | `''` |
83
+ | `months` | `months` | `1 \| 2` | `1` |
84
+ | `locale` | `locale` | `string` | page, then browser |
85
+ | `label` | `label` | `string` | from `setStrings` |
86
+
87
+ | Method | Description |
88
+ | --------- | ------------------------------------------ |
89
+ | `focus()` | Focuses the current day — chosen, or today |
90
+
91
+ | Event | Detail |
92
+ | ----------- | ------------------------------------------ |
93
+ | `kt-change` | `{ value: string }` — ISO date or interval |
94
+
95
+ | Part | Description |
96
+ | ------ | ------------ |
97
+ | `base` | The calendar |
98
+ | `day` | A day cell |
@@ -0,0 +1,93 @@
1
+ # `<kt-card>`
2
+
3
+ The system's content surface: 12px radius, a 1px `--border-subtle` border, 24px
4
+ of padding, 20px between its rows. Transparent at rest — a card reads as a
5
+ grouping, not as an elevation.
6
+
7
+ ```html
8
+ <kt-card>
9
+ <h6 slot="header">Recent transactions</h6>
10
+ <kt-table></kt-table>
11
+ <kt-button slot="footer" variant="text">View full history</kt-button>
12
+ </kt-card>
13
+ ```
14
+
15
+ Empty `header` and `footer` slots collapse, so an omitted footer costs no gap.
16
+
17
+ ## Clickable cards
18
+
19
+ ```html
20
+ <kt-card clickable>Entity 4812</kt-card>
21
+ ```
22
+
23
+ `clickable` gives the card `role="button"`, a tab stop, hover and active
24
+ states, and Enter/Space activation, then fires `kt-card-click`. The
25
+ div-with-a-click-handler it replaces did none of that.
26
+
27
+ Do not nest interactive elements inside a clickable card — a button inside a
28
+ button is invalid, and the two hit targets fight. Either the card is the
29
+ control, or the things inside it are.
30
+
31
+ ## Selected cards
32
+
33
+ `selected` marks a card as chosen — one filter of several, a picked plan:
34
+
35
+ ```html
36
+ <kt-card clickable selected>Pending</kt-card>
37
+ ```
38
+
39
+ It tints the card, outlines it in the primary colour and sets `aria-pressed`,
40
+ so the state is announced and not merely drawn.
41
+
42
+ Pair it with `clickable`. A card the user cannot toggle has no business looking
43
+ toggled, so a `selected` card that is not `clickable` gets the styling but no
44
+ `aria-pressed` — there is nothing to press, and claiming otherwise would be a
45
+ lie to a screen reader.
46
+
47
+ The outline lives on the card's own box rather than on the host element. An
48
+ outline only follows a `border-radius` declared on the same element, and the
49
+ host has none — put one on the host and you get sharp corners around a rounded
50
+ card.
51
+
52
+ ## Media
53
+
54
+ `image` is the convenience path; the `media` slot takes anything else.
55
+
56
+ ```html
57
+ <kt-card image="/preview.png" image-alt="Preview" image-position="top" image-size="160px"></kt-card>
58
+
59
+ <kt-card image-position="right" image-size="200px">
60
+ <canvas slot="media"></canvas>
61
+ Monthly breakdown
62
+ </kt-card>
63
+ ```
64
+
65
+ `image-size` sets the width for `left`/`right` and the height for `top`/`bottom`.
66
+
67
+ ## API
68
+
69
+ | Property | Attribute | Type | Default |
70
+ | --------------- | ---------------- | ---------------------------------------- | --------- |
71
+ | `clickable` | `clickable` | `boolean` | `false` |
72
+ | `selected` | `selected` | `boolean` | `false` |
73
+ | `image` | `image` | `string` | `''` |
74
+ | `imageAlt` | `image-alt` | `string` | `''` |
75
+ | `imagePosition` | `image-position` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'left'` |
76
+ | `imageSize` | `image-size` | `string` | `'120px'` |
77
+
78
+ | Slot | Description |
79
+ | --------- | ------------------------------ |
80
+ | _default_ | Card body |
81
+ | `header` | Above the body |
82
+ | `footer` | Below the body |
83
+ | `media` | Replaces the `image` attribute |
84
+
85
+ | Event | Detail | Fired when |
86
+ | --------------- | ------ | -------------------------------------------- |
87
+ | `kt-card-click` | — | A `clickable` card is activated. Cancelable. |
88
+
89
+ | Part | Description |
90
+ | ------- | ---------------------------- |
91
+ | `base` | The card container |
92
+ | `media` | The image or media well |
93
+ | `body` | Header/content/footer column |