@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
@@ -5,63 +5,58 @@ import { CountingNumber } from "../../primitives/CountingNumber/index.js";
5
5
  import { getStrictContext } from "../../lib/get-strict-context.js";
6
6
  import { Switch, SwitchThumb } from "../Switch/index.js";
7
7
  import { CornerBracket } from "../CornerBracket/index.js";
8
- import { dataCardStateStyles, dataCardStyles, dataCardVariants } from "./DataCard.styles.js";
8
+ import { dataCardStyles, dataCardVariants } from "./DataCard.styles.js";
9
9
  import * as React$1 from "react";
10
10
  import { motion } from "motion/react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
 
13
13
  //#region src/react-ui/ui/DataCard/index.tsx
14
- const [DataCardProvider, useDataCard] = getStrictContext("DataCardContext");
15
14
  /**
16
- * DataCard — comprehensive showcase component demonstrating mks-ui patterns.
15
+ * DataCard — glassmorphism dashboard metric card.
16
+ *
17
+ * Features: animated numbers, corner brackets, toggle, size/variant CVA,
18
+ * state-based styling, slot overrides, config for animation tuning.
17
19
  *
18
- * Features:
19
- * - Slot overrides for customization
20
- * - State-based styling (hover, pressed, disabled)
21
- * - CVA variants (size, color variant)
22
- * - asChild composition pattern
23
- * - Motion/animation integration
24
- * - Custom render functions
25
- * - Form field integration
26
- * - Icon composition
27
- * - Glassmorphism effects
20
+ * @module @mks2508/mks-ui/react/ui/DataCard
28
21
  *
29
22
  * @example
30
23
  * ```tsx
31
24
  * <DataCard variant="accent" size="default">
32
- * <DataCardValue number={1234} />
33
- * <DataCardToggle />
25
+ * <DataCardLabel title="Revenue" description="This month" />
26
+ * <DataCardValue number={45678} unit="$" />
34
27
  * </DataCard>
35
28
  * ```
36
29
  */
37
- function DataCard({ variant = "default", size = "default", disabled = false, glass = true, showBrackets = true, className, style, slots, config, children, ...props }) {
30
+ const [DataCardProvider, useDataCard] = getStrictContext("DataCardContext");
31
+ /**
32
+ * Root card container with glassmorphism, corner brackets, and state tracking.
33
+ *
34
+ * @param props - {@link IDataCardProps}
35
+ * @returns A motion.div card with context provider
36
+ */
37
+ function DataCard({ variant = "default", size = "default", disabled = false, glass = true, showBrackets = true, className, style, slots, config, children }) {
38
38
  const [state, setState] = React$1.useState({
39
39
  hovered: false,
40
40
  pressed: false,
41
- disabled: disabled ?? false
41
+ disabled
42
42
  });
43
43
  const contextValue = React$1.useMemo(() => ({
44
44
  variant,
45
45
  size,
46
- disabled: disabled ?? false,
46
+ disabled,
47
47
  glass,
48
- slots,
49
- setState
48
+ config,
49
+ slots
50
50
  }), [
51
51
  variant,
52
52
  size,
53
53
  disabled,
54
54
  glass,
55
+ config,
55
56
  slots
56
57
  ]);
57
- const computedClassName = React$1.useMemo(() => {
58
- if (typeof className === "function") return className(state);
59
- return className;
60
- }, [className, state]);
61
- const computedStyle = React$1.useMemo(() => {
62
- if (typeof style === "function") return style(state);
63
- return style;
64
- }, [style, state]);
58
+ const computedClassName = typeof className === "function" ? className(state) : className;
59
+ const computedStyle = typeof style === "function" ? style(state) : style;
65
60
  return /* @__PURE__ */ jsx(DataCardProvider, {
66
61
  value: contextValue,
67
62
  children: /* @__PURE__ */ jsxs(motion.div, {
@@ -74,29 +69,28 @@ function DataCard({ variant = "default", size = "default", disabled = false, gla
74
69
  variant,
75
70
  size,
76
71
  glass
77
- }), slots?.root, computedClassName, state.hovered && dataCardStateStyles.hovered, state.pressed && dataCardStateStyles.pressed, disabled && dataCardStateStyles.disabled),
72
+ }), disabled && "opacity-50 pointer-events-none", slots?.root, computedClassName),
78
73
  style: computedStyle,
79
- onHoverStart: () => setState((prev) => ({
80
- ...prev,
74
+ onHoverStart: () => setState((p) => ({
75
+ ...p,
81
76
  hovered: true
82
77
  })),
83
- onHoverEnd: () => setState((prev) => ({
84
- ...prev,
78
+ onHoverEnd: () => setState((p) => ({
79
+ ...p,
85
80
  hovered: false
86
81
  })),
87
- onTapStart: () => setState((prev) => ({
88
- ...prev,
82
+ onTapStart: () => setState((p) => ({
83
+ ...p,
89
84
  pressed: true
90
85
  })),
91
- onTap: () => setState((prev) => ({
92
- ...prev,
86
+ onTap: () => setState((p) => ({
87
+ ...p,
93
88
  pressed: false
94
89
  })),
95
- onTapCancel: () => setState((prev) => ({
96
- ...prev,
90
+ onTapCancel: () => setState((p) => ({
91
+ ...p,
97
92
  pressed: false
98
93
  })),
99
- ...props,
100
94
  children: [showBrackets && /* @__PURE__ */ jsxs(Fragment, { children: [
101
95
  /* @__PURE__ */ jsx(DataCardBracket, {
102
96
  position: "tl",
@@ -119,22 +113,19 @@ function DataCard({ variant = "default", size = "default", disabled = false, gla
119
113
  });
120
114
  }
121
115
  /**
122
- * DataCardValue displays animated number with CountingNumber.
116
+ * Animated number display with optional label and unit.
123
117
  *
124
- * @example
125
- * ```tsx
126
- * <DataCardValue
127
- * number={1234}
128
- * label="Active Users"
129
- * unit="users"
130
- * />
131
- * ```
118
+ * @param props - {@link IDataCardValueProps}
119
+ * @returns Animated number with CountingNumber primitive
132
120
  */
133
121
  function DataCardValue({ number, label, unit, decimalPlaces = 0, padStart = false }) {
134
- const { variant, size } = useDataCard();
122
+ const { size, config, slots } = useDataCard();
123
+ const stiffness = config?.counterStiffness ?? 90;
124
+ const damping = config?.counterDamping ?? 10;
125
+ const sizeClass = size === "compact" ? "text-3xl font-bold" : size === "spacious" ? "text-5xl font-bold" : "text-4xl font-bold";
135
126
  return /* @__PURE__ */ jsxs("div", {
136
127
  "data-slot": "data-card-value",
137
- className: cn(dataCardStyles.value),
128
+ className: cn(dataCardStyles.value, slots?.value),
138
129
  children: [
139
130
  label && /* @__PURE__ */ jsx("span", {
140
131
  className: "text-muted-foreground text-xs uppercase tracking-wider font-mono",
@@ -145,10 +136,10 @@ function DataCardValue({ number, label, unit, decimalPlaces = 0, padStart = fals
145
136
  decimalPlaces,
146
137
  padStart,
147
138
  transition: {
148
- stiffness: variant === "accent" ? 150 : 90,
149
- damping: variant === "accent" ? 20 : 10
139
+ stiffness,
140
+ damping
150
141
  },
151
- className: size === "compact" ? "text-3xl font-bold" : size === "spacious" ? "text-5xl font-bold" : "text-4xl font-bold"
142
+ className: sizeClass
152
143
  }),
153
144
  unit && /* @__PURE__ */ jsx("span", {
154
145
  className: "text-muted-foreground text-lg ml-1",
@@ -158,15 +149,10 @@ function DataCardValue({ number, label, unit, decimalPlaces = 0, padStart = fals
158
149
  });
159
150
  }
160
151
  /**
161
- * DataCardLabel displays title and description.
152
+ * Title + description label.
162
153
  *
163
- * @example
164
- * ```tsx
165
- * <DataCardLabel
166
- * title="Revenue"
167
- * description="This month"
168
- * />
169
- * ```
154
+ * @param props - {@link IDataCardLabelProps}
155
+ * @returns Title and optional description text
170
156
  */
171
157
  function DataCardLabel({ title, description, className, ...props }) {
172
158
  const { slots } = useDataCard();
@@ -184,60 +170,36 @@ function DataCardLabel({ title, description, className, ...props }) {
184
170
  });
185
171
  }
186
172
  /**
187
- * DataCardToggle interactive switch element.
173
+ * Switch toggle control inside a DataCard.
188
174
  *
189
- * Uses asChild pattern for composition with Switch component.
190
- *
191
- * @example
192
- * ```tsx
193
- * <DataCardToggle
194
- * checked={enabled}
195
- * onCheckedChange={setEnabled}
196
- * label="Enable notifications"
197
- * checkedIcon={<BellIcon />}
198
- * uncheckedIcon={<BellOffIcon />}
199
- * />
200
- * ```
175
+ * @param props - {@link IDataCardToggleProps}
176
+ * @returns Label + Switch toggle
201
177
  */
202
- function DataCardToggle({ checked, onCheckedChange, checkedIcon, uncheckedIcon, label }) {
203
- const { disabled, variant, size, glass, slots } = useDataCard();
178
+ function DataCardToggle({ checked, onCheckedChange, label }) {
179
+ const { disabled, slots } = useDataCard();
204
180
  return /* @__PURE__ */ jsxs("div", {
205
181
  "data-slot": "data-card-toggle",
206
182
  className: cn(dataCardStyles.toggle, slots?.toggle),
207
183
  children: [label && /* @__PURE__ */ jsx("span", {
208
184
  className: "text-sm text-muted-foreground",
209
185
  children: label
210
- }), /* @__PURE__ */ jsxs(Switch, {
186
+ }), /* @__PURE__ */ jsx(Switch, {
211
187
  checked,
212
188
  onCheckedChange,
213
189
  disabled,
214
- children: [
215
- /* @__PURE__ */ jsx(SwitchThumb, {}),
216
- checkedIcon && /* @__PURE__ */ jsx("div", {
217
- className: "text-primary",
218
- children: checkedIcon
219
- }),
220
- !checkedIcon && /* @__PURE__ */ jsx("div", {
221
- className: "text-muted-foreground",
222
- children: uncheckedIcon
223
- })
224
- ]
190
+ nativeButton: true,
191
+ children: /* @__PURE__ */ jsx(SwitchThumb, {})
225
192
  })]
226
193
  });
227
194
  }
228
195
  /**
229
- * DataCardActions container for action buttons.
196
+ * Action button container.
230
197
  *
231
- * @example
232
- * ```tsx
233
- * <DataCardActions align="end">
234
- * <Button size="sm">Edit</Button>
235
- * <Button size="sm" variant="ghost">Delete</Button>
236
- * </DataCardActions>
237
- * ```
198
+ * @param props - {@link IDataCardActionsProps}
199
+ * @returns Flex container for action buttons
238
200
  */
239
201
  function DataCardActions({ align = "end", className, children, ...props }) {
240
- const { variant, size, glass, slots } = useDataCard();
202
+ const { slots } = useDataCard();
241
203
  return /* @__PURE__ */ jsx("div", {
242
204
  "data-slot": "data-card-actions",
243
205
  "data-align": align,
@@ -247,28 +209,24 @@ function DataCardActions({ align = "end", className, children, ...props }) {
247
209
  });
248
210
  }
249
211
  /**
250
- * DataCardBracket — decorative corner bracket.
212
+ * Decorative corner bracket. Rendered directly via CornerBracket
213
+ * (no wrapper div — the SVG positions itself via absolute positioning).
251
214
  *
252
- * Wraps CornerBracket with motion animations.
253
- *
254
- * @example
255
- * ```tsx
256
- * <DataCardBracket position="tl" variant="accent" />
257
- * ```
215
+ * @param props - {@link IDataCardBracketProps}
216
+ * @returns CornerBracket SVG at the specified corner
258
217
  */
259
- function DataCardBracket({ position, variant = "default", className }) {
260
- const { size, glass, slots } = useDataCard();
261
- return /* @__PURE__ */ jsx("div", {
262
- className: cn(dataCardStyles.bracket, slots?.bracket, className),
263
- children: /* @__PURE__ */ jsx(CornerBracket, {
264
- position,
265
- variant,
266
- size: {
267
- compact: 16,
268
- default: 20,
269
- spacious: 24
270
- }[size ?? "default"]
271
- })
218
+ function DataCardBracket({ position, variant = "default" }) {
219
+ const { size, config } = useDataCard();
220
+ const animate = config?.animateBrackets ?? true;
221
+ return /* @__PURE__ */ jsx(CornerBracket, {
222
+ position,
223
+ variant,
224
+ size: {
225
+ compact: 16,
226
+ default: 20,
227
+ spacious: 24
228
+ }[size ?? "default"],
229
+ className: cn(animate && "opacity-60 group-hover/data-card:opacity-100 transition-opacity", !animate && "opacity-50")
272
230
  });
273
231
  }
274
232
 
@@ -0,0 +1,376 @@
1
+ /**
2
+ * DynamicToggle — CSS state transitions.
3
+ *
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';
8
+ */
9
+
10
+ /* ── Variables ── */
11
+ [data-slot="dt-root"] {
12
+ --dt-dur: 0.22s;
13
+ --dt-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
14
+ --dt-fade: 0.45;
15
+ --dt-indicator-dur: 0.3s;
16
+ --dt-indicator-ease: cubic-bezier(0.4, 0, 0.2, 1);
17
+ }
18
+
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);
22
+ }
23
+
24
+ /* ── Top-level option spans 2 grid cols ── */
25
+ [data-slot="dt-root"] [data-slot="dt-track"] > label {
26
+ grid-column: span 2;
27
+ }
28
+
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;
33
+ }
34
+ [data-slot="dt-root"] [data-slot="dt-track"]:not(:has(> input:checked)) > label {
35
+ color: var(--foreground);
36
+ opacity: var(--dt-fade);
37
+ }
38
+
39
+ /* ── Group: container queries ── */
40
+ [data-slot="dt-root"] [data-slot="dt-group"] {
41
+ container-type: size;
42
+ overflow: hidden;
43
+ }
44
+
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
+ }
87
+ }
88
+
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
+ }
122
+ }
123
+
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;
148
+ }
149
+
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%;
166
+ translate: -50% -80%;
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);
177
+ }
178
+
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);
193
+ }
194
+ [data-slot="dt-group"]:not([data-opts])::after {
195
+ content: none;
196
+ }
197
+
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);
207
+ }
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);
215
+ }
216
+
217
+ /* ── Collapsed mode: "title" — only ::before, labels slide+scale out ── */
218
+ [data-slot="dt-group"][data-collapsed="title"]::before {
219
+ translate: -50% -50%;
220
+ }
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;
230
+ }
231
+
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;
247
+ }
248
+
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%;
263
+ }
264
+
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
+ }
273
+ [data-slot="dt-group"]:has(input:checked) label {
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;
280
+ }
281
+ [data-slot="dt-group"]:has(input:nth-of-type(1):checked) label:nth-of-type(1),
282
+ [data-slot="dt-group"]:has(input:nth-of-type(2):checked) label:nth-of-type(2) {
283
+ color: var(--foreground);
284
+ opacity: 1;
285
+ }
286
+
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;
319
+ }
320
+
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;
327
+ }
328
+
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%;
365
+ scale: 1;
366
+ }
367
+
368
+ /* ── Path morph mode ── */
369
+ [data-slot="dt-root"][data-morph="path"] {
370
+ background: transparent;
371
+ border-color: transparent;
372
+ }
373
+ [data-slot="dt-root"][data-morph="path"] [data-slot="dt-track"] {
374
+ position: relative;
375
+ z-index: 1;
376
+ }