kiso 0.5.3.pre → 0.6.0.pre

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/tailwind/kiso/button.css +12 -3
  3. data/app/assets/tailwind/kiso/checkbox.css +13 -2
  4. data/app/assets/tailwind/kiso/color-mode.css +15 -3
  5. data/app/assets/tailwind/kiso/dashboard.css +97 -44
  6. data/app/assets/tailwind/kiso/dialog.css +39 -5
  7. data/app/assets/tailwind/kiso/engine.css +117 -34
  8. data/app/assets/tailwind/kiso/input-otp.css +24 -4
  9. data/app/assets/tailwind/kiso/palettes/blue.css +14 -5
  10. data/app/assets/tailwind/kiso/palettes/green.css +9 -5
  11. data/app/assets/tailwind/kiso/palettes/orange.css +9 -5
  12. data/app/assets/tailwind/kiso/palettes/violet.css +9 -5
  13. data/app/assets/tailwind/kiso/palettes/zinc.css +11 -7
  14. data/app/assets/tailwind/kiso/radio-group.css +11 -4
  15. data/app/assets/tailwind/kiso/slider.css +25 -6
  16. data/app/assets/tailwind/kiso/tooltip.css +37 -11
  17. data/app/helpers/kiso/app_component_helper.rb +83 -34
  18. data/app/helpers/kiso/component_helper.rb +227 -70
  19. data/app/helpers/kiso/icon_helper.rb +101 -39
  20. data/app/helpers/kiso/theme_helper.rb +50 -9
  21. data/app/helpers/kiso/ui_context_helper.rb +87 -35
  22. data/app/javascript/controllers/kiso/combobox_controller.js +10 -2
  23. data/app/javascript/controllers/kiso/command_controller.js +2 -0
  24. data/app/javascript/controllers/kiso/command_dialog_controller.js +4 -0
  25. data/app/javascript/controllers/kiso/dialog_controller.js +6 -1
  26. data/app/javascript/controllers/kiso/dialog_trigger_controller.js +1 -1
  27. data/app/javascript/controllers/kiso/dropdown_menu_controller.js +23 -5
  28. data/app/javascript/controllers/kiso/index.js +25 -0
  29. data/app/javascript/controllers/kiso/input_otp_controller.js +5 -3
  30. data/app/javascript/controllers/kiso/popover_controller.js +18 -4
  31. data/app/javascript/controllers/kiso/select_controller.js +10 -2
  32. data/app/javascript/controllers/kiso/sidebar_controller.js +26 -4
  33. data/app/javascript/controllers/kiso/slider_controller.js +3 -3
  34. data/app/javascript/controllers/kiso/theme_controller.js +2 -1
  35. data/app/javascript/controllers/kiso/toggle_controller.js +2 -0
  36. data/app/javascript/controllers/kiso/toggle_group_controller.js +3 -0
  37. data/app/javascript/controllers/kiso/tooltip_controller.js +3 -0
  38. data/app/javascript/kiso/utils/focusable.js +14 -0
  39. data/app/javascript/kiso/utils/highlight.js +15 -1
  40. data/app/views/kiso/components/_alert.html.erb +2 -0
  41. data/app/views/kiso/components/_alert_dialog.html.erb +5 -2
  42. data/app/views/kiso/components/_app.html.erb +2 -0
  43. data/app/views/kiso/components/_aspect_ratio.html.erb +1 -0
  44. data/app/views/kiso/components/_avatar.html.erb +4 -0
  45. data/app/views/kiso/components/_button.html.erb +3 -0
  46. data/app/views/kiso/components/_checkbox.html.erb +1 -0
  47. data/app/views/kiso/components/_color_mode_button.html.erb +2 -0
  48. data/app/views/kiso/components/_color_mode_select.html.erb +2 -0
  49. data/app/views/kiso/components/_combobox.html.erb +3 -0
  50. data/app/views/kiso/components/_command.html.erb +2 -0
  51. data/app/views/kiso/components/_dashboard_group.html.erb +4 -0
  52. data/app/views/kiso/components/_dashboard_navbar.html.erb +2 -0
  53. data/app/views/kiso/components/_dashboard_panel.html.erb +1 -0
  54. data/app/views/kiso/components/_dashboard_sidebar.html.erb +2 -0
  55. data/app/views/kiso/components/_dashboard_toolbar.html.erb +2 -0
  56. data/app/views/kiso/components/_dialog.html.erb +3 -0
  57. data/app/views/kiso/components/_dropdown_menu.html.erb +2 -0
  58. data/app/views/kiso/components/_empty.html.erb +2 -0
  59. data/app/views/kiso/components/_field.html.erb +2 -0
  60. data/app/views/kiso/components/_field_group.html.erb +1 -0
  61. data/app/views/kiso/components/_field_set.html.erb +1 -0
  62. data/app/views/kiso/components/_input_group.html.erb +1 -0
  63. data/app/views/kiso/components/_input_otp.html.erb +3 -0
  64. data/app/views/kiso/components/_nav.html.erb +2 -0
  65. data/app/views/kiso/components/_page_card.html.erb +3 -0
  66. data/app/views/kiso/components/_page_header.html.erb +3 -0
  67. data/app/views/kiso/components/_page_section.html.erb +2 -0
  68. data/app/views/kiso/components/_pagination.html.erb +2 -0
  69. data/app/views/kiso/components/_popover.html.erb +3 -0
  70. data/app/views/kiso/components/_select.html.erb +3 -0
  71. data/app/views/kiso/components/_select_native.html.erb +2 -0
  72. data/app/views/kiso/components/_separator.html.erb +2 -0
  73. data/app/views/kiso/components/_skeleton.html.erb +1 -0
  74. data/app/views/kiso/components/_slider.html.erb +4 -0
  75. data/app/views/kiso/components/_spinner.html.erb +2 -0
  76. data/app/views/kiso/components/_stats_card.html.erb +2 -0
  77. data/app/views/kiso/components/_stats_grid.html.erb +1 -0
  78. data/app/views/kiso/components/_switch.html.erb +2 -0
  79. data/app/views/kiso/components/_table.html.erb +2 -0
  80. data/app/views/kiso/components/_textarea.html.erb +3 -0
  81. data/app/views/kiso/components/_toggle.html.erb +2 -0
  82. data/app/views/kiso/components/_toggle_group.html.erb +2 -0
  83. data/app/views/kiso/components/_tooltip.html.erb +3 -0
  84. data/app/views/kiso/components/alert_dialog/_action.html.erb +1 -0
  85. data/app/views/kiso/components/alert_dialog/_cancel.html.erb +1 -0
  86. data/app/views/kiso/components/alert_dialog/_description.html.erb +1 -0
  87. data/app/views/kiso/components/alert_dialog/_title.html.erb +1 -0
  88. data/app/views/kiso/components/avatar/_image.html.erb +1 -0
  89. data/app/views/kiso/components/breadcrumb/_separator.html.erb +3 -0
  90. data/app/views/kiso/components/combobox/_chips.html.erb +3 -0
  91. data/app/views/kiso/components/command/_dialog.html.erb +2 -0
  92. data/app/views/kiso/components/dashboard_sidebar/_collapse.html.erb +2 -0
  93. data/app/views/kiso/components/dialog/_close.html.erb +1 -0
  94. data/app/views/kiso/components/field/_error.html.erb +4 -0
  95. data/app/views/kiso/components/field/_label.html.erb +2 -0
  96. data/app/views/kiso/components/field/_separator.html.erb +3 -0
  97. data/app/views/kiso/components/input_otp/_separator.html.erb +2 -0
  98. data/app/views/kiso/components/input_otp/_slot.html.erb +2 -0
  99. data/app/views/kiso/components/nav/_section.html.erb +4 -0
  100. data/app/views/kiso/components/tooltip/_content.html.erb +2 -0
  101. data/lib/generators/kiso/install/USAGE +23 -0
  102. data/lib/generators/kiso/install/install_generator.rb +91 -0
  103. data/lib/generators/kiso/install/templates/design_system.md.tt +190 -0
  104. data/lib/generators/kiso/install/templates/initializer.rb.tt +40 -0
  105. data/lib/kiso/cli/make.rb +6 -3
  106. data/lib/kiso/cli.rb +10 -0
  107. data/lib/kiso/color_utils.rb +21 -2
  108. data/lib/kiso/engine.rb +9 -2
  109. data/lib/kiso/propshaft_tailwind_stub_filter.rb +9 -2
  110. data/lib/kiso/themes/avatar.rb +40 -6
  111. data/lib/kiso/themes/badge.rb +5 -1
  112. data/lib/kiso/themes/color_mode_button.rb +11 -0
  113. data/lib/kiso/themes/color_mode_select.rb +7 -0
  114. data/lib/kiso/themes/dashboard.rb +28 -0
  115. data/lib/kiso/themes/dropdown_menu.rb +2 -2
  116. data/lib/kiso/themes/input_otp.rb +6 -3
  117. data/lib/kiso/themes/nav.rb +17 -0
  118. data/lib/kiso/themes/pagination.rb +9 -4
  119. data/lib/kiso/themes/shared.rb +27 -7
  120. data/lib/kiso/version.rb +5 -2
  121. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e2ae8ad60d92d8f26f348cfcf1dc7e244ab9d43d8650c70bb55563a569ebd92
4
- data.tar.gz: 75bcf4fd1147c79754256ab6c3cccd994a1b5a7ace087fd52529e716d7521422
3
+ metadata.gz: 71bd073faf4eaf81467972ad2e834769956d808d79790d0de417697766823fcf
4
+ data.tar.gz: c31b53740f75c262b588d7b56416ed91a58496e6aad704286662c4575af81dff
5
5
  SHA512:
6
- metadata.gz: 80ddd9d1a892eec79083c535516369d0278c3680ee75fd9e7cbffb406a8684d705805104ee12dd2e8d5a920baa15aa1a445d31c637d0ec8a8a675b05889f96e6
7
- data.tar.gz: '06824eba6296399c458259426d806da86901040d0922c696efc1f066e0c1feed2c8f405fb09edc5344e54995ef51a932e313291ee77846605315d3e8c433f1ca'
6
+ metadata.gz: fd986968f5371f77ac6ab53852d6a9227fdb645734fd1c8fa51e0c6737b1074bb6d1839a6779bd3f2ed4e30c69f126a504ad5330b607ba68e4deadd9a735b125
7
+ data.tar.gz: c1834a11353dd43503b96317612cdbf76287c614845e264b53efb23182cdfc472f92dacd81d673cd7d2834abd278e0af96c22d63e1d894951459776975a15080
@@ -1,6 +1,15 @@
1
- /* Default display for buttons — lives in @layer components so utility classes
2
- (including `hidden`) override via the higher-priority utilities layer. When
3
- a utility like `hidden` is removed via JS, this rule restores inline-flex.
1
+ /* ── Button ──────────────────────────────────────────────────────────────────
2
+ Default display value for the Button component.
3
+
4
+ Why CSS instead of a Tailwind class?
5
+ The theme module applies `inline-flex` via Tailwind, but Tailwind utilities
6
+ live in @layer utilities — the highest-priority layer. That means when JS
7
+ removes a `hidden` class, the button's `inline-flex` (also in utilities)
8
+ has no cascade advantage to restore visibility. Placing the default display
9
+ in @layer components (lower priority) lets utility classes like `hidden`
10
+ override it naturally, while still restoring inline-flex when `hidden` is
11
+ removed.
12
+
4
13
  Fixes: https://github.com/steveclarke/kiso/issues/200 */
5
14
 
6
15
  @layer components {
@@ -1,5 +1,16 @@
1
- /* Checkbox indicator — CSS ::after for the checkmark since native <input>
2
- can't contain child elements. Uses mask-image with the Lucide Check icon. */
1
+ /* ── Checkbox ────────────────────────────────────────────────────────────────
2
+ Checkmark indicator via CSS ::after pseudo-element.
3
+
4
+ Why CSS instead of ERB?
5
+ Native <input type="checkbox"> cannot contain child elements, so the
6
+ checkmark must be rendered as a pseudo-element. The indicator uses
7
+ mask-image with an inline SVG (Lucide Check icon) and currentColor for
8
+ the fill, which inherits the text color set by compound variants — this
9
+ keeps the checkmark color in sync with the checkbox's checked state
10
+ styling (e.g., text-primary-foreground on a bg-primary background).
11
+
12
+ The grid + place-content: center pattern ensures the ::after checkmark
13
+ is perfectly centered within the checkbox regardless of its size. */
3
14
 
4
15
  [data-component="checkbox"] {
5
16
  display: grid;
@@ -1,6 +1,18 @@
1
- /* ── Color Mode Components ────────────────────────────────────────────────────
2
- Icon visibility for the color mode button. The .dark class on <html> drives
3
- which icon is shown — sun in light mode, moon in dark mode. */
1
+ /* ── Color Mode Button ────────────────────────────────────────────────────────
2
+ Icon visibility toggling for the light/dark mode button (kui(:color_mode_button)).
3
+
4
+ Why CSS instead of ERB?
5
+ The icon swap must respond to the .dark class on <html>, which is set by a
6
+ blocking script before first paint. Using CSS ensures the correct icon
7
+ (sun or moon) is visible immediately — no Stimulus controller or JS needed
8
+ for the initial render. The kiso--theme controller handles toggling after
9
+ that.
10
+
11
+ Uses @layer components so utility classes on the icon elements can override
12
+ if needed.
13
+
14
+ Light mode: sun icon visible, moon icon hidden.
15
+ Dark mode: moon icon visible, sun icon hidden. */
4
16
 
5
17
  @layer components {
6
18
  [data-slot="color-mode-icon-dark"] { display: none; }
@@ -1,37 +1,51 @@
1
1
  /* ── Kiso Dashboard Layout ────────────────────────────────────────────────────
2
- Tokens and mechanics for the full-screen sidebar + topbar dashboard shell.
3
- Bundled with the engine all apps get these tokens and layout rules.
2
+ CSS grid mechanics, tokens, and state-driven rules for the full-screen
3
+ sidebar + topbar dashboard shell (kui(:dashboard_group) and friends).
4
4
 
5
- Host apps can override any token in their own @theme block:
6
- @theme { --sidebar-width: 18rem; }
7
- */
5
+ Why CSS instead of ERB?
6
+ The dashboard layout uses CSS Grid with animated column widths, fixed
7
+ positioning for mobile overlays, pseudo-state-driven visibility, and
8
+ custom Tailwind variants — none of which can be expressed as Tailwind
9
+ utility classes in ERB templates. The sidebar open/close animation,
10
+ grid template transitions, mobile overlay transform, and scrim backdrop
11
+ all require real CSS rules.
8
12
 
9
- /* ── Tokens ──────────────────────────────────────────────────────────────── */
13
+ Host apps can override any @theme token in their own @theme block:
14
+ @theme { --sidebar-width: 18rem; --topbar-height: 4rem; }
15
+
16
+ All selectors target [data-slot="..."] attributes (shadcn v4 convention)
17
+ rather than class names, making them resilient to theme customization.
18
+ ──────────────────────────────────────────────────────────────────────────── */
19
+
20
+ /* ── Design Tokens ───────────────────────────────────────────────────────── */
10
21
 
11
22
  @theme {
12
- /* Sidebar geometry */
23
+ /* Sidebar geometry — fixed width when open (collapses to 0 when closed) */
13
24
  --sidebar-width: 16rem;
14
25
 
15
- /* Layout heights */
26
+ /* Topbar row height — sidebar header matches this to align horizontally */
16
27
  --topbar-height: 3.5rem;
17
28
 
18
- /* Sidebar surface tokens (light mode defaults) */
29
+ /* Sidebar surface tokens (light mode defaults).
30
+ These are separate from the main semantic tokens because the sidebar
31
+ can have its own distinct color scheme (e.g., a dark sidebar in an
32
+ otherwise light app). */
19
33
  --sidebar-background: var(--color-white);
20
34
  --sidebar-foreground: var(--color-zinc-900);
21
35
  --sidebar-border: var(--color-zinc-200);
22
36
  --sidebar-accent: var(--color-zinc-100);
23
37
  --sidebar-accent-foreground: var(--color-zinc-700);
24
38
 
25
- /* Animation */
39
+ /* Animation timing for sidebar open/close transitions */
26
40
  --sidebar-duration: 220ms;
27
41
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
28
42
 
29
- /* Z-index stack */
43
+ /* Z-index stack — sidebar above topbar for mobile overlay */
30
44
  --z-topbar: 30;
31
45
  --z-sidebar: 40;
32
46
  }
33
47
 
34
- /* Dark mode sidebar token overrides */
48
+ /* Dark mode overrides for sidebar-specific surface tokens */
35
49
  .dark {
36
50
  --sidebar-background: var(--color-zinc-950);
37
51
  --sidebar-foreground: var(--color-zinc-100);
@@ -40,11 +54,13 @@
40
54
  --sidebar-accent-foreground: var(--color-zinc-300);
41
55
  }
42
56
 
43
- /* ── Sidebar state variants ──────────────────────────────────────────────────
44
- Custom Tailwind variants for showing/hiding content based on sidebar state.
45
- Works on any descendant of dashboard_group. Compose with breakpoints:
46
- kui-sidebar-open:lg:hidden — hide on desktop when sidebar expanded
47
- kui-sidebar-closed:lg:hidden — hide on desktop when sidebar collapsed */
57
+ /* ── Sidebar State Variants (Tailwind v4 @custom-variant) ────────────────────
58
+ Register custom Tailwind variants that activate based on sidebar open/close
59
+ state. These let ERB templates use conditional classes without JS:
60
+ kui-sidebar-open:lg:hidden — hide on desktop when sidebar is expanded
61
+ kui-sidebar-closed:lg:hidden — hide on desktop when sidebar is collapsed
62
+ The variant matches any descendant of an element with the data-sidebar-open
63
+ attribute (set by the kiso--sidebar Stimulus controller on dashboard_group). */
48
64
 
49
65
  @custom-variant kui-sidebar-open {
50
66
  [data-sidebar-open="true"] & {
@@ -58,53 +74,71 @@
58
74
  }
59
75
  }
60
76
 
61
- /* ── Layout mechanics ─────────────────────────────────────────────────────── */
77
+ /* ── Layout Mechanics ─────────────────────────────────────────────────────────
78
+ All layout rules live in @layer components so Tailwind utility classes
79
+ (in the higher-priority @layer utilities) can override them when needed.
80
+
81
+ Grid structure (default "sidebar" layout):
82
+ ┌─────────────┬──────────────────────────┐
83
+ │ sidebar │ topbar (navbar) │ row 1: --topbar-height
84
+ │ (full ├──────────────────────────┤
85
+ │ height) │ panel (main content) │ row 2: 1fr
86
+ └─────────────┴──────────────────────────┘
87
+ col 1: sidebar col 2: 1fr
88
+ ──────────────────────────────────────────────────────────────────────────── */
62
89
 
63
90
  @layer components {
64
- /* Drive --sidebar-current-width from the data attribute set by sidebar controller */
91
+
92
+ /* ── Sidebar width state ─────────────────────────────────────────────────
93
+ The kiso--sidebar Stimulus controller sets data-sidebar-open on the
94
+ dashboard_group element. These rules translate that attribute into a
95
+ CSS variable that drives the grid column width. */
65
96
  [data-sidebar-open="true"] { --sidebar-current-width: var(--sidebar-width); }
66
97
  [data-sidebar-open="false"] { --sidebar-current-width: 0rem; }
67
98
 
68
- /* Hide sidebar border when collapsed to prevent ghost border at 0 width */
99
+ /* Prevent a 1px ghost border when sidebar is fully collapsed */
69
100
  [data-sidebar-open="false"] [data-slot="dashboard-sidebar"] {
70
101
  border-right-width: 0;
71
102
  }
72
103
 
73
- /* Flat 2×2 grid: topbar row + sidebar/panel row */
104
+ /* ── Grid container ──────────────────────────────────────────────────── */
105
+
106
+ /* 2-row, 2-column grid with animated sidebar column */
74
107
  [data-slot="dashboard-group"] {
75
108
  grid-template-rows: var(--topbar-height) 1fr;
76
109
  grid-template-columns: var(--sidebar-current-width) 1fr;
77
110
  transition: grid-template-columns var(--sidebar-duration) var(--ease-out-expo);
78
111
  }
79
112
 
80
- /* Topbar spans panel column only (sidebar owns full height) */
113
+ /* Topbar occupies column 2 only (sidebar spans both rows in column 1) */
81
114
  [data-slot="dashboard-navbar"] {
82
115
  grid-column: 2;
83
116
  }
84
117
 
85
- /*
86
- Sidebar: spans full height (both grid rows). background-color and
87
- border-right-color are set here (not via Tailwind arbitrary classes)
88
- because bg-[--css-var] classes don't generate reliably.
89
- */
118
+ /* Sidebar spans full height (row 1 through last row).
119
+ background-color and border-right-color reference sidebar-specific CSS
120
+ variables and are set here rather than via Tailwind arbitrary classes
121
+ because bg-(--css-var) classes in @theme are tree-shaken if not
122
+ referenced elsewhere. */
90
123
  [data-slot="dashboard-sidebar"] {
91
124
  grid-row: 1 / -1;
92
125
  background-color: var(--sidebar-background);
93
126
  border-right: 1px solid var(--sidebar-border);
94
127
  }
95
128
 
96
- /* Sidebar header: matches topbar height, bottom border */
129
+ /* Sidebar header aligns with the topbar height */
97
130
  [data-slot="dashboard-sidebar-header"] {
98
131
  height: var(--topbar-height);
99
132
  border-bottom: 1px solid var(--sidebar-border);
100
133
  }
101
134
 
102
- /* Sidebar footer: top border */
103
135
  [data-slot="dashboard-sidebar-footer"] {
104
136
  border-top: 1px solid var(--sidebar-border);
105
137
  }
106
138
 
107
- /* Inner nav: always sidebar-width, flex column for header/nav/footer layout */
139
+ /* Inner nav container: fixed at --sidebar-width so content doesn't reflow
140
+ during the grid column animation (the outer column shrinks, clipping
141
+ the overflow from this fixed-width inner element). */
108
142
  [data-slot="dashboard-sidebar-inner"] {
109
143
  width: var(--sidebar-width);
110
144
  height: 100%;
@@ -114,13 +148,16 @@
114
148
  color: var(--sidebar-foreground);
115
149
  }
116
150
 
117
- /* Panel: row 2, col 2 */
151
+ /* Main content panel: row 2, column 2 */
118
152
  [data-slot="dashboard-panel"] {
119
153
  grid-column: 2;
120
154
  grid-row: 2;
121
155
  }
122
156
 
123
- /* ── Navbar layout variant: navbar spans full width, sidebar below ────── */
157
+ /* ── "Navbar" layout variant ─────────────────────────────────────────────
158
+ Alternative layout where the navbar spans the full width (both columns)
159
+ and the sidebar sits below it instead of spanning full height. Activated
160
+ by data-layout="navbar" on the dashboard_group element. */
124
161
  [data-layout="navbar"] [data-slot="dashboard-navbar"] {
125
162
  grid-column: 1 / -1;
126
163
  }
@@ -128,16 +165,20 @@
128
165
  grid-row: auto;
129
166
  }
130
167
 
131
- /* Scrim: hidden on desktop */
168
+ /* Scrim overlay: hidden on desktop, shown on mobile when sidebar is open */
132
169
  [data-slot="dashboard-scrim"] { display: none; }
133
170
 
134
- /* ── Collapse button icon switching ────────────────────────────────────── */
171
+ /* ── Collapse button icon switching ──────────────────────────────────────
172
+ The sidebar collapse button shows different icons depending on whether
173
+ the sidebar is open or closed (e.g., chevron-left vs chevron-right). */
135
174
  [data-sidebar-open="true"] [data-slot="collapse-icon-open"] { display: inline; }
136
175
  [data-sidebar-open="true"] [data-slot="collapse-icon-closed"] { display: none; }
137
176
  [data-sidebar-open="false"] [data-slot="collapse-icon-open"] { display: none; }
138
177
  [data-sidebar-open="false"] [data-slot="collapse-icon-closed"] { display: inline; }
139
178
 
140
- /* ── Nav section (details/summary) ───────────────────────────────────── */
179
+ /* ── Nav section accordion (native <details>/<summary>) ─────────────────
180
+ Chevron rotates 180deg when the section is expanded. Uses CSS transition
181
+ rather than JS for smooth animation. */
141
182
  [data-slot="nav-section-chevron"] {
142
183
  transition: transform 200ms ease;
143
184
  }
@@ -145,7 +186,9 @@
145
186
  transform: rotate(180deg);
146
187
  }
147
188
 
148
- /* ── Nav item sidebar context ────────────────────────────────────────── */
189
+ /* ── Nav item hover/active states inside sidebar ────────────────────────
190
+ Nav items use the sidebar's own accent tokens (not the global accent
191
+ tokens) so the sidebar can have an independent color scheme. */
149
192
  [data-slot="dashboard-sidebar-inner"] [data-slot="nav-item"]:hover {
150
193
  background-color: var(--sidebar-accent);
151
194
  color: var(--sidebar-accent-foreground);
@@ -155,34 +198,44 @@
155
198
  color: var(--sidebar-accent-foreground);
156
199
  }
157
200
 
158
- /* ── Mobile: sidebar becomes a fixed full-width overlay ───────────────── */
201
+ /* ── Mobile (< md breakpoint): sidebar becomes a full-width overlay ────
202
+ On small screens, the sidebar column collapses to 0 in the grid and the
203
+ sidebar element switches to a fixed-position overlay that slides in from
204
+ the left. A semi-transparent scrim appears behind it. */
159
205
  @media (max-width: 767px) {
160
- /* Collapse sidebar column to 0 on mobile */
206
+ /* Remove the sidebar column from the grid on mobile.
207
+ --sidebar-mobile-width caps the sidebar so the scrim remains
208
+ visible and tappable for dismiss (#208). */
161
209
  [data-slot="dashboard-group"] {
210
+ --sidebar-mobile-width: min(var(--sidebar-width), 85dvw);
162
211
  grid-template-columns: 0 1fr;
163
212
  }
164
213
 
165
- /* Sidebar switches from grid column to fixed overlay */
214
+ /* Sidebar becomes a fixed overlay that slides in from the left */
166
215
  [data-slot="dashboard-sidebar"] {
167
216
  position: fixed;
168
217
  inset-block: 0;
169
218
  inset-inline-start: 0;
170
219
  z-index: var(--z-sidebar);
171
- width: 100dvw;
172
- transform: translateX(-100dvw);
220
+ width: var(--sidebar-mobile-width);
221
+ transform: translateX(-100%);
173
222
  transition: transform var(--sidebar-duration) var(--ease-out-expo);
174
223
  }
175
224
 
225
+ /* Slide sidebar into view when open */
176
226
  [data-sidebar-open="true"] [data-slot="dashboard-sidebar"] {
177
227
  transform: translateX(0);
178
228
  }
179
229
 
180
- /* Inner nav fills the full-width sidebar on mobile */
230
+ /* Inner nav matches mobile sidebar width */
181
231
  [data-slot="dashboard-sidebar-inner"] {
182
- width: 100dvw;
232
+ width: var(--sidebar-mobile-width);
183
233
  }
184
234
 
185
- /* Scrim: appears behind the sidebar overlay on mobile */
235
+ /* Semi-transparent scrim behind the open sidebar overlay.
236
+ z-index is one below the sidebar so it sits behind the sidebar
237
+ but above the main content. Clicking the scrim closes the sidebar
238
+ (handled by the kiso--sidebar Stimulus controller). */
186
239
  [data-sidebar-open="true"] [data-slot="dashboard-scrim"] {
187
240
  display: block;
188
241
  position: fixed;
@@ -1,6 +1,26 @@
1
- /* Dialog and Alert Dialog entry/exit animations and scroll lock. */
1
+ /* ── Dialog & Alert Dialog ────────────────────────────────────────────────────
2
+ Entry/exit animations, scroll lock, and reduced-motion support for both
3
+ Dialog and Alert Dialog components.
2
4
 
3
- /* Entry */
5
+ Why CSS instead of ERB?
6
+ Both components use the native <dialog> element with showModal(). Animations
7
+ on open/close, the ::backdrop fade, and scroll locking on the <html> element
8
+ require CSS — they cannot be expressed as Tailwind utility classes.
9
+
10
+ Animation lifecycle (managed by the kiso--dialog Stimulus controller):
11
+ 1. Open: controller calls dialog.showModal() → browser sets [open] →
12
+ CSS triggers fade-in + scale-up on both backdrop and content.
13
+ 2. Close: controller sets data-state="closing" → CSS triggers fade-out +
14
+ scale-down with `forwards` fill → animationend fires → controller
15
+ calls dialog.close() and removes data-state.
16
+
17
+ Selectors use :is() to target both dialog and alert-dialog with shared
18
+ rules, avoiding duplication.
19
+ ──────────────────────────────────────────────────────────────────────────── */
20
+
21
+ /* ── Entry animations ──────────────────────────────────────────────────────
22
+ Triggered by the native [open] attribute when showModal() is called.
23
+ Backdrop fades in; content fades in and scales up from 95%. */
4
24
  :is(dialog[data-slot="dialog"], dialog[data-slot="alert-dialog"])[open] {
5
25
  animation: kiso-dialog-backdrop-in 200ms ease-out;
6
26
  }
@@ -8,7 +28,10 @@
8
28
  animation: kiso-dialog-content-in 200ms ease-out;
9
29
  }
10
30
 
11
- /* Exit data-state="closing" set by Stimulus before calling .close() */
31
+ /* ── Exit animations ──────────────────────────────────────────────────────
32
+ Triggered by data-state="closing" which the Stimulus controller sets
33
+ before calling .close(). Uses `forwards` fill mode to hold the final
34
+ frame (opacity: 0) until the controller removes the dialog from view. */
12
35
  :is(dialog[data-slot="dialog"], dialog[data-slot="alert-dialog"])[data-state="closing"] {
13
36
  animation: kiso-dialog-backdrop-out 200ms ease-in forwards;
14
37
  }
@@ -16,12 +39,18 @@
16
39
  animation: kiso-dialog-content-out 200ms ease-in forwards;
17
40
  }
18
41
 
42
+ /* ── Keyframes ─────────────────────────────────────────────────────────── */
43
+
44
+ /* Backdrop: simple opacity fade */
19
45
  @keyframes kiso-dialog-backdrop-in {
20
46
  from { opacity: 0; }
21
47
  }
22
48
  @keyframes kiso-dialog-backdrop-out {
23
49
  to { opacity: 0; }
24
50
  }
51
+
52
+ /* Content: opacity fade combined with a subtle scale transform (95% → 100%
53
+ on entry, 100% → 95% on exit) for a "zoom in" feel. */
25
54
  @keyframes kiso-dialog-content-in {
26
55
  from { opacity: 0; transform: scale(0.95); }
27
56
  }
@@ -29,13 +58,18 @@
29
58
  to { opacity: 0; transform: scale(0.95); }
30
59
  }
31
60
 
32
- /* Scroll lock — prevent body scroll when modal dialog is open */
61
+ /* ── Scroll lock ──────────────────────────────────────────────────────────
62
+ Prevent background scrolling when a modal dialog is open. The :modal
63
+ pseudo-class ensures this only applies to dialogs opened with showModal()
64
+ (not dialog.show()). scrollbar-gutter: stable prevents layout shift when
65
+ the scrollbar disappears. */
33
66
  html:has(:is(dialog[data-slot="dialog"], dialog[data-slot="alert-dialog"])[open]:modal) {
34
67
  overflow: hidden;
35
68
  scrollbar-gutter: stable;
36
69
  }
37
70
 
38
- /* Respect reduced motion */
71
+ /* ── Reduced motion ───────────────────────────────────────────────────────
72
+ Disable all dialog animations for users who prefer reduced motion. */
39
73
  @media (prefers-reduced-motion: reduce) {
40
74
  :is(dialog[data-slot="dialog"], dialog[data-slot="alert-dialog"])[open],
41
75
  :is(dialog[data-slot="dialog"], dialog[data-slot="alert-dialog"])[open] > :is([data-slot="dialog-content"], [data-slot="alert-dialog-content"]),