maquina-components 0.2.0 → 0.3.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +77 -0
  3. data/app/assets/stylesheets/calendar.css +222 -0
  4. data/app/assets/stylesheets/combobox.css +218 -0
  5. data/app/assets/stylesheets/date_picker.css +172 -0
  6. data/app/assets/stylesheets/toast.css +433 -0
  7. data/app/assets/tailwind/maquina_components_engine/engine.css +16 -14
  8. data/app/helpers/maquina_components/calendar_helper.rb +196 -0
  9. data/app/helpers/maquina_components/combobox_helper.rb +300 -0
  10. data/app/helpers/maquina_components/icons_helper.rb +220 -0
  11. data/app/helpers/maquina_components/table_helper.rb +9 -10
  12. data/app/helpers/maquina_components/toast_helper.rb +115 -0
  13. data/app/javascript/controllers/calendar_controller.js +394 -0
  14. data/app/javascript/controllers/combobox_controller.js +325 -0
  15. data/app/javascript/controllers/date_picker_controller.js +261 -0
  16. data/app/javascript/controllers/toast_controller.js +115 -0
  17. data/app/javascript/controllers/toaster_controller.js +226 -0
  18. data/app/views/components/_calendar.html.erb +121 -0
  19. data/app/views/components/_combobox.html.erb +13 -0
  20. data/app/views/components/_date_picker.html.erb +102 -0
  21. data/app/views/components/_toast.html.erb +53 -0
  22. data/app/views/components/_toaster.html.erb +17 -0
  23. data/app/views/components/calendar/_header.html.erb +22 -0
  24. data/app/views/components/calendar/_week.html.erb +53 -0
  25. data/app/views/components/combobox/_content.html.erb +17 -0
  26. data/app/views/components/combobox/_empty.html.erb +9 -0
  27. data/app/views/components/combobox/_group.html.erb +8 -0
  28. data/app/views/components/combobox/_input.html.erb +18 -0
  29. data/app/views/components/combobox/_label.html.erb +8 -0
  30. data/app/views/components/combobox/_list.html.erb +8 -0
  31. data/app/views/components/combobox/_option.html.erb +24 -0
  32. data/app/views/components/combobox/_separator.html.erb +6 -0
  33. data/app/views/components/combobox/_trigger.html.erb +22 -0
  34. data/app/views/components/toast/_action.html.erb +14 -0
  35. data/app/views/components/toast/_description.html.erb +8 -0
  36. data/app/views/components/toast/_title.html.erb +8 -0
  37. data/lib/maquina_components/version.rb +1 -1
  38. metadata +33 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdbbf854a700d7b0fa55c3b91f1975d7fd53282ddeb0a14db55011d055db96a3
4
- data.tar.gz: 1b69e2f23f390efa573e3192410125e8945ec182001ca8f2db431fee72523efc
3
+ metadata.gz: 1449d3d6edf1341e73d321b0821c1e3e355659022865465949ebf2202426d7b5
4
+ data.tar.gz: ffbe3396ad981086e0979756e401942410a66e2257db71a0e21a3d7a674658ed
5
5
  SHA512:
6
- metadata.gz: 07a7b92b85cdaf2987c2a816133bd174e1d838ff9af4ce16166ea218729c0896f7f4bbe71c7c8caaa3f175196dbe097d6f426c28db96e0cdd703054a788ba4f9
7
- data.tar.gz: 53c9a63b128ed2cef878c3935185ed559d3e01274f1354ab34f438926a8e06cdac3980eabdd4c962e9a02765f7102c1694a106c9775cc05ce7f7358ed89add10
6
+ metadata.gz: ec918743d27c761eb32bdce4c3a7f79e02668ea649c6ee3cdf676408c8666866e5d1dddeb72ec725d8a1a0f738273091553d75214cadf0a3c18a1c6932795131
7
+ data.tar.gz: 2ba3990b4ed9dfb77e03bf5f9734a355f48ee9a3903afb41cd1b8e00c5f022a17b3db790e774a9d4868b8d8bd663969d278f24fc5678441c37579662d693930f
data/README.md CHANGED
@@ -152,8 +152,17 @@ bin/rails generate maquina_components:install --skip-helper
152
152
 
153
153
  | Component | Description | Documentation |
154
154
  |-----------|-------------|---------------|
155
+ | **Calendar** | Inline date picker with single/range selection | [Calendar](https://maquina.app/documentation/components/calendar/) |
156
+ | **Combobox** | Searchable dropdown with keyboard navigation | [Combobox](https://maquina.app/documentation/components/combobox/) |
157
+ | **Date Picker** | Popover-based date selection | [Date Picker](https://maquina.app/documentation/components/date-picker/) |
155
158
  | **Toggle Group** | Single/multiple selection button group | [Toggle Group](https://maquina.app/documentation/components/toggle-group/) |
156
159
 
160
+ ### Feedback Components
161
+
162
+ | Component | Description | Documentation |
163
+ |-----------|-------------|---------------|
164
+ | **Toast** | Non-intrusive notifications with auto-dismiss | [Toast](https://maquina.app/documentation/components/toast/) |
165
+
157
166
  ### Form Components
158
167
 
159
168
  | Component | Data Attribute | Variants |
@@ -252,6 +261,41 @@ bin/rails generate maquina_components:install --skip-helper
252
261
  <%= pagination_nav(@pagy, :users_path) %>
253
262
  ```
254
263
 
264
+ ### Combobox
265
+
266
+ ```erb
267
+ <%= render "components/combobox", placeholder: "Select framework..." do |combobox_id| %>
268
+ <%= render "components/combobox/trigger", for_id: combobox_id, placeholder: "Select framework..." %>
269
+ <%= render "components/combobox/content", id: combobox_id do %>
270
+ <%= render "components/combobox/input", placeholder: "Search..." %>
271
+ <%= render "components/combobox/list" do %>
272
+ <%= render "components/combobox/option", value: "rails" do %>Ruby on Rails<% end %>
273
+ <%= render "components/combobox/option", value: "hanami" do %>Hanami<% end %>
274
+ <%= render "components/combobox/option", value: "sinatra" do %>Sinatra<% end %>
275
+ <% end %>
276
+ <%= render "components/combobox/empty" %>
277
+ <% end %>
278
+ <% end %>
279
+ ```
280
+
281
+ ### Toast Notifications
282
+
283
+ ```erb
284
+ <%# Add toaster to your layout %>
285
+ <%= render "components/toaster", position: :bottom_right do %>
286
+ <%= toast_flash_messages %>
287
+ <% end %>
288
+
289
+ <%# In your controller %>
290
+ flash[:success] = "Changes saved successfully!"
291
+
292
+ <%# Or use the JavaScript API %>
293
+ <script>
294
+ Toast.success("Profile updated!")
295
+ Toast.error("Something went wrong", { description: "Please try again." })
296
+ </script>
297
+ ```
298
+
255
299
  ### Tables
256
300
 
257
301
  ```erb
@@ -349,6 +393,10 @@ The install generator adds default theme variables. Customize them in `app/asset
349
393
  | `sidebar_open?(cookie_name)` | Check if the sidebar is expanded |
350
394
  | `pagination_nav(pagy, route)` | Render pagination from Pagy object |
351
395
  | `pagination_simple(pagy, route)` | Render simple Previous/Next pagination |
396
+ | `toast_flash_messages` | Render all flash messages as toasts |
397
+ | `toast(variant, title, **options)` | Render a single toast notification |
398
+ | `combobox(placeholder:, **options, &block)` | Builder pattern for combobox |
399
+ | `combobox_simple(options:, **options)` | Data-driven simple combobox |
352
400
 
353
401
  ---
354
402
 
@@ -379,8 +427,15 @@ The install generator adds default theme variables. Customize them in `app/asset
379
427
 
380
428
  ### Interactive
381
429
 
430
+ - **[Calendar](https://maquina.app/documentation/components/calendar/)** — Inline date picker
431
+ - **[Combobox](https://maquina.app/documentation/components/combobox/)** — Searchable dropdown selection
432
+ - **[Date Picker](https://maquina.app/documentation/components/date-picker/)** — Popover date selection
382
433
  - **[Toggle Group](https://maquina.app/documentation/components/toggle-group/)** — Toggle button groups
383
434
 
435
+ ### Feedback
436
+
437
+ - **[Toast](https://maquina.app/documentation/components/toast/)** — Toast notifications
438
+
384
439
  ### Forms
385
440
 
386
441
  - **[Form Components](https://maquina.app/documentation/components/form/)** — Buttons, inputs, and form styling
@@ -404,6 +459,28 @@ bin/rails test
404
459
 
405
460
  ---
406
461
 
462
+ ## Claude Code Skill
463
+
464
+ This repository includes a Claude Code skill that teaches Claude how to build consistent, accessible UIs using maquina_components. The skill provides:
465
+
466
+ - **Component catalog** — Complete reference for all components with ERB examples
467
+ - **Form patterns** — Validation, error handling, and complex form structures
468
+ - **Layout patterns** — Sidebar navigation, page structure, responsive design
469
+ - **Turbo integration** — Turbo Frames, Streams, and component updates
470
+ - **Spec checklist** — Review criteria for UI implementation quality
471
+
472
+ ### Installation
473
+
474
+ Copy the `skill/` directory to your Rails project:
475
+
476
+ ```bash
477
+ cp -r /path/to/maquina_components/skill .claude/skills/maquina-ui-standards
478
+ ```
479
+
480
+ See the [Skill README](skill/README.md) for detailed installation and usage instructions.
481
+
482
+ ---
483
+
407
484
  ## Contributing
408
485
 
409
486
  Bug reports and pull requests are welcome on GitHub at [github.com/maquina-app/maquina_components](https://github.com/maquina-app/maquina_components).
@@ -0,0 +1,222 @@
1
+ /* ===== Calendar Component Styles ===== */
2
+ /*
3
+ * A date picker calendar with single and range selection.
4
+ * Uses data attributes for styling to avoid inline utility classes.
5
+ * Fully compatible with dark mode via CSS variables.
6
+ *
7
+ * Structure:
8
+ * - calendar (root container)
9
+ * - header (navigation)
10
+ * - weekdays (day name headers)
11
+ * - grid (day buttons grid)
12
+ * - week (row)
13
+ * - day (button)
14
+ */
15
+
16
+ /* ===== Root Container ===== */
17
+ [data-component="calendar"] {
18
+ --cell-size: 2rem;
19
+
20
+ @apply p-3 w-fit rounded-lg border;
21
+ background-color: var(--background);
22
+ border-color: var(--border);
23
+ }
24
+
25
+ /* ===== Header (Navigation) ===== */
26
+ [data-calendar-part="header"] {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ @apply mb-4;
31
+ }
32
+
33
+ [data-calendar-part="header"] button {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ width: var(--cell-size);
38
+ height: var(--cell-size);
39
+ @apply rounded-md;
40
+ border: none;
41
+ cursor: pointer;
42
+ background-color: transparent;
43
+ color: var(--foreground);
44
+ @apply transition-colors duration-150;
45
+ }
46
+
47
+ [data-calendar-part="header"] button:hover:not(:disabled) {
48
+ background-color: var(--accent);
49
+ color: var(--accent-foreground);
50
+ }
51
+
52
+ [data-calendar-part="header"] button:focus-visible {
53
+ @apply outline-none;
54
+ box-shadow: 0 0 0 2px var(--background),
55
+ 0 0 0 4px var(--ring);
56
+ }
57
+
58
+ [data-calendar-part="header"] button:disabled {
59
+ @apply opacity-50 cursor-not-allowed;
60
+ }
61
+
62
+ [data-calendar-part="header"] button svg {
63
+ @apply size-4 shrink-0;
64
+ }
65
+
66
+ [data-calendar-part="caption"] {
67
+ @apply text-sm font-medium select-none;
68
+ color: var(--foreground);
69
+ }
70
+
71
+ /* ===== Weekday Headers ===== */
72
+ [data-calendar-part="weekdays"] {
73
+ display: grid;
74
+ grid-template-columns: repeat(7, var(--cell-size));
75
+ @apply mb-1;
76
+ }
77
+
78
+ [data-calendar-part="weekday"] {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ width: var(--cell-size);
83
+ height: var(--cell-size);
84
+ @apply text-xs font-normal select-none;
85
+ color: var(--muted-foreground);
86
+ }
87
+
88
+ /* ===== Calendar Grid ===== */
89
+ [data-calendar-part="grid"] {
90
+ display: flex;
91
+ flex-direction: column;
92
+ @apply gap-1;
93
+ }
94
+
95
+ [data-calendar-part="week"] {
96
+ display: grid;
97
+ grid-template-columns: repeat(7, var(--cell-size));
98
+ }
99
+
100
+ /* ===== Day Button Base ===== */
101
+ [data-calendar-part="day"] {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ width: var(--cell-size);
106
+ height: var(--cell-size);
107
+ @apply text-sm font-normal rounded-md;
108
+ border: none;
109
+ cursor: pointer;
110
+ background-color: transparent;
111
+ color: var(--foreground);
112
+ @apply transition-colors duration-150;
113
+ }
114
+
115
+ /* ===== Day States ===== */
116
+
117
+ /* Hover */
118
+ [data-calendar-part="day"]:hover:not(:disabled):not([data-state]) {
119
+ background-color: var(--accent);
120
+ color: var(--accent-foreground);
121
+ }
122
+
123
+ /* Today */
124
+ [data-calendar-part="day"][data-today] {
125
+ background-color: var(--accent);
126
+ color: var(--accent-foreground);
127
+ }
128
+
129
+ /* Selected (single mode) */
130
+ [data-calendar-part="day"][data-state="selected"] {
131
+ background-color: var(--primary);
132
+ color: var(--primary-foreground);
133
+ @apply rounded-md;
134
+ }
135
+
136
+ [data-calendar-part="day"][data-state="selected"]:hover {
137
+ background-color: var(--primary);
138
+ color: var(--primary-foreground);
139
+ }
140
+
141
+ /* Range Start */
142
+ [data-calendar-part="day"][data-state="range-start"] {
143
+ background-color: var(--primary);
144
+ color: var(--primary-foreground);
145
+ @apply rounded-l-md rounded-r-none;
146
+ }
147
+
148
+ /* Range End */
149
+ [data-calendar-part="day"][data-state="range-end"] {
150
+ background-color: var(--primary);
151
+ color: var(--primary-foreground);
152
+ @apply rounded-r-md rounded-l-none;
153
+ }
154
+
155
+ /* Range Middle */
156
+ [data-calendar-part="day"][data-state="range-middle"] {
157
+ background-color: var(--accent);
158
+ color: var(--accent-foreground);
159
+ @apply rounded-none;
160
+ }
161
+
162
+ /* Today within selection - override background */
163
+ [data-calendar-part="day"][data-today][data-state="selected"],
164
+ [data-calendar-part="day"][data-today][data-state="range-start"],
165
+ [data-calendar-part="day"][data-today][data-state="range-end"] {
166
+ background-color: var(--primary);
167
+ color: var(--primary-foreground);
168
+ }
169
+
170
+ [data-calendar-part="day"][data-today][data-state="range-middle"] {
171
+ background-color: var(--accent);
172
+ color: var(--accent-foreground);
173
+ }
174
+
175
+ /* Outside days (previous/next month) */
176
+ [data-calendar-part="day"][data-outside] {
177
+ color: var(--muted-foreground);
178
+ @apply opacity-50;
179
+ }
180
+
181
+ [data-calendar-part="day"][data-outside][data-state="selected"],
182
+ [data-calendar-part="day"][data-outside][data-state="range-start"],
183
+ [data-calendar-part="day"][data-outside][data-state="range-end"] {
184
+ color: var(--primary-foreground);
185
+ @apply opacity-30;
186
+ }
187
+
188
+ [data-calendar-part="day"][data-outside][data-state="range-middle"] {
189
+ color: var(--accent-foreground);
190
+ @apply opacity-30;
191
+ }
192
+
193
+ /* Disabled */
194
+ [data-calendar-part="day"]:disabled {
195
+ color: var(--muted-foreground);
196
+ @apply opacity-50 cursor-not-allowed;
197
+ }
198
+
199
+ /* Focus */
200
+ [data-calendar-part="day"]:focus-visible {
201
+ @apply outline-none;
202
+ position: relative;
203
+ z-index: 10;
204
+ box-shadow: 0 0 0 2px var(--background),
205
+ 0 0 0 4px var(--ring);
206
+ }
207
+
208
+ /* ===== Responsive Cell Sizes ===== */
209
+ /*
210
+ * Custom cell sizes can be set via --cell-size CSS variable:
211
+ * style="--cell-size: 2.5rem;"
212
+ *
213
+ * Or with Tailwind classes:
214
+ * css_classes: "[--cell-size:2.5rem] md:[--cell-size:3rem]"
215
+ */
216
+
217
+ /* ===== Dark Mode ===== */
218
+ /*
219
+ * Dark mode is handled automatically through CSS variables.
220
+ * The theme variables change based on the .dark class on html/body.
221
+ * No additional dark mode styles needed here.
222
+ */
@@ -0,0 +1,218 @@
1
+ /* ===== Combobox Component Styles ===== */
2
+ /*
3
+ * Combobox component for autocomplete/search with selection.
4
+ * Uses HTML5 Popover API for positioning and light-dismiss.
5
+ * Uses data attributes for styling to avoid inline utility classes.
6
+ * Fully compatible with dark mode via CSS variables.
7
+ *
8
+ * Structure:
9
+ * - combobox (root with Stimulus controller)
10
+ * - trigger (button that opens popover)
11
+ * - content (popover container)
12
+ * - input (search field)
13
+ * - list (scrollable options)
14
+ * - option (selectable item)
15
+ * - group (logical grouping)
16
+ * - label (section heading)
17
+ * - separator (divider)
18
+ * - empty (no results message)
19
+ */
20
+
21
+ /* ===== Root Container ===== */
22
+ [data-component="combobox"] {
23
+ position: relative;
24
+ display: inline-block;
25
+ }
26
+
27
+ /* ===== Trigger Button ===== */
28
+ /* Trigger uses button component styles via data-component="button" */
29
+ [data-combobox-part="trigger"] {
30
+ @apply justify-between gap-2;
31
+ min-width: 200px;
32
+ }
33
+
34
+ [data-combobox-part="trigger"] [data-combobox-target="label"] {
35
+ @apply truncate text-left flex-1;
36
+ }
37
+
38
+ /* Placeholder state */
39
+ [data-combobox-part="trigger"][data-has-value="false"] [data-combobox-target="label"] {
40
+ color: var(--muted-foreground);
41
+ }
42
+
43
+ /* Icon styling */
44
+ [data-combobox-part="trigger"] > svg {
45
+ @apply size-4 shrink-0 opacity-50;
46
+ }
47
+
48
+ /* ===== Content (Popover) ===== */
49
+ [data-combobox-part="content"] {
50
+ background-color: var(--popover, var(--background));
51
+ color: var(--popover-foreground, var(--foreground));
52
+ border-color: var(--border);
53
+ @apply rounded-md border p-0 shadow-md outline-none;
54
+ @apply min-w-[200px];
55
+
56
+ /* Reset popover defaults - positioning handled by JS */
57
+ margin: 0;
58
+ padding: 0;
59
+ inset: unset;
60
+ }
61
+
62
+ /* Width variants */
63
+ [data-combobox-part="content"][data-width="sm"] {
64
+ @apply w-40;
65
+ }
66
+
67
+ [data-combobox-part="content"][data-width="default"] {
68
+ @apply w-[200px];
69
+ }
70
+
71
+ [data-combobox-part="content"][data-width="md"] {
72
+ @apply w-56;
73
+ }
74
+
75
+ [data-combobox-part="content"][data-width="lg"] {
76
+ @apply w-72;
77
+ }
78
+
79
+ [data-combobox-part="content"][data-width="full"] {
80
+ @apply w-full;
81
+ }
82
+
83
+ /* Alignment is handled by JavaScript for proper popover positioning */
84
+
85
+ /* Animation */
86
+ [data-combobox-part="content"]:popover-open {
87
+ animation: combobox-in 150ms ease-out;
88
+ }
89
+
90
+ @keyframes combobox-in {
91
+ from {
92
+ opacity: 0;
93
+ transform: scale(0.95) translateY(-4px);
94
+ }
95
+ to {
96
+ opacity: 1;
97
+ transform: scale(1) translateY(0);
98
+ }
99
+ }
100
+
101
+ /* ===== Search Input ===== */
102
+ [data-combobox-part="input-wrapper"] {
103
+ display: flex;
104
+ align-items: center;
105
+ border-bottom: 1px solid var(--border);
106
+ @apply px-3 gap-2;
107
+ }
108
+
109
+ [data-combobox-part="input-wrapper"] > svg {
110
+ @apply size-4 shrink-0 opacity-50;
111
+ }
112
+
113
+ [data-combobox-part="input"] {
114
+ background-color: transparent;
115
+ color: var(--foreground);
116
+ @apply flex h-10 w-full py-3 text-sm outline-none;
117
+ @apply placeholder:text-muted-foreground;
118
+ @apply disabled:cursor-not-allowed disabled:opacity-50;
119
+ }
120
+
121
+ /* ===== Options List ===== */
122
+ [data-combobox-part="list"] {
123
+ @apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1;
124
+ }
125
+
126
+ /* ===== Option Item ===== */
127
+ [data-combobox-part="option"] {
128
+ position: relative;
129
+ display: flex;
130
+ align-items: center;
131
+ cursor: default;
132
+ user-select: none;
133
+ color: var(--popover-foreground, var(--foreground));
134
+ @apply gap-2 rounded-sm py-1.5 text-sm outline-none transition-colors;
135
+ @apply pl-8 pr-2; /* Space for check icon on left */
136
+ }
137
+
138
+ /* Hover state */
139
+ [data-combobox-part="option"]:hover:not([aria-disabled="true"]) {
140
+ background-color: var(--accent);
141
+ color: var(--accent-foreground);
142
+ cursor: pointer;
143
+ }
144
+
145
+ /* Focus state (keyboard navigation) */
146
+ [data-combobox-part="option"]:focus {
147
+ background-color: var(--accent);
148
+ color: var(--accent-foreground);
149
+ }
150
+
151
+ /* Selected state */
152
+ [data-combobox-part="option"][data-selected="true"] {
153
+ background-color: var(--accent);
154
+ color: var(--accent-foreground);
155
+ }
156
+
157
+ /* Disabled state */
158
+ [data-combobox-part="option"][aria-disabled="true"] {
159
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
160
+ }
161
+
162
+ /* Hidden state (filtered out) */
163
+ [data-combobox-part="option"][hidden] {
164
+ display: none;
165
+ }
166
+
167
+ /* Check indicator */
168
+ [data-combobox-part="check"] {
169
+ position: absolute;
170
+ left: 8px;
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ @apply size-4;
175
+ }
176
+
177
+ [data-combobox-part="check"].invisible {
178
+ visibility: hidden;
179
+ }
180
+
181
+ [data-combobox-part="check"] svg {
182
+ @apply size-4;
183
+ }
184
+
185
+ /* ===== Empty State ===== */
186
+ [data-combobox-part="empty"] {
187
+ @apply py-6 text-center text-sm;
188
+ color: var(--muted-foreground);
189
+ }
190
+
191
+ [data-combobox-part="empty"][hidden] {
192
+ display: none;
193
+ }
194
+
195
+ /* ===== Group ===== */
196
+ [data-combobox-part="group"] {
197
+ display: flex;
198
+ flex-direction: column;
199
+ }
200
+
201
+ /* ===== Label ===== */
202
+ [data-combobox-part="label"] {
203
+ @apply px-2 py-1.5 text-xs font-medium;
204
+ color: var(--muted-foreground);
205
+ }
206
+
207
+ /* ===== Separator ===== */
208
+ [data-combobox-part="separator"] {
209
+ @apply -mx-1 my-1 h-px;
210
+ background-color: var(--border);
211
+ }
212
+
213
+ /* ===== Dark Mode ===== */
214
+ /*
215
+ * Dark mode is handled automatically through CSS variables.
216
+ * The theme variables change based on the .dark class on html/body.
217
+ * No additional dark mode styles needed here.
218
+ */