@duskmoon-dev/core 1.10.0 → 1.10.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @duskmoon-dev/core
2
2
 
3
- > DuskMoonUI - A Tailwind CSS v4 plugin with Material Design 3 color system and 42 component styles
3
+ > DuskMoonUI - A Tailwind CSS v4 plugin with Material Design 3 color system and 49 component styles
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@duskmoon-dev/core.svg)](https://www.npmjs.com/package/@duskmoon-dev/core)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@duskmoon-dev/core.svg)](https://www.npmjs.com/package/@duskmoon-dev/core)
@@ -12,7 +12,7 @@
12
12
  - 🎨 **Three-color system** - Primary, secondary, and tertiary brand colors with automatic content colors
13
13
  - 🌓 **Built-in themes** - Sunshine (light) and Moonlight (dark) themes ready to use
14
14
  - 📦 **55+ Material Design 3 color tokens** - Full MD3 color system with OKLCH format
15
- - 🧩 **42 UI components** - Complete component library with consistent styling
15
+ - 🧩 **49 UI components** - Complete component library with consistent styling
16
16
  - 🚀 **Zero runtime JS** - Pure CSS with CSS custom properties for theme switching
17
17
  - ♿ **Accessible by default** - WCAG AA compliant contrast ratios (4.5:1 minimum)
18
18
  - 🎯 **Tailwind v4 native** - Pure CSS using `@import`, no JavaScript configuration
@@ -96,7 +96,7 @@ Theme switching is instant and uses pure CSS custom properties.
96
96
 
97
97
  ## Components
98
98
 
99
- @duskmoon-dev/core includes **42 components** organized into 7 categories:
99
+ @duskmoon-dev/core includes **49 components** organized into 7 categories:
100
100
 
101
101
  ### Core Components (6)
102
102
 
@@ -8355,6 +8355,287 @@
8355
8355
  }
8356
8356
  }
8357
8357
 
8358
+ /**
8359
+ * Theme Controller Component Styles
8360
+ * DuskMoonUI - Two display modes for theme switching:
8361
+ *
8362
+ * 1. Switch (.theme-controller) — inline pill-shaped radio group, all options visible
8363
+ * 2. Dropdown (.theme-controller-dropdown) — <details>/<summary> with floating menu
8364
+ *
8365
+ * Both modes reuse .theme-controller-item (hidden radio) and .theme-controller-label
8366
+ * for CSS-only active state via :checked + label.
8367
+ */
8368
+
8369
+ @layer components {
8370
+ /* ========================================
8371
+ * SHARED — Radio input & label (used in both modes)
8372
+ * ======================================== */
8373
+
8374
+ /* Hidden Radio Input — visually hidden but accessible */
8375
+ .theme-controller-item {
8376
+ position: absolute;
8377
+ width: 1px;
8378
+ height: 1px;
8379
+ padding: 0;
8380
+ margin: -1px;
8381
+ overflow: hidden;
8382
+ clip: rect(0, 0, 0, 0);
8383
+ white-space: nowrap;
8384
+ border: 0;
8385
+ }
8386
+
8387
+ /* Visible Label — the clickable option */
8388
+ .theme-controller-label {
8389
+ display: inline-flex;
8390
+ align-items: center;
8391
+ justify-content: center;
8392
+ gap: 0.5rem;
8393
+ padding: 0.5rem 1rem;
8394
+ font-size: 0.875rem;
8395
+ font-weight: 500;
8396
+ line-height: 1.25rem;
8397
+ color: var(--color-on-surface-variant);
8398
+ background-color: transparent;
8399
+ border-radius: calc(var(--radius-box, 1.5rem) - 0.25rem);
8400
+ cursor: pointer;
8401
+ transition: background-color 150ms ease-in-out, color 150ms ease-in-out,
8402
+ box-shadow 150ms ease-in-out;
8403
+ user-select: none;
8404
+ white-space: nowrap;
8405
+ }
8406
+
8407
+ /* Hover state */
8408
+ .theme-controller-label:hover {
8409
+ background-color: var(--color-surface-container-high);
8410
+ }
8411
+
8412
+ /* Focus-visible ring on the label when its radio is focused */
8413
+ .theme-controller-item:focus-visible + .theme-controller-label {
8414
+ outline: 2px solid var(--color-primary);
8415
+ outline-offset: 2px;
8416
+ }
8417
+
8418
+ /* Active/checked state */
8419
+ .theme-controller-item:checked + .theme-controller-label {
8420
+ background-color: var(--color-primary-container);
8421
+ color: var(--color-on-primary-container);
8422
+ box-shadow: var(--shadow-xs);
8423
+ }
8424
+
8425
+ .theme-controller-item:checked + .theme-controller-label:hover {
8426
+ background-color: color-mix(in oklch, var(--color-primary-container), black 5%);
8427
+ }
8428
+
8429
+ /* ========================================
8430
+ * SWITCH MODE — inline pill radio group
8431
+ * ======================================== */
8432
+
8433
+ .theme-controller {
8434
+ display: inline-flex;
8435
+ align-items: stretch;
8436
+ background-color: var(--color-surface-container);
8437
+ border: 1px solid var(--color-outline-variant);
8438
+ border-radius: var(--radius-box, 1.5rem);
8439
+ padding: 0.25rem;
8440
+ gap: 0.125rem;
8441
+ }
8442
+
8443
+ /* ── Switch Size Variants ── */
8444
+
8445
+ .theme-controller-sm {
8446
+ padding: 0.125rem;
8447
+ border-radius: calc(var(--radius-box, 1.5rem) * 0.75);
8448
+ }
8449
+
8450
+ .theme-controller-sm .theme-controller-label {
8451
+ padding: 0.375rem 0.75rem;
8452
+ font-size: 0.75rem;
8453
+ line-height: 1rem;
8454
+ border-radius: calc(var(--radius-box, 1.5rem) * 0.75 - 0.125rem);
8455
+ }
8456
+
8457
+ .theme-controller-lg {
8458
+ padding: 0.375rem;
8459
+ gap: 0.25rem;
8460
+ border-radius: calc(var(--radius-box, 1.5rem) * 1.25);
8461
+ }
8462
+
8463
+ .theme-controller-lg .theme-controller-label {
8464
+ padding: 0.75rem 1.5rem;
8465
+ font-size: 1rem;
8466
+ line-height: 1.5rem;
8467
+ border-radius: calc(var(--radius-box, 1.5rem) * 1.25 - 0.375rem);
8468
+ }
8469
+
8470
+ /* ── Switch Icon-only Variant ── */
8471
+
8472
+ .theme-controller-icon .theme-controller-label {
8473
+ padding: 0.5rem;
8474
+ }
8475
+
8476
+ .theme-controller-icon.theme-controller-sm .theme-controller-label {
8477
+ padding: 0.375rem;
8478
+ }
8479
+
8480
+ .theme-controller-icon.theme-controller-lg .theme-controller-label {
8481
+ padding: 0.75rem;
8482
+ }
8483
+
8484
+ /* ========================================
8485
+ * DROPDOWN MODE — <details>/<summary> with floating menu
8486
+ * ======================================== */
8487
+
8488
+ .theme-controller-dropdown {
8489
+ position: relative;
8490
+ display: inline-block;
8491
+ }
8492
+
8493
+ /* Trigger button (<summary>) */
8494
+ .theme-controller-trigger {
8495
+ display: inline-flex;
8496
+ align-items: center;
8497
+ gap: 0.5rem;
8498
+ padding: 0.5rem 0.75rem;
8499
+ font-size: 0.875rem;
8500
+ font-weight: 500;
8501
+ line-height: 1.25rem;
8502
+ color: var(--color-on-surface);
8503
+ background-color: var(--color-surface-container);
8504
+ border: 1px solid var(--color-outline-variant);
8505
+ border-radius: var(--radius-field, 0.5rem);
8506
+ cursor: pointer;
8507
+ user-select: none;
8508
+ transition: background-color 150ms ease-in-out;
8509
+ list-style: none;
8510
+ }
8511
+
8512
+ .theme-controller-trigger::-webkit-details-marker {
8513
+ display: none;
8514
+ }
8515
+
8516
+ .theme-controller-trigger:hover {
8517
+ background-color: var(--color-surface-container-high);
8518
+ }
8519
+
8520
+ .theme-controller-trigger:focus-visible {
8521
+ outline: 2px solid var(--color-primary);
8522
+ outline-offset: 2px;
8523
+ }
8524
+
8525
+ /* Chevron indicator */
8526
+ .theme-controller-trigger::after {
8527
+ content: '';
8528
+ display: inline-block;
8529
+ width: 0.5rem;
8530
+ height: 0.5rem;
8531
+ border-right: 2px solid currentColor;
8532
+ border-bottom: 2px solid currentColor;
8533
+ transform: rotate(45deg);
8534
+ margin-top: -0.125rem;
8535
+ transition: transform 150ms ease-in-out;
8536
+ }
8537
+
8538
+ .theme-controller-dropdown[open] .theme-controller-trigger::after {
8539
+ transform: rotate(-135deg);
8540
+ margin-top: 0.125rem;
8541
+ }
8542
+
8543
+ /* Floating menu */
8544
+ .theme-controller-menu {
8545
+ position: absolute;
8546
+ top: calc(100% + 0.25rem);
8547
+ left: 0;
8548
+ z-index: 50;
8549
+ min-width: 100%;
8550
+ background-color: var(--color-surface-container);
8551
+ border: 1px solid var(--color-outline-variant);
8552
+ border-radius: var(--radius-field, 0.5rem);
8553
+ padding: 0.25rem;
8554
+ box-shadow: var(--shadow-md);
8555
+ display: flex;
8556
+ flex-direction: column;
8557
+ gap: 0.125rem;
8558
+ }
8559
+
8560
+ /* Menu labels are full-width and left-aligned */
8561
+ .theme-controller-menu .theme-controller-label {
8562
+ width: 100%;
8563
+ justify-content: flex-start;
8564
+ border-radius: calc(var(--radius-field, 0.5rem) - 0.25rem);
8565
+ }
8566
+
8567
+ /* ── Dropdown Size Variants ── */
8568
+
8569
+ .theme-controller-dropdown-sm .theme-controller-trigger {
8570
+ padding: 0.375rem 0.625rem;
8571
+ font-size: 0.75rem;
8572
+ line-height: 1rem;
8573
+ }
8574
+
8575
+ .theme-controller-dropdown-sm .theme-controller-menu {
8576
+ padding: 0.125rem;
8577
+ }
8578
+
8579
+ .theme-controller-dropdown-sm .theme-controller-menu .theme-controller-label {
8580
+ padding: 0.375rem 0.75rem;
8581
+ font-size: 0.75rem;
8582
+ line-height: 1rem;
8583
+ }
8584
+
8585
+ .theme-controller-dropdown-lg .theme-controller-trigger {
8586
+ padding: 0.75rem 1rem;
8587
+ font-size: 1rem;
8588
+ line-height: 1.5rem;
8589
+ }
8590
+
8591
+ .theme-controller-dropdown-lg .theme-controller-menu {
8592
+ padding: 0.375rem;
8593
+ }
8594
+
8595
+ .theme-controller-dropdown-lg .theme-controller-menu .theme-controller-label {
8596
+ padding: 0.75rem 1.5rem;
8597
+ font-size: 1rem;
8598
+ line-height: 1.5rem;
8599
+ }
8600
+
8601
+ /* ── Dropdown Icon-only Trigger ── */
8602
+
8603
+ .theme-controller-dropdown-icon .theme-controller-trigger {
8604
+ padding: 0.5rem;
8605
+ }
8606
+
8607
+ /* Hide chevron for icon-only trigger */
8608
+ .theme-controller-dropdown-icon .theme-controller-trigger::after {
8609
+ display: none;
8610
+ }
8611
+
8612
+ .theme-controller-dropdown-icon.theme-controller-dropdown-sm .theme-controller-trigger {
8613
+ padding: 0.375rem;
8614
+ }
8615
+
8616
+ .theme-controller-dropdown-icon.theme-controller-dropdown-lg .theme-controller-trigger {
8617
+ padding: 0.75rem;
8618
+ }
8619
+
8620
+ /* ── Dropdown Alignment ── */
8621
+
8622
+ .theme-controller-dropdown-end .theme-controller-menu {
8623
+ left: auto;
8624
+ right: 0;
8625
+ }
8626
+
8627
+ /* ========================================
8628
+ * REDUCED MOTION
8629
+ * ======================================== */
8630
+
8631
+ @media (prefers-reduced-motion: reduce) {
8632
+ .theme-controller-label,
8633
+ .theme-controller-trigger,
8634
+ .theme-controller-trigger::after {
8635
+ transition: none;
8636
+ }
8637
+ }
8638
+ }
8358
8639
 
8359
8640
  /**
8360
8641
  * Toggle Button Component Styles
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Theme Controller Component Styles
3
+ * DuskMoonUI - Two display modes for theme switching:
4
+ *
5
+ * 1. Switch (.theme-controller) — inline pill-shaped radio group, all options visible
6
+ * 2. Dropdown (.theme-controller-dropdown) — <details>/<summary> with floating menu
7
+ *
8
+ * Both modes reuse .theme-controller-item (hidden radio) and .theme-controller-label
9
+ * for CSS-only active state via :checked + label.
10
+ */
11
+
12
+ @layer components {
13
+ /* ========================================
14
+ * SHARED — Radio input & label (used in both modes)
15
+ * ======================================== */
16
+
17
+ /* Hidden Radio Input — visually hidden but accessible */
18
+ .theme-controller-item {
19
+ position: absolute;
20
+ width: 1px;
21
+ height: 1px;
22
+ padding: 0;
23
+ margin: -1px;
24
+ overflow: hidden;
25
+ clip: rect(0, 0, 0, 0);
26
+ white-space: nowrap;
27
+ border: 0;
28
+ }
29
+
30
+ /* Visible Label — the clickable option */
31
+ .theme-controller-label {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ gap: 0.5rem;
36
+ padding: 0.5rem 1rem;
37
+ font-size: 0.875rem;
38
+ font-weight: 500;
39
+ line-height: 1.25rem;
40
+ color: var(--color-on-surface-variant);
41
+ background-color: transparent;
42
+ border-radius: calc(var(--radius-box, 1.5rem) - 0.25rem);
43
+ cursor: pointer;
44
+ transition: background-color 150ms ease-in-out, color 150ms ease-in-out,
45
+ box-shadow 150ms ease-in-out;
46
+ user-select: none;
47
+ white-space: nowrap;
48
+ }
49
+
50
+ /* Hover state */
51
+ .theme-controller-label:hover {
52
+ background-color: var(--color-surface-container-high);
53
+ }
54
+
55
+ /* Focus-visible ring on the label when its radio is focused */
56
+ .theme-controller-item:focus-visible + .theme-controller-label {
57
+ outline: 2px solid var(--color-primary);
58
+ outline-offset: 2px;
59
+ }
60
+
61
+ /* Active/checked state */
62
+ .theme-controller-item:checked + .theme-controller-label {
63
+ background-color: var(--color-primary-container);
64
+ color: var(--color-on-primary-container);
65
+ box-shadow: var(--shadow-xs);
66
+ }
67
+
68
+ .theme-controller-item:checked + .theme-controller-label:hover {
69
+ background-color: color-mix(in oklch, var(--color-primary-container), black 5%);
70
+ }
71
+
72
+ /* ========================================
73
+ * SWITCH MODE — inline pill radio group
74
+ * ======================================== */
75
+
76
+ .theme-controller {
77
+ display: inline-flex;
78
+ align-items: stretch;
79
+ background-color: var(--color-surface-container);
80
+ border: 1px solid var(--color-outline-variant);
81
+ border-radius: var(--radius-box, 1.5rem);
82
+ padding: 0.25rem;
83
+ gap: 0.125rem;
84
+ }
85
+
86
+ /* ── Switch Size Variants ── */
87
+
88
+ .theme-controller-sm {
89
+ padding: 0.125rem;
90
+ border-radius: calc(var(--radius-box, 1.5rem) * 0.75);
91
+ }
92
+
93
+ .theme-controller-sm .theme-controller-label {
94
+ padding: 0.375rem 0.75rem;
95
+ font-size: 0.75rem;
96
+ line-height: 1rem;
97
+ border-radius: calc(var(--radius-box, 1.5rem) * 0.75 - 0.125rem);
98
+ }
99
+
100
+ .theme-controller-lg {
101
+ padding: 0.375rem;
102
+ gap: 0.25rem;
103
+ border-radius: calc(var(--radius-box, 1.5rem) * 1.25);
104
+ }
105
+
106
+ .theme-controller-lg .theme-controller-label {
107
+ padding: 0.75rem 1.5rem;
108
+ font-size: 1rem;
109
+ line-height: 1.5rem;
110
+ border-radius: calc(var(--radius-box, 1.5rem) * 1.25 - 0.375rem);
111
+ }
112
+
113
+ /* ── Switch Icon-only Variant ── */
114
+
115
+ .theme-controller-icon .theme-controller-label {
116
+ padding: 0.5rem;
117
+ }
118
+
119
+ .theme-controller-icon.theme-controller-sm .theme-controller-label {
120
+ padding: 0.375rem;
121
+ }
122
+
123
+ .theme-controller-icon.theme-controller-lg .theme-controller-label {
124
+ padding: 0.75rem;
125
+ }
126
+
127
+ /* ========================================
128
+ * DROPDOWN MODE — <details>/<summary> with floating menu
129
+ * ======================================== */
130
+
131
+ .theme-controller-dropdown {
132
+ position: relative;
133
+ display: inline-block;
134
+ }
135
+
136
+ /* Trigger button (<summary>) */
137
+ .theme-controller-trigger {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: 0.5rem;
141
+ padding: 0.5rem 0.75rem;
142
+ font-size: 0.875rem;
143
+ font-weight: 500;
144
+ line-height: 1.25rem;
145
+ color: var(--color-on-surface);
146
+ background-color: var(--color-surface-container);
147
+ border: 1px solid var(--color-outline-variant);
148
+ border-radius: var(--radius-field, 0.5rem);
149
+ cursor: pointer;
150
+ user-select: none;
151
+ transition: background-color 150ms ease-in-out;
152
+ list-style: none;
153
+ }
154
+
155
+ .theme-controller-trigger::-webkit-details-marker {
156
+ display: none;
157
+ }
158
+
159
+ .theme-controller-trigger:hover {
160
+ background-color: var(--color-surface-container-high);
161
+ }
162
+
163
+ .theme-controller-trigger:focus-visible {
164
+ outline: 2px solid var(--color-primary);
165
+ outline-offset: 2px;
166
+ }
167
+
168
+ /* Chevron indicator */
169
+ .theme-controller-trigger::after {
170
+ content: '';
171
+ display: inline-block;
172
+ width: 0.5rem;
173
+ height: 0.5rem;
174
+ border-right: 2px solid currentColor;
175
+ border-bottom: 2px solid currentColor;
176
+ transform: rotate(45deg);
177
+ margin-top: -0.125rem;
178
+ transition: transform 150ms ease-in-out;
179
+ }
180
+
181
+ .theme-controller-dropdown[open] .theme-controller-trigger::after {
182
+ transform: rotate(-135deg);
183
+ margin-top: 0.125rem;
184
+ }
185
+
186
+ /* Floating menu */
187
+ .theme-controller-menu {
188
+ position: absolute;
189
+ top: calc(100% + 0.25rem);
190
+ left: 0;
191
+ z-index: 50;
192
+ min-width: 100%;
193
+ background-color: var(--color-surface-container);
194
+ border: 1px solid var(--color-outline-variant);
195
+ border-radius: var(--radius-field, 0.5rem);
196
+ padding: 0.25rem;
197
+ box-shadow: var(--shadow-md);
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 0.125rem;
201
+ }
202
+
203
+ /* Menu labels are full-width and left-aligned */
204
+ .theme-controller-menu .theme-controller-label {
205
+ width: 100%;
206
+ justify-content: flex-start;
207
+ border-radius: calc(var(--radius-field, 0.5rem) - 0.25rem);
208
+ }
209
+
210
+ /* ── Dropdown Size Variants ── */
211
+
212
+ .theme-controller-dropdown-sm .theme-controller-trigger {
213
+ padding: 0.375rem 0.625rem;
214
+ font-size: 0.75rem;
215
+ line-height: 1rem;
216
+ }
217
+
218
+ .theme-controller-dropdown-sm .theme-controller-menu {
219
+ padding: 0.125rem;
220
+ }
221
+
222
+ .theme-controller-dropdown-sm .theme-controller-menu .theme-controller-label {
223
+ padding: 0.375rem 0.75rem;
224
+ font-size: 0.75rem;
225
+ line-height: 1rem;
226
+ }
227
+
228
+ .theme-controller-dropdown-lg .theme-controller-trigger {
229
+ padding: 0.75rem 1rem;
230
+ font-size: 1rem;
231
+ line-height: 1.5rem;
232
+ }
233
+
234
+ .theme-controller-dropdown-lg .theme-controller-menu {
235
+ padding: 0.375rem;
236
+ }
237
+
238
+ .theme-controller-dropdown-lg .theme-controller-menu .theme-controller-label {
239
+ padding: 0.75rem 1.5rem;
240
+ font-size: 1rem;
241
+ line-height: 1.5rem;
242
+ }
243
+
244
+ /* ── Dropdown Icon-only Trigger ── */
245
+
246
+ .theme-controller-dropdown-icon .theme-controller-trigger {
247
+ padding: 0.5rem;
248
+ }
249
+
250
+ /* Hide chevron for icon-only trigger */
251
+ .theme-controller-dropdown-icon .theme-controller-trigger::after {
252
+ display: none;
253
+ }
254
+
255
+ .theme-controller-dropdown-icon.theme-controller-dropdown-sm .theme-controller-trigger {
256
+ padding: 0.375rem;
257
+ }
258
+
259
+ .theme-controller-dropdown-icon.theme-controller-dropdown-lg .theme-controller-trigger {
260
+ padding: 0.75rem;
261
+ }
262
+
263
+ /* ── Dropdown Alignment ── */
264
+
265
+ .theme-controller-dropdown-end .theme-controller-menu {
266
+ left: auto;
267
+ right: 0;
268
+ }
269
+
270
+ /* ========================================
271
+ * REDUCED MOTION
272
+ * ======================================== */
273
+
274
+ @media (prefers-reduced-motion: reduce) {
275
+ .theme-controller-label,
276
+ .theme-controller-trigger,
277
+ .theme-controller-trigger::after {
278
+ transition: none;
279
+ }
280
+ }
281
+ }