@mks2508/mks-ui 0.5.2 → 0.5.7

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 (72) hide show
  1. package/dist/react-ui/index.js +8 -3
  2. package/dist/react-ui/primitives/index.js +5 -0
  3. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +120 -0
  4. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -0
  5. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +10 -0
  6. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -0
  7. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +190 -0
  8. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts +7 -0
  9. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts.map +1 -0
  10. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.js +78 -0
  11. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts +7 -0
  12. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts.map +1 -0
  13. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.js +51 -0
  14. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +94 -0
  15. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -0
  16. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +182 -0
  17. package/dist/react-ui/primitives/waapi/Gooey/index.d.ts +28 -0
  18. package/dist/react-ui/primitives/waapi/Gooey/index.d.ts.map +1 -0
  19. package/dist/react-ui/primitives/waapi/Gooey/index.js +5 -0
  20. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts +7 -0
  21. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts.map +1 -0
  22. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.js +47 -0
  23. package/dist/react-ui/primitives/waapi/index.d.ts +2 -0
  24. package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -1
  25. package/dist/react-ui/primitives/waapi/index.js +6 -0
  26. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +26 -16
  27. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -1
  28. package/dist/react-ui/ui/DataCard/DataCard.styles.js +36 -74
  29. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +50 -70
  30. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -1
  31. package/dist/react-ui/ui/DataCard/index.d.ts +24 -93
  32. package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -1
  33. package/dist/react-ui/ui/DataCard/index.js +76 -118
  34. package/dist/react-ui/ui/DynamicToggle/DynamicToggle-DOR3Ld-k.css +376 -0
  35. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +376 -0
  36. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.js +0 -0
  37. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts +20 -8
  38. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts.map +1 -1
  39. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +55 -27
  40. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +69 -14
  41. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
  42. package/dist/react-ui/ui/DynamicToggle/index.d.ts +22 -20
  43. package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
  44. package/dist/react-ui/ui/DynamicToggle/index.js +133 -96
  45. package/dist/react-ui/ui/Switch/index.js +1 -1
  46. package/dist/react-ui/ui/index.js +2 -2
  47. package/package.json +2 -2
  48. package/src/css.d.ts +1 -0
  49. package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +141 -0
  50. package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +217 -0
  51. package/src/react-ui/primitives/waapi/Gooey/GooeyFilter.tsx +77 -0
  52. package/src/react-ui/primitives/waapi/Gooey/MorphPath.tsx +58 -0
  53. package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +253 -0
  54. package/src/react-ui/primitives/waapi/Gooey/index.ts +50 -0
  55. package/src/react-ui/primitives/waapi/Gooey/useMorphPath.ts +48 -0
  56. package/src/react-ui/primitives/waapi/index.ts +23 -0
  57. package/src/react-ui/ui/DataCard/DataCard.styles.ts +45 -101
  58. package/src/react-ui/ui/DataCard/DataCard.types.ts +52 -73
  59. package/src/react-ui/ui/DataCard/index.tsx +118 -184
  60. package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +320 -94
  61. package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +60 -40
  62. package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +101 -14
  63. package/src/react-ui/ui/DynamicToggle/index.tsx +172 -96
  64. package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +413 -0
  65. package/src/react-ui/ui/DynamicToggle/prototype-v7.html +615 -0
  66. package/src/react-ui/ui/DynamicToggle/prototype-v8-gooey-safari.html +560 -0
  67. package/src/react-ui/ui/DynamicToggle/prototype-v8b-react-structure.html +227 -0
  68. package/src/react-ui/ui/DynamicToggle/prototype.html +419 -0
  69. package/src/react-ui/ui/Switch/index.tsx +1 -1
  70. /package/dist/react-ui/blocks/Terminal/panel/{terminal-filter-dropdown.module-DAcl_XQZ.css → terminal-filter-dropdown.module-C6oDcFBS.css} +0 -0
  71. /package/dist/react-ui/blocks/Terminal/panel/{terminal-session-tabs.module-DNAop5e3.css → terminal-session-tabs.module-D_-sgyza.css} +0 -0
  72. /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-BJrjXisF.css → morphing-popover.module-B1ftlaYj.css} +0 -0
@@ -1,150 +1,376 @@
1
1
  /**
2
- * DynamicToggle — CSS-only animated transitions.
2
+ * DynamicToggle — CSS state transitions.
3
3
  *
4
- * Ported from @jh3yy codepen. All colors via CSS variables (semantic tokens).
5
- * Uses :has(:checked) for state-driven animation.
6
- * container-type: size on group for cqh units in clip-path.
4
+ * Rules requiring :has(), container queries, clip-path, or pseudo-elements.
5
+ * Layout, colors, sizing in Tailwind (DynamicToggle.styles.ts).
6
+ *
7
+ * @import '@mks2508/mks-ui/dist/react-ui/ui/DynamicToggle/DynamicToggle.css';
7
8
  */
8
9
 
9
- :root {
10
- --dt-duration: 0.22s;
10
+ /* ── Variables ── */
11
+ [data-slot="dt-root"] {
12
+ --dt-dur: 0.22s;
11
13
  --dt-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
12
- --dt-drop-off: 0.45;
14
+ --dt-fade: 0.45;
15
+ --dt-indicator-dur: 0.3s;
16
+ --dt-indicator-ease: cubic-bezier(0.4, 0, 0.2, 1);
13
17
  }
14
18
 
15
- /* ── Track layout ── */
16
- [data-slot="dt-track"] {
17
- grid-template-columns: repeat(4, 1fr);
19
+ /* ── Track: explicit row prevents h-full items from overflowing container ── */
20
+ [data-slot="dt-root"] [data-slot="dt-track"] {
21
+ grid-template-rows: minmax(0, 1fr);
18
22
  }
19
23
 
20
- [data-slot="dt-track"] > label:first-of-type,
21
- [data-slot="dt-group"] {
24
+ /* ── Top-level option spans 2 grid cols ── */
25
+ [data-slot="dt-root"] [data-slot="dt-track"] > label {
22
26
  grid-column: span 2;
23
27
  }
24
28
 
25
- /* ── Main indicator slide ── */
26
- [data-slot="dt-indicator"] {
27
- transition: translate var(--dt-duration) var(--dt-ease);
29
+ /* ── Primary option text ── */
30
+ [data-slot="dt-root"] [data-slot="dt-track"]:has(> input:checked) > label {
31
+ color: var(--accent-foreground);
32
+ z-index: 2;
28
33
  }
29
-
30
- /* When primary (non-group) is checked → indicator on left */
31
- [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-indicator"] {
32
- translate: 0 0;
34
+ [data-slot="dt-root"] [data-slot="dt-track"]:not(:has(> input:checked)) > label {
35
+ color: var(--foreground);
36
+ opacity: var(--dt-fade);
33
37
  }
34
38
 
35
- /* When nothing in track is checked → group is active → indicator slides right */
36
- [data-slot="dt-track"]:not(:has(> input:checked)) [data-slot="dt-indicator"] {
37
- translate: 100% 0;
39
+ /* ── Group: container queries ── */
40
+ [data-slot="dt-root"] [data-slot="dt-group"] {
41
+ container-type: size;
42
+ overflow: hidden;
38
43
  }
39
44
 
40
- /* ── Primary option text ── */
41
- [data-slot="dt-track"]:has(> input:checked) > label {
42
- color: var(--card);
45
+ /* ══════════════════════════════════════════════════════════
46
+ * INDICATOR POSITIONING
47
+ *
48
+ * Modern: CSS Anchor Positioning — indicator follows active option
49
+ * Fallback: translate-based positioning for older browsers
50
+ * ══════════════════════════════════════════════════════════ */
51
+
52
+ /* ── Anchor-based indicator (requires full anchor API) ── */
53
+ @supports (anchor-scope: all) {
54
+ /* Scope anchors per toggle instance */
55
+ [data-slot="dt-root"]:not([data-indicator="translate"]) {
56
+ anchor-scope: --dt-active;
57
+ }
58
+
59
+ /* Active option becomes the anchor via native :checked */
60
+ [data-slot="dt-root"]:not([data-indicator="translate"]) [data-slot="dt-track"] > label:has(+ input:checked) {
61
+ anchor-name: --dt-active;
62
+ }
63
+ [data-slot="dt-root"]:not([data-indicator="translate"]) [data-slot="dt-group"] > label:has(+ input:checked) {
64
+ anchor-name: --dt-active;
65
+ }
66
+
67
+ /* Single unified indicator: morphs from full-width to half-width */
68
+ [data-slot="dt-root"]:not([data-indicator="translate"]) [data-slot="dt-indicator"] {
69
+ position-anchor: --dt-active;
70
+ top: anchor(top);
71
+ right: anchor(right);
72
+ bottom: anchor(bottom);
73
+ left: anchor(left);
74
+ translate: none;
75
+ width: auto;
76
+ transition:
77
+ top var(--dt-indicator-dur) var(--dt-indicator-ease),
78
+ right var(--dt-indicator-dur) var(--dt-indicator-ease),
79
+ bottom var(--dt-indicator-dur) var(--dt-indicator-ease),
80
+ left var(--dt-indicator-dur) var(--dt-indicator-ease);
81
+ }
82
+
83
+ /* Hide the group indicator — unified indicator handles everything */
84
+ [data-slot="dt-root"]:not([data-indicator="translate"]) [data-slot="dt-group-indicator"] {
85
+ display: none;
86
+ }
43
87
  }
44
88
 
45
- [data-slot="dt-track"]:not(:has(> input:checked)) > label {
46
- color: var(--foreground);
47
- opacity: var(--dt-drop-off);
89
+ /* ── Inset-based fallback (older browsers) same morph as anchor but hardcoded ── */
90
+ @supports not (anchor-scope: all) {
91
+ /* Unified indicator: left/right transition morphs width + position */
92
+ [data-slot="dt-root"] [data-slot="dt-indicator"] {
93
+ left: 50%;
94
+ right: 0;
95
+ width: auto;
96
+ translate: none;
97
+ transition:
98
+ left var(--dt-indicator-dur) var(--dt-indicator-ease),
99
+ right var(--dt-indicator-dur) var(--dt-indicator-ease);
100
+ }
101
+ /* Top-level checked: indicator covers left half */
102
+ [data-slot="dt-root"] [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-indicator"] {
103
+ left: 0;
104
+ right: 50%;
105
+ }
106
+ /* Group option 1 checked: indicator at 3rd quarter */
107
+ [data-slot="dt-root"] [data-slot="dt-group"]:has(input:nth-of-type(1):checked) ~ [data-slot="dt-indicator"],
108
+ [data-slot="dt-root"] [data-slot="dt-track"]:has([data-slot="dt-group"] input:nth-of-type(1):checked) [data-slot="dt-indicator"] {
109
+ left: 50%;
110
+ right: 25%;
111
+ }
112
+ /* Group option 2 checked: indicator at 4th quarter */
113
+ [data-slot="dt-root"] [data-slot="dt-group"]:has(input:nth-of-type(2):checked) ~ [data-slot="dt-indicator"],
114
+ [data-slot="dt-root"] [data-slot="dt-track"]:has([data-slot="dt-group"] input:nth-of-type(2):checked) [data-slot="dt-indicator"] {
115
+ left: 75%;
116
+ right: 0;
117
+ }
118
+ /* Hide group indicator — unified indicator handles everything */
119
+ [data-slot="dt-root"] [data-slot="dt-group-indicator"] {
120
+ display: none;
121
+ }
48
122
  }
49
123
 
50
- /* ── Group container ── */
51
- [data-slot="dt-group"] {
52
- container-type: size;
53
- grid-template-columns: 1fr 1fr;
124
+ /* ── Force inset mode via data-indicator="translate" (works regardless of @supports) ── */
125
+ [data-slot="dt-root"][data-indicator="translate"] [data-slot="dt-indicator"] {
126
+ left: 50%;
127
+ right: 0;
128
+ width: auto;
129
+ translate: none;
130
+ transition:
131
+ left var(--dt-indicator-dur) var(--dt-indicator-ease),
132
+ right var(--dt-indicator-dur) var(--dt-indicator-ease);
133
+ }
134
+ [data-slot="dt-root"][data-indicator="translate"] [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-indicator"] {
135
+ left: 0;
136
+ right: 50%;
137
+ }
138
+ [data-slot="dt-root"][data-indicator="translate"] [data-slot="dt-track"]:has([data-slot="dt-group"] input:nth-of-type(1):checked) [data-slot="dt-indicator"] {
139
+ left: 50%;
140
+ right: 25%;
141
+ }
142
+ [data-slot="dt-root"][data-indicator="translate"] [data-slot="dt-track"]:has([data-slot="dt-group"] input:nth-of-type(2):checked) [data-slot="dt-indicator"] {
143
+ left: 75%;
144
+ right: 0;
145
+ }
146
+ [data-slot="dt-root"][data-indicator="translate"] [data-slot="dt-group-indicator"] {
147
+ display: none;
54
148
  }
55
149
 
56
- /* ── Group collapsed label ("Changes") ── */
57
- [data-slot="dt-group-label"] {
150
+ /* ══════════════════════════════════════════════════════════
151
+ * GROUP COLLAPSED STATE
152
+ *
153
+ * ::before = title text (via data-label attr)
154
+ * ::after = combined opts text (via data-opts attr)
155
+ * <label>s = controlled by data-collapsed mode
156
+ *
157
+ * 3 modes: title | opts | title-opts (default)
158
+ * ══════════════════════════════════════════════════════════ */
159
+
160
+ /* ── ::before — group title ── */
161
+ [data-slot="dt-group"]::before {
162
+ content: attr(data-label);
163
+ position: absolute;
164
+ left: 50%;
165
+ top: 50%;
58
166
  translate: -50% -80%;
59
- transition: translate var(--dt-duration) var(--dt-ease),
60
- scale var(--dt-duration) var(--dt-ease);
167
+ color: var(--foreground);
168
+ font-size: inherit;
169
+ font-weight: 500;
170
+ z-index: 2;
171
+ white-space: nowrap;
172
+ pointer-events: none;
173
+ transition:
174
+ scale var(--dt-dur) var(--dt-ease),
175
+ translate var(--dt-dur) var(--dt-ease),
176
+ opacity var(--dt-dur) var(--dt-ease);
61
177
  }
62
178
 
63
- /* When group is active label moves up and shrinks */
64
- [data-slot="dt-group"]:has(input:checked) [data-slot="dt-group-label"] {
65
- translate: -50% -250%;
66
- scale: 0.85;
179
+ /* ── ::after combined opts text ── */
180
+ [data-slot="dt-group"]::after {
181
+ content: attr(data-opts);
182
+ position: absolute;
183
+ left: 50%;
184
+ top: 50%;
185
+ translate: -50% 20%;
186
+ color: var(--muted-foreground);
187
+ font-size: 0.85em;
188
+ opacity: 0.6;
189
+ z-index: 2;
190
+ white-space: nowrap;
191
+ pointer-events: none;
192
+ transition: opacity var(--dt-dur) var(--dt-ease);
67
193
  }
68
-
69
- /* ── Group internal indicator ── */
70
- [data-slot="dt-group-indicator"] {
71
- transition: translate var(--dt-duration) var(--dt-ease),
72
- clip-path var(--dt-duration) var(--dt-ease),
73
- background var(--dt-duration) var(--dt-ease);
74
- /* Collapsed: clip to tiny pill in center */
75
- clip-path: inset(73cqh calc(50% + 1px) calc(27cqh - 2px) calc(50% - 3px) round 100px);
76
- translate: -50% 0;
77
- background: var(--foreground);
194
+ [data-slot="dt-group"]:not([data-opts])::after {
195
+ content: none;
78
196
  }
79
197
 
80
- /* When group is active indicator expands and positions */
81
- [data-slot="dt-group"]:has(input:checked) [data-slot="dt-group-indicator"] {
82
- background: var(--card);
83
- clip-path: inset(0 0 0 0 round 100px);
198
+ /* ── Group labels transition props ── */
199
+ [data-slot="dt-root"] [data-slot="dt-group"] label {
200
+ color: var(--muted-foreground);
201
+ cursor: pointer;
202
+ z-index: 2;
203
+ transition:
204
+ color var(--dt-dur) var(--dt-ease),
205
+ opacity var(--dt-dur) var(--dt-ease),
206
+ translate var(--dt-dur) var(--dt-ease);
84
207
  }
85
-
86
- /* First sub-option checked → indicator left */
87
- [data-slot="dt-group"]:has(input:nth-of-type(1):checked) [data-slot="dt-group-indicator"] {
88
- translate: -100% 0;
208
+ [data-slot="dt-root"] [data-slot="dt-group"] label span {
209
+ display: grid;
210
+ place-items: center;
211
+ height: 100%;
212
+ width: 100%;
213
+ border-radius: var(--dt-radius, 9999px);
214
+ transition: scale var(--dt-dur) var(--dt-ease);
89
215
  }
90
216
 
91
- /* Second sub-option checked indicator right */
92
- [data-slot="dt-group"]:has(input:nth-of-type(2):checked) [data-slot="dt-group-indicator"] {
93
- translate: 0 0;
217
+ /* ── Collapsed mode: "title" only ::before, labels slide+scale out ── */
218
+ [data-slot="dt-group"][data-collapsed="title"]::before {
219
+ translate: -50% -50%;
94
220
  }
95
-
96
- /* ── Group option labels ── */
97
- [data-slot="dt-group"] label {
98
- color: var(--muted-foreground);
99
- transition: color var(--dt-duration) var(--dt-ease),
100
- opacity var(--dt-duration) var(--dt-ease);
221
+ [data-slot="dt-group"][data-collapsed="title"]::after {
222
+ display: none;
223
+ }
224
+ [data-slot="dt-group"][data-collapsed="title"]:not(:has(input:checked)) label {
225
+ opacity: 0;
226
+ translate: 0 30%;
227
+ }
228
+ [data-slot="dt-group"][data-collapsed="title"]:not(:has(input:checked)) label span {
229
+ scale: 0.5;
101
230
  }
102
231
 
103
- [data-slot="dt-group"] label span {
104
- transition: scale var(--dt-duration) var(--dt-ease);
232
+ /* ── Collapsed mode: "opts" only ::after, labels slide+scale out ── */
233
+ [data-slot="dt-group"][data-collapsed="opts"]::before {
234
+ display: none;
235
+ }
236
+ [data-slot="dt-group"][data-collapsed="opts"]::after {
237
+ translate: -50% -50%;
238
+ font-size: inherit;
239
+ opacity: 0.7;
240
+ }
241
+ [data-slot="dt-group"][data-collapsed="opts"]:not(:has(input:checked)) label {
242
+ opacity: 0;
243
+ translate: 0 30%;
244
+ }
245
+ [data-slot="dt-group"][data-collapsed="opts"]:not(:has(input:checked)) label span {
246
+ scale: 0.5;
105
247
  }
106
248
 
107
- /* When group collapsed: hide sub-option labels */
108
- [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-group"] label {
109
- color: var(--muted-foreground);
249
+ /* ── Collapsed mode: "title-opts" WIP: disabled, falls back to "title" behavior ── */
250
+ /* TODO: title-opts needs a redesign — title (::before) and scaled labels overlap
251
+ at all container sizes. The codepen original morph relied on specific dimensions
252
+ that don't translate to the component's size variants. Needs a different approach
253
+ (e.g., crossfade, flex layout, or JS-measured positions). */
254
+ [data-slot="dt-group"][data-collapsed="title-opts"]::after {
255
+ content: none;
256
+ }
257
+ [data-slot="dt-group"][data-collapsed="title-opts"]::before {
258
+ translate: -50% -50%;
259
+ }
260
+ [data-slot="dt-group"][data-collapsed="title-opts"]:not(:has(input:checked)) label {
261
+ opacity: 0;
262
+ translate: 0 30%;
110
263
  }
111
264
 
112
- /* When group active: show labels, highlight active one */
265
+ /* ── When group expanded ── */
266
+ [data-slot="dt-group"]:has(input:checked)::before {
267
+ translate: -50% -250%;
268
+ scale: 0.85;
269
+ }
270
+ [data-slot="dt-group"]:has(input:checked)::after {
271
+ opacity: 0;
272
+ }
113
273
  [data-slot="dt-group"]:has(input:checked) label {
114
- color: var(--muted-foreground);
115
274
  opacity: 0.75;
275
+ color: var(--muted-foreground);
276
+ translate: 0 0;
277
+ }
278
+ [data-slot="dt-group"]:has(input:checked) label span {
279
+ scale: 1;
116
280
  }
117
-
118
281
  [data-slot="dt-group"]:has(input:nth-of-type(1):checked) label:nth-of-type(1),
119
282
  [data-slot="dt-group"]:has(input:nth-of-type(2):checked) label:nth-of-type(2) {
120
283
  color: var(--foreground);
121
284
  opacity: 1;
122
285
  }
123
286
 
124
- /* Sub-option label scale animation */
125
- [data-slot="dt-group"] label:nth-of-type(1) span {
126
- scale: 0.75;
127
- transform-origin: 150% 150%;
287
+ /* ══════════════════════════════════════════════════════════
288
+ * GROUP LABEL (above/below the pill)
289
+ *
290
+ * Replaces the old "bubble" element. Positioned via CSS grid.
291
+ * In filter/path morph modes, rendered inside GooeyCanvas.
292
+ * In none mode, simple CSS-driven show/hide.
293
+ * ══════════════════════════════════════════════════════════ */
294
+
295
+ [data-slot="dt-group-label"] {
296
+ display: grid;
297
+ grid-template-rows: 0fr;
298
+ left: 20%;
299
+ right: 20%;
300
+ transition:
301
+ grid-template-rows calc(var(--dt-dur) * 1.5) var(--dt-ease),
302
+ opacity var(--dt-dur) var(--dt-ease);
303
+ opacity: 0;
304
+ background: var(--card);
305
+ border: 1px solid var(--border);
306
+ z-index: 3;
307
+ transform: translateZ(0);
308
+ -webkit-transform: translateZ(0);
309
+ }
310
+ [data-slot="dt-group-label"] > span {
311
+ overflow: hidden;
312
+ min-height: 0;
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: center;
316
+ padding: 0 0.75em;
317
+ height: calc(var(--dt-h, 38px) * 0.4);
318
+ box-sizing: border-box;
128
319
  }
129
320
 
130
- [data-slot="dt-group"] label:nth-of-type(2) span {
131
- scale: 0.75;
132
- transform-origin: -65% 150%;
321
+ /* Top position */
322
+ [data-slot="dt-group-label"][data-position="top"] {
323
+ bottom: 100%;
324
+ border-radius: calc(var(--dt-h, 38px) * 0.2) calc(var(--dt-h, 38px) * 0.2) 0 0;
325
+ border-bottom: none;
326
+ margin-bottom: -1px;
133
327
  }
134
328
 
135
- [data-slot="dt-group"]:has(input:checked) label span {
329
+ /* Bottom position */
330
+ [data-slot="dt-group-label"][data-position="bottom"] {
331
+ top: 100%;
332
+ border-radius: 0 0 calc(var(--dt-h, 38px) * 0.2) calc(var(--dt-h, 38px) * 0.2);
333
+ border-top: none;
334
+ margin-top: -1px;
335
+ }
336
+
337
+ /* When group active → group label grows */
338
+ [data-slot="dt-root"]:not(:has([data-slot="dt-track"] > input:checked)) [data-slot="dt-group-label"] {
339
+ grid-template-rows: 1fr;
340
+ opacity: 1;
341
+ }
342
+ [data-slot="dt-root"]:not(:has([data-slot="dt-track"] > input:checked)) [data-slot="dt-group-label"] > span {
343
+ padding: 0.35em 0.75em;
344
+ }
345
+
346
+ /* ── Filter morph mode ── */
347
+ [data-slot="dt-root"][data-morph="filter"] {
348
+ background: transparent;
349
+ border-color: transparent;
350
+ box-shadow: none;
351
+ overflow: visible;
352
+ }
353
+ [data-slot="dt-root"][data-morph="filter"] [data-slot="dt-group-label"] {
354
+ border: none;
355
+ }
356
+ [data-slot="dt-root"][data-morph="filter"] [data-slot="dt-track"] {
357
+ position: relative;
358
+ z-index: 1;
359
+ }
360
+
361
+ /* ── Filter morph: ::before hides on expand, gooey canvas handles junction ── */
362
+ [data-slot="dt-root"][data-morph="filter"] [data-slot="dt-group"]:has(input:checked)::before {
363
+ opacity: 0;
364
+ translate: -50% -80%;
136
365
  scale: 1;
137
366
  }
138
367
 
139
- /* ── Screen reader only (hidden radios) ── */
140
- [data-slot="dt-radio"] {
141
- position: absolute;
142
- width: 1px;
143
- height: 1px;
144
- padding: 0;
145
- margin: -1px;
146
- overflow: hidden;
147
- clip: rect(0, 0, 0, 0);
148
- white-space: nowrap;
149
- border-width: 0;
368
+ /* ── Path morph mode ── */
369
+ [data-slot="dt-root"][data-morph="path"] {
370
+ background: transparent;
371
+ border-color: transparent;
150
372
  }
373
+ [data-slot="dt-root"][data-morph="path"] [data-slot="dt-track"] {
374
+ position: relative;
375
+ z-index: 1;
376
+ }
@@ -1,12 +1,13 @@
1
1
  /**
2
- * DynamicToggle style slots and types.
2
+ * DynamicToggle style slots + CVA variants.
3
3
  *
4
- * Uses semantic CSS variables for all colors.
5
- * Animation handled in DynamicToggle.css (clip-path, transitions).
4
+ * Layout via Tailwind. State animations via CSS file (`:has()`, `clip-path`).
5
+ * Shape propagated to indicators via `--dt-radius` CSS variable.
6
6
  *
7
7
  * @module @mks2508/mks-ui/react/ui/DynamicToggle
8
8
  */
9
9
 
10
+ import { cva, type VariantProps } from 'class-variance-authority';
10
11
  import type { StyleSlots } from '@/core/types';
11
12
 
12
13
  /** Slot names for DynamicToggle */
@@ -16,50 +17,69 @@ export type DynamicToggleSlot =
16
17
  | 'option'
17
18
  | 'indicator'
18
19
  | 'group'
19
- | 'groupLabel'
20
- | 'groupIndicator';
20
+ | 'groupIndicator'
21
+ | 'groupLabel';
21
22
 
22
23
  /**
23
24
  * Default styles for each DynamicToggle slot.
24
25
  *
25
- * @example
26
- * ```tsx
27
- * <DynamicToggle slots={{ root: 'w-72', indicator: 'bg-primary' }}>
28
- * ...
29
- * </DynamicToggle>
30
- * ```
26
+ * Width is set by size variants — required because indicator uses `width: 50%`
27
+ * and clip-path uses container query units. Override: `slots={{ root: 'w-80' }}`.
31
28
  */
32
29
  export const dynamicToggleStyles: StyleSlots<DynamicToggleSlot> = {
33
- root: [
34
- 'relative rounded-full border border-border bg-card',
35
- 'p-[2px] select-none',
36
- ].join(' '),
37
- track: [
38
- 'relative grid place-items-center',
39
- 'w-full h-full',
40
- ].join(' '),
41
- option: [
42
- 'inline-grid place-items-center cursor-pointer',
43
- 'text-xs font-medium z-[2] h-full w-full',
44
- 'transition-[color,opacity] duration-[220ms]',
45
- ].join(' '),
46
- indicator: [
47
- 'absolute w-1/2 left-0 top-0 bottom-0',
48
- 'bg-foreground rounded-full',
49
- 'pointer-events-none z-0',
50
- ].join(' '),
51
- group: [
52
- 'relative w-full h-full grid',
53
- 'border border-transparent',
54
- ].join(' '),
30
+ root: 'relative border p-[2px] select-none',
31
+ track: 'relative grid grid-cols-[repeat(4,1fr)] place-items-center w-full h-full',
32
+ option: 'inline-grid place-items-center cursor-pointer font-medium z-[2] h-full w-full whitespace-nowrap',
33
+ indicator: 'absolute w-1/2 left-0 top-0 bottom-0 bg-accent rounded-[var(--dt-radius,9999px)] pointer-events-none z-0',
34
+ group: 'col-span-2 relative w-full h-full grid grid-cols-2',
35
+ groupIndicator: 'absolute left-1/2 top-0 bottom-0 w-1/2 bg-accent rounded-[var(--dt-radius,9999px)] pointer-events-none z-0',
55
36
  groupLabel: [
56
- 'absolute left-1/2 top-1/2 z-[2]',
57
- 'text-xs font-medium text-foreground',
37
+ 'absolute',
38
+ 'flex items-center justify-center',
39
+ 'text-muted-foreground font-medium whitespace-nowrap',
58
40
  'pointer-events-none',
59
41
  ].join(' '),
60
- groupIndicator: [
61
- 'absolute left-1/2 top-0 bottom-0',
62
- 'bg-foreground rounded-full',
63
- 'pointer-events-none z-0',
64
- ].join(' '),
65
42
  };
43
+
44
+ /**
45
+ * CVA variants for DynamicToggle root.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * <DynamicToggle size="sm" variant="outline" shape="rounded">
50
+ * ```
51
+ */
52
+ export const dynamicToggleVariants = cva(dynamicToggleStyles.root, {
53
+ variants: {
54
+ /** Visual variant — background and border */
55
+ variant: {
56
+ default: 'bg-card border-border shadow-sm',
57
+ ghost: 'bg-transparent border-transparent',
58
+ muted: 'bg-muted border-muted',
59
+ outline: 'bg-transparent border-border',
60
+ },
61
+ /**
62
+ * Size — height, width, and font size.
63
+ * Width is required for the 50% indicator and cqh clip-path.
64
+ */
65
+ size: {
66
+ sm: 'h-[30px] w-[210px] text-[10px] [--dt-h:30px]',
67
+ default: 'h-[38px] w-[260px] text-xs [--dt-h:38px]',
68
+ lg: 'h-[44px] w-80 text-sm [--dt-h:44px]',
69
+ },
70
+ /** Shape — border radius propagated to indicators via --dt-radius */
71
+ shape: {
72
+ pill: 'rounded-full [--dt-radius:9999px]',
73
+ rounded: 'rounded-xl [--dt-radius:0.75rem]',
74
+ square: 'rounded-md [--dt-radius:0.375rem]',
75
+ },
76
+ },
77
+ defaultVariants: {
78
+ variant: 'default',
79
+ size: 'default',
80
+ shape: 'pill',
81
+ },
82
+ });
83
+
84
+ /** Variant props extracted from CVA */
85
+ export type DynamicToggleVariantProps = VariantProps<typeof dynamicToggleVariants>;