@kouji-ui/components 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/fesm2022/kouji-ui-components.mjs +137 -65
  2. package/fesm2022/kouji-ui-components.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/src/accordion/accordion.css +68 -0
  5. package/src/action-sheet/action-sheet.css +79 -0
  6. package/src/alert/alert.css +133 -0
  7. package/src/avatar/avatar.css +93 -0
  8. package/src/badge/badge.css +59 -0
  9. package/src/breadcrumb/breadcrumb.css +145 -0
  10. package/src/button/button.css +295 -0
  11. package/src/button-group/button-group.css +114 -0
  12. package/src/calendar/calendar.css +129 -0
  13. package/src/card/card.css +120 -0
  14. package/src/carousel/carousel.css +181 -0
  15. package/src/cascade-select/cascade-select.css +120 -0
  16. package/src/chart/chart.css +26 -0
  17. package/src/chat/chat-ai.css +352 -0
  18. package/src/chat/chat.css +231 -0
  19. package/src/checkbox/checkbox.css +74 -0
  20. package/src/color-picker/color-picker.css +183 -0
  21. package/src/combobox/combobox.css +101 -0
  22. package/src/command-palette/command-palette.css +234 -0
  23. package/src/confirm-popup/confirm-popup.css +47 -0
  24. package/src/date-picker/date-picker.css +61 -0
  25. package/src/date-range-presets/date-range-presets.css +49 -0
  26. package/src/datetime-picker/datetime-picker.css +84 -0
  27. package/src/dialog/dialog.css +56 -0
  28. package/src/direction-toggle/direction-toggle.css +55 -0
  29. package/src/divider/divider.css +93 -0
  30. package/src/drawer/drawer.css +130 -0
  31. package/src/dropdown-menu/dropdown-menu.css +108 -0
  32. package/src/editor/editor.css +140 -0
  33. package/src/empty-state/empty-state.css +114 -0
  34. package/src/field/field.css +148 -0
  35. package/src/file-upload/file-upload.css +207 -0
  36. package/src/form/form.css +63 -0
  37. package/src/input/input.css +70 -0
  38. package/src/input-group/input-group.css +125 -0
  39. package/src/input-mask/input-mask.css +39 -0
  40. package/src/input-otp/input-otp.css +87 -0
  41. package/src/kbd/kbd.css +67 -0
  42. package/src/link/link.css +104 -0
  43. package/src/list/list.css +166 -0
  44. package/src/menubar/menubar.css +62 -0
  45. package/src/number-input/number-input.css +115 -0
  46. package/src/overflow/overflow.css +43 -0
  47. package/src/overlay/overlay.css +45 -0
  48. package/src/overlay-badge/overlay-badge.css +118 -0
  49. package/src/pagination/pagination.css +146 -0
  50. package/src/password-input/password-input.css +155 -0
  51. package/src/popover/popover.css +71 -0
  52. package/src/progress-bar/progress-bar.css +113 -0
  53. package/src/radio/radio.css +58 -0
  54. package/src/rich-text/rich-text-editor.css +351 -0
  55. package/src/select/select.css +91 -0
  56. package/src/sheet/sheet.css +121 -0
  57. package/src/skeleton/skeleton.css +111 -0
  58. package/src/skip-link/skip-link.css +58 -0
  59. package/src/slider/slider.css +172 -0
  60. package/src/speed-dial/speed-dial.css +137 -0
  61. package/src/spinner/spinner.css +161 -0
  62. package/src/stepper/stepper.css +147 -0
  63. package/src/table/table-filters/filters.css +53 -0
  64. package/src/table/table.css +509 -0
  65. package/src/tabs/tabs.css +162 -0
  66. package/src/tag/tag.css +183 -0
  67. package/src/textarea/textarea.css +117 -0
  68. package/src/time-picker/time-picker.css +126 -0
  69. package/src/toast/toast.css +115 -0
  70. package/src/toggle/toggle.css +123 -0
  71. package/src/tooltip/tooltip.css +61 -0
  72. package/src/tree-select/tree-select.css +220 -0
  73. package/types/kouji-ui-components.d.ts +17 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kouji-ui/components",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Opinionated styled Angular components built on @kouji-ui/core directives. Themed at runtime via @kouji-ui/themes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,68 @@
1
+ @layer kj.component {
2
+ .kj-accordion {
3
+ --kj-accordion-bg: var(--kj-bg-body);
4
+ --kj-accordion-border: var(--kj-border-default);
5
+ --kj-accordion-radius: var(--kj-radius-box);
6
+
7
+ display: block;
8
+ background: var(--kj-accordion-bg);
9
+ border: var(--kj-border) solid var(--kj-accordion-border);
10
+ border-radius: var(--kj-accordion-radius);
11
+ overflow: hidden;
12
+ }
13
+
14
+ .kj-accordion-item { display: block; }
15
+ .kj-accordion-item + .kj-accordion-item { border-top: var(--kj-border) solid var(--kj-accordion-border); }
16
+
17
+ .kj-accordion-trigger {
18
+ width: 100%;
19
+ text-align: left;
20
+ background: none;
21
+ border: 0;
22
+ padding: var(--kj-space-md) var(--kj-space-lg);
23
+ color: var(--kj-fg-default);
24
+ font: 0.875rem / 1.4 var(--kj-font-sans);
25
+ font-weight: 600;
26
+ cursor: pointer;
27
+ display: flex;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+ }
31
+ .kj-accordion-trigger::after {
32
+ content: '▾';
33
+ transition: transform 0.15s;
34
+ }
35
+ .kj-accordion-trigger[aria-expanded="true"]::after { transform: rotate(180deg); }
36
+ .kj-accordion-trigger:focus-visible { outline: 2px solid var(--kj-border-focus); outline-offset: -2px; }
37
+ .kj-accordion-trigger[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
38
+
39
+ /* Always in the DOM so open/close can animate; visual state is driven by
40
+ `[data-state]` from the headless directive. `overflow: hidden` clips the
41
+ content during the max-height transition so children don't bleed out. */
42
+ .kj-accordion-content {
43
+ color: var(--kj-fg-default);
44
+ font: 0.875rem / 1.5 var(--kj-font-sans);
45
+ overflow: hidden;
46
+ max-height: 0;
47
+ opacity: 0;
48
+ /* `padding` participates in the height transition — collapses to 0 when
49
+ closed so the panel reads as fully hidden. */
50
+ padding: 0 var(--kj-space-lg);
51
+ transition:
52
+ max-height var(--kj-transition, 0.2s ease),
53
+ opacity var(--kj-transition, 0.2s ease),
54
+ padding var(--kj-transition, 0.2s ease);
55
+ }
56
+ .kj-accordion-content[data-state="open"] {
57
+ /* `1000px` is the conventional "tall enough" ceiling for height tweens
58
+ on content of unknown size. If panels routinely exceed this, raise the
59
+ value — the cost is only a slightly slower expand animation. */
60
+ max-height: 1000px;
61
+ opacity: 1;
62
+ padding: 0 var(--kj-space-lg) var(--kj-space-md);
63
+ }
64
+
65
+ @media (prefers-reduced-motion: reduce) {
66
+ .kj-accordion-content { transition: none; }
67
+ }
68
+ }
@@ -0,0 +1,79 @@
1
+ @layer kj.component {
2
+ .kj-action-sheet__title {
3
+ margin: 0 0 var(--kj-space-xs);
4
+ font: 600 1.0625rem / 1.3 var(--kj-font-sans);
5
+ color: var(--kj-fg-default);
6
+ text-align: center;
7
+ }
8
+
9
+ .kj-action-sheet__description {
10
+ margin: 0 0 var(--kj-space-md);
11
+ font: 0.875rem / 1.4 var(--kj-font-sans);
12
+ color: var(--kj-fg-muted);
13
+ text-align: center;
14
+ }
15
+
16
+ .kj-action-sheet__list {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--kj-space-2xs, 0.25rem);
20
+ }
21
+
22
+ .kj-action-sheet__item,
23
+ .kj-action-sheet__cancel {
24
+ appearance: none;
25
+ display: flex;
26
+ align-items: center;
27
+ gap: var(--kj-space-sm);
28
+ width: 100%;
29
+ min-height: 44px;
30
+ padding: var(--kj-space-sm) var(--kj-space-md);
31
+ border: 0;
32
+ border-radius: var(--kj-radius-md, 0.5rem);
33
+ background: var(--kj-bg-subtle, transparent);
34
+ color: var(--kj-fg-default);
35
+ font: 500 0.9375rem / 1.4 var(--kj-font-sans);
36
+ text-align: left;
37
+ cursor: pointer;
38
+ }
39
+
40
+ .kj-action-sheet__item:hover:not(:disabled),
41
+ .kj-action-sheet__cancel:hover {
42
+ background: var(--kj-bg-muted, var(--kj-border-muted));
43
+ }
44
+
45
+ .kj-action-sheet__item:focus-visible,
46
+ .kj-action-sheet__cancel:focus-visible {
47
+ outline: var(--kj-focus-ring-width, 2px) solid var(--kj-focus-ring-color, var(--kj-primary));
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ .kj-action-sheet__item:disabled {
52
+ opacity: 0.5;
53
+ cursor: not-allowed;
54
+ }
55
+
56
+ .kj-action-sheet__item.is-destructive {
57
+ color: var(--kj-danger-fg, var(--kj-danger, #d92d20));
58
+ }
59
+ .kj-action-sheet__item.is-destructive:hover:not(:disabled) {
60
+ background: var(--kj-danger-bg-subtle, color-mix(in srgb, var(--kj-danger, #d92d20) 12%, transparent));
61
+ }
62
+
63
+ .kj-action-sheet__icon {
64
+ flex-shrink: 0;
65
+ width: 1.25rem;
66
+ height: 1.25rem;
67
+ }
68
+
69
+ .kj-action-sheet__label {
70
+ flex: 1;
71
+ }
72
+
73
+ .kj-action-sheet__cancel {
74
+ justify-content: center;
75
+ margin-top: var(--kj-space-sm);
76
+ font-weight: 600;
77
+ background: var(--kj-bg-muted, var(--kj-border-muted));
78
+ }
79
+ }
@@ -0,0 +1,133 @@
1
+ @layer kj.component {
2
+ .kj-alert {
3
+ /* component knobs — themes/users override any of these */
4
+ --kj-alert-bg: var(--kj-bg-body);
5
+ --kj-alert-fg: var(--kj-fg-default);
6
+ --kj-alert-border-color: var(--kj-border-default);
7
+ --kj-alert-accent: var(--kj-fg-info);
8
+ --kj-alert-radius: var(--kj-radius-box, 0.5rem);
9
+ --kj-alert-padding-x: var(--kj-space-lg, 1rem);
10
+ --kj-alert-padding-y: var(--kj-space-md, 0.75rem);
11
+ --kj-alert-gap: var(--kj-space-md, 0.75rem);
12
+ --kj-alert-font-size: var(--kj-text-sm, 0.875rem);
13
+
14
+ display: flex;
15
+ align-items: flex-start;
16
+ gap: var(--kj-alert-gap);
17
+ background: var(--kj-alert-bg);
18
+ color: var(--kj-alert-fg);
19
+ border: var(--kj-border, 1px) solid var(--kj-alert-border-color);
20
+ border-left: 4px solid var(--kj-alert-accent);
21
+ border-radius: var(--kj-alert-radius);
22
+ padding: var(--kj-alert-padding-y) var(--kj-alert-padding-x);
23
+ font: var(--kj-alert-font-size) / 1.45 var(--kj-font-sans, sans-serif);
24
+ transition: opacity 200ms ease, transform 200ms ease;
25
+ }
26
+
27
+ .kj-alert[data-dismissed="true"] {
28
+ opacity: 0;
29
+ transform: translateY(-4px);
30
+ }
31
+
32
+ /* Severity variants — flip accent + (optionally) bg/fg. */
33
+ .kj-alert[data-variant="info"] { --kj-alert-accent: var(--kj-fg-info, #2563eb); }
34
+ .kj-alert[data-variant="success"] { --kj-alert-accent: var(--kj-fg-success, #16a34a); }
35
+ .kj-alert[data-variant="warning"] { --kj-alert-accent: var(--kj-fg-warning, #d97706); }
36
+ .kj-alert[data-variant="error"] { --kj-alert-accent: var(--kj-fg-danger, #dc2626); }
37
+ .kj-alert[data-variant="neutral"] { --kj-alert-accent: var(--kj-fg-muted, #6b7280); }
38
+
39
+ /* Static / banner mode — span full width, sticky-friendly chrome. */
40
+ .kj-alert[data-mode="static"] {
41
+ border-left-width: var(--kj-border, 1px);
42
+ border-top: 4px solid var(--kj-alert-accent);
43
+ border-radius: 0;
44
+ }
45
+
46
+ /* Sizes */
47
+ .kj-alert[data-size="sm"] {
48
+ --kj-alert-padding-x: var(--kj-space-md, 0.75rem);
49
+ --kj-alert-padding-y: var(--kj-space-sm, 0.5rem);
50
+ --kj-alert-font-size: 0.8125rem;
51
+ }
52
+ .kj-alert[data-size="lg"] {
53
+ --kj-alert-padding-x: var(--kj-space-xl, 1.25rem);
54
+ --kj-alert-padding-y: var(--kj-space-lg, 1rem);
55
+ --kj-alert-font-size: 1rem;
56
+ }
57
+
58
+ /* ── Body wrapper ─────────────────────────────────────────────── */
59
+ .kj-alert__body {
60
+ flex: 1 1 auto;
61
+ min-width: 0;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 0.25rem;
65
+ }
66
+
67
+ /* ── Title / Description ──────────────────────────────────────── */
68
+ .kj-alert__title {
69
+ margin: 0;
70
+ font-weight: 600;
71
+ font-size: 1em;
72
+ color: var(--kj-alert-accent);
73
+ line-height: 1.3;
74
+ }
75
+ .kj-alert__description {
76
+ margin: 0;
77
+ color: var(--kj-alert-fg);
78
+ opacity: 0.9;
79
+ }
80
+
81
+ /* ── Icon ─────────────────────────────────────────────────────── */
82
+ .kj-alert__icon {
83
+ flex: 0 0 auto;
84
+ display: inline-flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ width: 1.25em;
88
+ height: 1.25em;
89
+ color: var(--kj-alert-accent);
90
+ margin-top: 0.1em;
91
+ }
92
+
93
+ /* ── Actions ──────────────────────────────────────────────────── */
94
+ .kj-alert__actions {
95
+ flex: 0 0 auto;
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: var(--kj-space-sm, 0.5rem);
99
+ margin-left: auto;
100
+ }
101
+
102
+ /* ── Dismiss ──────────────────────────────────────────────────── */
103
+ .kj-alert__dismiss {
104
+ flex: 0 0 auto;
105
+ appearance: none;
106
+ background: transparent;
107
+ border: 0;
108
+ color: inherit;
109
+ cursor: pointer;
110
+ padding: 4px 8px;
111
+ border-radius: 4px;
112
+ line-height: 1;
113
+ font-size: 1.1rem;
114
+ opacity: 0.7;
115
+ margin-left: auto;
116
+ /* Touch-target floor (WCAG 2.5.5) */
117
+ min-width: 2.75rem;
118
+ min-height: 2.75rem;
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ }
123
+ .kj-alert__dismiss:hover { opacity: 1; background: rgb(0 0 0 / 0.06); }
124
+ .kj-alert__dismiss:focus-visible {
125
+ outline: 2px solid var(--kj-alert-accent);
126
+ outline-offset: 2px;
127
+ }
128
+
129
+ /* When actions and dismiss coexist, dismiss should not push actions away. */
130
+ .kj-alert__actions + .kj-alert__dismiss {
131
+ margin-left: 0;
132
+ }
133
+ }
@@ -0,0 +1,93 @@
1
+ @layer kj.component {
2
+ .kj-avatar {
3
+ --kj-avatar-size: 2.5rem;
4
+ --kj-avatar-bg: var(--kj-bg-field);
5
+ --kj-avatar-fg: var(--kj-fg-default);
6
+ --kj-avatar-radius: 9999px;
7
+
8
+ position: relative;
9
+ display: inline-flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ width: var(--kj-avatar-size);
13
+ height: var(--kj-avatar-size);
14
+ background: var(--kj-avatar-bg);
15
+ color: var(--kj-avatar-fg);
16
+ border-radius: var(--kj-avatar-radius);
17
+ overflow: hidden;
18
+ font: 0.875rem / 1 var(--kj-font-sans);
19
+ font-weight: 600;
20
+ }
21
+
22
+ /* `rounded` reads `--kj-radius-box-lg` (1rem default, declared in
23
+ `kj.base`) rather than `--kj-radius-box` because some themes (kouji,
24
+ bauhaus) set `--kj-radius-box` to 0 as a brutalist signature — falling
25
+ back to that would make `rounded` and `circle` look identical on those
26
+ themes. `--kj-radius-box-lg` stays unset by theme overrides, so the
27
+ toggle always produces a visible shape change. */
28
+ .kj-avatar[data-shape='rounded'] {
29
+ --kj-avatar-radius: var(--kj-radius-box-lg);
30
+ }
31
+
32
+ /* The host's `[hidden]` attribute (written by the avatar-group when a
33
+ child overflows `kjMax`) needs an explicit `display: none` because the
34
+ `inline-flex` declaration above wins against the UA `[hidden] { display:
35
+ none }` rule via selector specificity. Without this, "hidden" overflow
36
+ avatars still render and `kjMax` looks broken. */
37
+ .kj-avatar[hidden] {
38
+ display: none;
39
+ }
40
+
41
+ /* The "+N" chip is a native button wrapping the +N avatar: no chrome of
42
+ its own, the avatar paints; the open state tints the avatar. */
43
+ .kj-avatar-group-overflow-trigger {
44
+ display: inline-flex;
45
+ padding: 0;
46
+ border: 0;
47
+ background: none;
48
+ border-radius: 9999px;
49
+ }
50
+ .kj-avatar-group-overflow-trigger[aria-expanded='true'] .kj-avatar {
51
+ --kj-avatar-bg: var(--kj-bg-primary);
52
+ --kj-avatar-fg: var(--kj-fg-on-primary);
53
+ }
54
+
55
+ .kj-avatar[data-size='xs'] {
56
+ --kj-avatar-size: 1.5rem;
57
+ font-size: 0.6875rem;
58
+ }
59
+ .kj-avatar[data-size='sm'] {
60
+ --kj-avatar-size: 2rem;
61
+ font-size: 0.75rem;
62
+ }
63
+ .kj-avatar[data-size='lg'] {
64
+ --kj-avatar-size: 3rem;
65
+ font-size: 1rem;
66
+ }
67
+ .kj-avatar[data-size='xl'] {
68
+ --kj-avatar-size: 4rem;
69
+ font-size: 1.25rem;
70
+ }
71
+
72
+ .kj-avatar-image {
73
+ width: 100%;
74
+ height: 100%;
75
+ object-fit: cover;
76
+ display: block;
77
+ }
78
+ .kj-avatar-image:not([data-loaded]) {
79
+ opacity: 0;
80
+ }
81
+
82
+ .kj-avatar-fallback {
83
+ position: absolute;
84
+ inset: 0;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ }
89
+ .kj-avatar-fallback:not([data-visible]) {
90
+ opacity: 0;
91
+ pointer-events: none;
92
+ }
93
+ }
@@ -0,0 +1,59 @@
1
+ @layer kj.component {
2
+ .kj-badge {
3
+ --kj-badge-bg: var(--kj-bg-primary);
4
+ --kj-badge-fg: var(--kj-fg-on-primary);
5
+ --kj-badge-border-color: transparent;
6
+ --kj-badge-radius: var(--kj-radius-selector);
7
+ --kj-badge-padding-x: var(--kj-space-sm);
8
+ --kj-badge-padding-y: 0.125rem;
9
+ --kj-badge-font-size: 0.75rem;
10
+ --kj-badge-dot-size: 0.375rem;
11
+ --kj-badge-dot-color: currentColor;
12
+
13
+ display: inline-flex;
14
+ align-items: center;
15
+ gap: 0.25rem;
16
+ background: var(--kj-badge-bg);
17
+ color: var(--kj-badge-fg);
18
+ border: var(--kj-border) solid var(--kj-badge-border-color);
19
+ border-radius: var(--kj-badge-radius);
20
+ padding: var(--kj-badge-padding-y) var(--kj-badge-padding-x);
21
+ font: var(--kj-badge-font-size) / 1 var(--kj-font-sans);
22
+ font-weight: 500;
23
+ }
24
+
25
+ .kj-badge[data-dot]::before {
26
+ content: "";
27
+ flex: none;
28
+ width: var(--kj-badge-dot-size);
29
+ height: var(--kj-badge-dot-size);
30
+ border-radius: 50%;
31
+ background: var(--kj-badge-dot-color);
32
+ }
33
+
34
+ .kj-badge[data-variant="secondary"] {
35
+ --kj-badge-bg: var(--kj-bg-surface);
36
+ --kj-badge-fg: var(--kj-fg-default);
37
+ }
38
+ .kj-badge[data-variant="destructive"] {
39
+ --kj-badge-bg: var(--kj-bg-danger);
40
+ --kj-badge-fg: var(--kj-fg-on-danger);
41
+ }
42
+ .kj-badge[data-variant="outline"] {
43
+ --kj-badge-bg: transparent;
44
+ --kj-badge-fg: var(--kj-fg-default);
45
+ --kj-badge-border-color: var(--kj-border-default);
46
+ }
47
+
48
+ .kj-badge[data-size="xs"] {
49
+ --kj-badge-padding-x: 0.1875rem;
50
+ --kj-badge-padding-y: 0.03125rem;
51
+ --kj-badge-font-size: 0.5625rem;
52
+ }
53
+ .kj-badge[data-size="sm"] {
54
+ --kj-badge-padding-x: 0.375rem;
55
+ --kj-badge-padding-y: 0.125rem;
56
+ --kj-badge-font-size: 0.6875rem;
57
+ }
58
+ .kj-badge[data-size="lg"] { --kj-badge-padding-x: var(--kj-space-md); --kj-badge-font-size: 0.875rem; }
59
+ }
@@ -0,0 +1,145 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ .kj-breadcrumb — component-layer tokens + structural CSS
3
+ Tokens default to shared-layer values from @kouji-ui/themes.
4
+ The component owns its own gap, separator content, and per-cell
5
+ colours; the per-crumb anchor inherits Link's tokens by default.
6
+ ──────────────────────────────────────────────────────────── */
7
+
8
+ @layer kj.component {
9
+ .kj-breadcrumb {
10
+ /* component knobs — themes / users can override any of these */
11
+ --kj-breadcrumb-gap: var(--kj-space-sm, 0.5rem);
12
+ --kj-breadcrumb-separator-content: '/';
13
+ --kj-breadcrumb-overflow-pad: 0.25rem;
14
+ --kj-color-breadcrumb-muted: var(--kj-fg-muted);
15
+ --kj-color-breadcrumb-current: var(--kj-fg-default);
16
+
17
+ display: block;
18
+ color: var(--kj-color-breadcrumb-muted);
19
+ font-size: 0.875rem;
20
+ }
21
+
22
+ /* ── Sizes (root) ───────────────────────────────────────────── */
23
+ .kj-breadcrumb[data-size="sm"] { font-size: 0.8125rem; }
24
+ .kj-breadcrumb[data-size="md"] { font-size: 0.875rem; }
25
+ .kj-breadcrumb[data-size="lg"] { font-size: 1rem; }
26
+
27
+ /* ── Ordered list ───────────────────────────────────────────── */
28
+ .kj-breadcrumb-list {
29
+ display: flex;
30
+ align-items: center;
31
+ flex-wrap: nowrap;
32
+ gap: var(--kj-breadcrumb-gap);
33
+ margin: 0;
34
+ padding: 0;
35
+ list-style: none;
36
+ min-width: 0;
37
+ }
38
+
39
+ .kj-breadcrumb-list[data-wrap="wrap"] { flex-wrap: wrap; }
40
+ .kj-breadcrumb-list[data-wrap="no-wrap"] { flex-wrap: nowrap; }
41
+ .kj-breadcrumb-list[data-wrap="truncate"] { flex-wrap: nowrap; }
42
+
43
+ /* ── Item ───────────────────────────────────────────────────── */
44
+ .kj-breadcrumb-item {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: var(--kj-breadcrumb-gap);
48
+ min-width: 0;
49
+ }
50
+
51
+ .kj-breadcrumb-item[data-hidden] { display: none; }
52
+
53
+ /* ── Auto separator (CSS pseudo-element) ────────────────────── */
54
+ .kj-breadcrumb-list:not([data-explicit-separators]) > .kj-breadcrumb-item:not(:first-child)::before {
55
+ content: var(--kj-breadcrumb-separator-content, '/');
56
+ color: var(--kj-color-breadcrumb-muted);
57
+ opacity: 0.65;
58
+ user-select: none;
59
+ margin-inline-end: var(--kj-breadcrumb-gap);
60
+ }
61
+
62
+ /* RTL mirroring: chevron-like glyphs flip; slashes stay. The data-attr
63
+ is set by the consumer when they want mirroring; we ship the rule. */
64
+ [dir="rtl"] .kj-breadcrumb-list:not([data-explicit-separators]) > .kj-breadcrumb-item:not(:first-child)::before {
65
+ /* No-op for '/'. For '›' / '→' the consumer adds [data-mirror-separator]. */
66
+ }
67
+ [dir="rtl"] .kj-breadcrumb[data-mirror-separator] .kj-breadcrumb-list:not([data-explicit-separators]) > .kj-breadcrumb-item:not(:first-child)::before {
68
+ display: inline-block;
69
+ transform: scaleX(-1);
70
+ }
71
+
72
+ /* ── Explicit separator ─────────────────────────────────────── */
73
+ .kj-breadcrumb-separator {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ color: var(--kj-color-breadcrumb-muted);
77
+ opacity: 0.65;
78
+ user-select: none;
79
+ }
80
+
81
+ /* ── Per-crumb link ─────────────────────────────────────────────
82
+ Owns its resting colour. The anchor carries `.kj-link`, but the
83
+ KjLink *component* (which ships link.css) may not be on the page,
84
+ so those rules aren't guaranteed to load — without an explicit
85
+ colour the anchor falls back to the UA default blue in every theme.
86
+ Retargeting the Link tokens keeps styling consistent when link.css
87
+ *is* present. */
88
+ .kj-breadcrumb-link {
89
+ --kj-link-fg: var(--kj-color-breadcrumb-muted);
90
+ --kj-link-hover-fg: var(--kj-color-breadcrumb-current);
91
+ color: var(--kj-link-fg);
92
+ text-decoration: none;
93
+ padding-block: 0.5rem; /* hit-area extension for WCAG 2.5.5 */
94
+ transition: color var(--kj-transition, 0.15s ease);
95
+ }
96
+
97
+ .kj-breadcrumb-link:hover {
98
+ color: var(--kj-link-hover-fg);
99
+ text-decoration: underline;
100
+ text-underline-offset: 3px;
101
+ }
102
+
103
+ /* Self-contained focus ring — link.css may not be loaded (see above),
104
+ so don't depend on `.kj-link:focus-visible` for WCAG 2.4.7. */
105
+ .kj-breadcrumb-link:focus-visible {
106
+ outline: 2px solid var(--kj-fg-primary);
107
+ outline-offset: 2px;
108
+ border-radius: var(--kj-radius-field, 0.25rem);
109
+ }
110
+
111
+ /* ── Current cell ───────────────────────────────────────────── */
112
+ .kj-breadcrumb-current {
113
+ color: var(--kj-color-breadcrumb-current);
114
+ font-weight: 500;
115
+ }
116
+
117
+ /* ── Ellipsis cell ──────────────────────────────────────────── */
118
+ .kj-breadcrumb-ellipsis {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ min-width: 1.5rem;
123
+ padding-inline: var(--kj-breadcrumb-overflow-pad);
124
+ color: var(--kj-color-breadcrumb-muted);
125
+ background: transparent;
126
+ border: 0;
127
+ cursor: default;
128
+ user-select: none;
129
+ }
130
+
131
+ .kj-breadcrumb-ellipsis[data-overflow="menu"] {
132
+ cursor: pointer;
133
+ border-radius: var(--kj-radius-field, 0.25rem);
134
+ }
135
+
136
+ .kj-breadcrumb-ellipsis[data-overflow="menu"]:hover {
137
+ background: var(--kj-bg-surface, transparent);
138
+ }
139
+
140
+ /* ── Print: show the full breadcrumb ────────────────────────── */
141
+ @media print {
142
+ .kj-breadcrumb-item[data-hidden] { display: revert; }
143
+ .kj-breadcrumb-ellipsis { display: none; }
144
+ }
145
+ }