@mks2508/mks-ui 0.5.4 → 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 (26) hide show
  1. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +21 -4
  2. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -1
  3. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +2 -2
  4. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -1
  5. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +163 -32
  6. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +7 -0
  7. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -1
  8. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +6 -1
  9. package/dist/react-ui/ui/DynamicToggle/{DynamicToggle-Cm6-VceQ.css → DynamicToggle-DOR3Ld-k.css} +104 -32
  10. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +105 -32
  11. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +2 -2
  12. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +6 -0
  13. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
  14. package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
  15. package/dist/react-ui/ui/DynamicToggle/index.js +23 -5
  16. package/package.json +1 -1
  17. package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +21 -3
  18. package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +177 -40
  19. package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +9 -0
  20. package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +105 -32
  21. package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +2 -2
  22. package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +6 -0
  23. package/src/react-ui/ui/DynamicToggle/index.tsx +30 -8
  24. package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +413 -0
  25. package/src/react-ui/ui/DynamicToggle/prototype-v8-gooey-safari.html +560 -0
  26. package/src/react-ui/ui/DynamicToggle/prototype-v8b-react-structure.html +227 -0
@@ -12,6 +12,8 @@
12
12
  --dt-dur: 0.22s;
13
13
  --dt-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
14
14
  --dt-fade: 0.45;
15
+ --dt-indicator-dur: 0.3s;
16
+ --dt-indicator-ease: cubic-bezier(0.4, 0, 0.2, 1);
15
17
  }
16
18
 
17
19
  /* ── Track: explicit row prevents h-full items from overflowing container ── */
@@ -24,18 +26,9 @@
24
26
  grid-column: span 2;
25
27
  }
26
28
 
27
- /* ── Main indicator slide ── */
28
- [data-slot="dt-root"] [data-slot="dt-indicator"] {
29
- transition: translate var(--dt-dur) var(--dt-ease);
30
- translate: 100% 0;
31
- }
32
- [data-slot="dt-root"] [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-indicator"] {
33
- translate: 0 0;
34
- }
35
-
36
29
  /* ── Primary option text ── */
37
30
  [data-slot="dt-root"] [data-slot="dt-track"]:has(> input:checked) > label {
38
- color: var(--card);
31
+ color: var(--accent-foreground);
39
32
  z-index: 2;
40
33
  }
41
34
  [data-slot="dt-root"] [data-slot="dt-track"]:not(:has(> input:checked)) > label {
@@ -49,31 +42,109 @@
49
42
  overflow: hidden;
50
43
  }
51
44
 
52
- /* ── Group indicator: clip-path reveal ── */
53
- [data-slot="dt-root"] [data-slot="dt-group-indicator"] {
54
- pointer-events: none;
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;
55
130
  transition:
56
- translate var(--dt-dur) var(--dt-ease),
57
- clip-path var(--dt-dur) var(--dt-ease),
58
- background var(--dt-dur) var(--dt-ease);
59
- clip-path: inset(
60
- 73cqh calc(50% + 1px) calc(27cqh - 2px) calc(50% - 3px)
61
- round var(--dt-radius, 9999px)
62
- );
63
- translate: -50% 0;
64
- }
65
- [data-slot="dt-root"] [data-slot="dt-track"]:has(> input:checked) [data-slot="dt-group-indicator"] {
66
- background: transparent;
131
+ left var(--dt-indicator-dur) var(--dt-indicator-ease),
132
+ right var(--dt-indicator-dur) var(--dt-indicator-ease);
67
133
  }
68
- [data-slot="dt-root"] [data-slot="dt-group"]:has(input:checked) [data-slot="dt-group-indicator"] {
69
- background: var(--card);
70
- clip-path: inset(0 0 0 0 round var(--dt-radius, 9999px));
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%;
71
137
  }
72
- [data-slot="dt-root"] [data-slot="dt-group"]:has(input:nth-of-type(1):checked) [data-slot="dt-group-indicator"] {
73
- translate: -100% 0;
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%;
74
141
  }
75
- [data-slot="dt-root"] [data-slot="dt-group"]:has(input:nth-of-type(2):checked) [data-slot="dt-group-indicator"] {
76
- translate: 0 0;
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;
77
148
  }
78
149
 
79
150
  /* ══════════════════════════════════════════════════════════
@@ -233,6 +304,8 @@
233
304
  background: var(--card);
234
305
  border: 1px solid var(--border);
235
306
  z-index: 3;
307
+ transform: translateZ(0);
308
+ -webkit-transform: translateZ(0);
236
309
  }
237
310
  [data-slot="dt-group-label"] > span {
238
311
  overflow: hidden;
@@ -300,4 +373,4 @@
300
373
  [data-slot="dt-root"][data-morph="path"] [data-slot="dt-track"] {
301
374
  position: relative;
302
375
  z-index: 1;
303
- }
376
+ }
@@ -19,9 +19,9 @@ const dynamicToggleStyles = {
19
19
  root: "relative border p-[2px] select-none",
20
20
  track: "relative grid grid-cols-[repeat(4,1fr)] place-items-center w-full h-full",
21
21
  option: "inline-grid place-items-center cursor-pointer font-medium z-[2] h-full w-full whitespace-nowrap",
22
- indicator: "absolute w-1/2 left-0 top-0 bottom-0 bg-foreground rounded-[var(--dt-radius,9999px)] pointer-events-none z-0",
22
+ indicator: "absolute w-1/2 left-0 top-0 bottom-0 bg-accent rounded-[var(--dt-radius,9999px)] pointer-events-none z-0",
23
23
  group: "col-span-2 relative w-full h-full grid grid-cols-2",
24
- groupIndicator: "absolute left-1/2 top-0 bottom-0 w-1/2 bg-foreground rounded-[var(--dt-radius,9999px)] pointer-events-none z-0",
24
+ groupIndicator: "absolute left-1/2 top-0 bottom-0 w-1/2 bg-accent rounded-[var(--dt-radius,9999px)] pointer-events-none z-0",
25
25
  groupLabel: [
26
26
  "absolute",
27
27
  "flex items-center justify-center",
@@ -30,6 +30,12 @@ export interface IDynamicToggleConfig extends IBaseConfig {
30
30
  labelAnimation?: 'morph' | 'float' | 'none';
31
31
  /** Gooey morph mode for the pill↔groupLabel junction (default: 'none') */
32
32
  morphMode?: DynamicToggleMorphMode;
33
+ /** Indicator slide duration in seconds (default: 0.3) */
34
+ indicatorDuration?: number;
35
+ /** Indicator slide easing (default: material standard cubic-bezier) */
36
+ indicatorEasing?: string;
37
+ /** Force translate-based indicator instead of CSS Anchor (debug/compat) */
38
+ forceTranslateIndicator?: boolean;
33
39
  }
34
40
  /**
35
41
  * Context shared between DynamicToggle root and its children.
@@ -1 +1 @@
1
- {"version":3,"file":"DynamicToggle.types.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAM3F,uEAAuE;AACvE,MAAM,MAAM,0BAA0B,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;AAEzE,wDAAwD;AACxD,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAMhE;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAqB,SAAQ,WAAW;IACvD,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IAC5C,0EAA0E;IAC1E,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAMD;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,WAAW,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,aAAa,EAAE,CACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,EACrC,aAAa,EAAE,0BAA0B,KACtC,IAAI,CAAC;CACX,CAAC;AAMF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACzC,uCAAuC;IACvC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,wBAAwB;IACvC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,8DAA8D;IAC9D,aAAa,CAAC,EAAE,0BAA0B,CAAC;IAC3C,6CAA6C;IAC7C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"DynamicToggle.types.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/ui/DynamicToggle/index.tsx"],"names":[],"mappings":"AA8BA,OAAO,qBAAqB,CAAC;AAC7B,OAAO,EAAuB,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,KAAK,EACV,wBAAwB,EAExB,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAM/B,QAAA,MAA8B,gBAAgB,gCACsB,CAAC;AAuBrE;;GAEG;AACH,iBAAS,aAAa,CAAC,EACrB,OAAO,EACP,IAAI,EACJ,KAAK,EACL,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,SAAS,EACT,QAAQ,EACR,YAAY,EAAE,SAAS,EACvB,GAAG,KAAK,EACT,EAAE,mBAAmB,2CAuFrB;kBAlGQ,aAAa;;;AAwGtB;;GAEG;AACH,iBAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,yBAAyB,2CA2BrF;kBA3BQ,mBAAmB;;;AAiC5B;;;GAGG;AACH,iBAAS,kBAAkB,CAAC,EAC1B,KAAK,EACL,aAAqB,EACrB,aAAuB,EACvB,QAAQ,EACR,SAAS,GACV,EAAE,wBAAwB,2CA0C1B;kBAhDQ,kBAAkB;;;AA2D3B,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,CAAC;AAEF,YAAY,EACV,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-ui/ui/DynamicToggle/index.tsx"],"names":[],"mappings":"AA8BA,OAAO,qBAAqB,CAAC;AAC7B,OAAO,EAAuB,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,KAAK,EACV,wBAAwB,EAExB,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAM/B,QAAA,MAA8B,gBAAgB,gCACsB,CAAC;AAmCrE;;GAEG;AACH,iBAAS,aAAa,CAAC,EACrB,OAAO,EACP,IAAI,EACJ,KAAK,EACL,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,SAAS,EACT,QAAQ,EACR,YAAY,EAAE,SAAS,EACvB,GAAG,KAAK,EACT,EAAE,mBAAmB,2CAiGrB;kBA5GQ,aAAa;;;AAkHtB;;GAEG;AACH,iBAAS,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,yBAAyB,2CA2BrF;kBA3BQ,mBAAmB;;;AAiC5B;;;GAGG;AACH,iBAAS,kBAAkB,CAAC,EAC1B,KAAK,EACL,aAAqB,EACrB,aAAuB,EACvB,QAAQ,EACR,SAAS,GACV,EAAE,wBAAwB,2CA0C1B;kBAhDQ,kBAAkB;;;AA2D3B,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,CAAC;AAEF,YAAY,EACV,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -39,6 +39,16 @@ const SIZE_HEIGHT_PX = {
39
39
  default: 38,
40
40
  lg: 44
41
41
  };
42
+ const SIZE_WIDTH_PX = {
43
+ sm: 210,
44
+ default: 260,
45
+ lg: 320
46
+ };
47
+ const SHAPE_RADIUS = {
48
+ pill: 9999,
49
+ rounded: 12,
50
+ square: 6
51
+ };
42
52
  /**
43
53
  * Root container — pill-shaped toggle with expanding sub-options and group label.
44
54
  */
@@ -65,7 +75,12 @@ function DynamicToggle({ variant, size, shape, slots, config, disabled = false,
65
75
  const effectiveMorphMode = resolvedVariant === "ghost" || resolvedVariant === "outline" ? "none" : morphMode;
66
76
  const showGroupLabel = (config?.labelAnimation ?? "morph") !== "none" && groupPosition !== "hidden" && groupLabel;
67
77
  const heightPx = SIZE_HEIGHT_PX[size ?? "default"] ?? 32;
68
- const style = config?.duration ? { "--dt-dur": `${config.duration}s` } : void 0;
78
+ const style = {
79
+ ...config?.duration && { "--dt-dur": `${config.duration}s` },
80
+ ...config?.indicatorDuration && { "--dt-indicator-dur": `${config.indicatorDuration}s` },
81
+ ...config?.indicatorEasing && { "--dt-indicator-ease": config.indicatorEasing }
82
+ };
83
+ const hasStyle = Object.keys(style).length > 0;
69
84
  const groupLabelElement = showGroupLabel ? /* @__PURE__ */ jsx("div", {
70
85
  "data-slot": "dt-group-label",
71
86
  "data-position": groupPosition,
@@ -84,21 +99,24 @@ function DynamicToggle({ variant, size, shape, slots, config, disabled = false,
84
99
  children: /* @__PURE__ */ jsxs("div", {
85
100
  "data-slot": "dt-root",
86
101
  "data-morph": effectiveMorphMode !== "none" ? effectiveMorphMode : void 0,
102
+ "data-indicator": config?.forceTranslateIndicator ? "translate" : void 0,
87
103
  "data-group-active": groupActive || void 0,
88
104
  "data-disabled": disabled || void 0,
89
105
  role: "radiogroup",
90
106
  "aria-label": ariaLabel,
91
- style,
107
+ style: hasStyle ? style : void 0,
92
108
  className: cn(dynamicToggleVariants({
93
109
  variant,
94
110
  size,
95
111
  shape
96
112
  }), slots?.root, className),
97
113
  children: [
98
- effectiveMorphMode === "filter" && /* @__PURE__ */ jsxs(GooeyCanvas, {
114
+ effectiveMorphMode === "filter" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(GooeyCanvas, {
99
115
  height: heightPx,
100
- children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-[inherit] bg-card" }), groupLabelElement]
101
- }),
116
+ width: SIZE_WIDTH_PX[size ?? "default"] ?? 260,
117
+ radius: SHAPE_RADIUS[shape ?? "pill"] ?? 9999,
118
+ expanded: groupActive
119
+ }), groupLabelElement] }),
102
120
  effectiveMorphMode === "path" && groupLabelElement,
103
121
  /* @__PURE__ */ jsxs("div", {
104
122
  "data-slot": "dt-track",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mks2508/mks-ui",
3
- "version": "0.5.4",
3
+ "version": "0.5.7",
4
4
  "description": "UI component library - Shadcn/Animate UI based with DevEnv components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -50,8 +50,20 @@ export interface IGooeyFilterProps {
50
50
  export interface IGooeyCanvasProps {
51
51
  /** Blur radius override. If omitted, auto-calculated from `height`. */
52
52
  blur?: number;
53
- /** Element height used to auto-calculate blur (default: 32) */
53
+ /** Pill height in px (default: 32) */
54
54
  height?: number;
55
+ /** Pill width in px (default: 260) */
56
+ width?: number;
57
+ /** Border radius in px (default: 9999) */
58
+ radius?: number;
59
+ /** Fill color (default: 'var(--card)') */
60
+ fillColor?: string;
61
+ /** Bubble height in px when expanded (default: 40% of height) */
62
+ bubbleHeight?: number;
63
+ /** Bubble inset from edges as fraction 0-1 (default: 0.2) */
64
+ bubbleInset?: number;
65
+ /** Whether the bubble is expanded */
66
+ expanded?: boolean;
55
67
  /** Drop-shadow outline blur in px (default: 0.5) */
56
68
  outlineBlur?: number;
57
69
  /** Drop-shadow outline color (default: 'var(--border)') */
@@ -62,10 +74,16 @@ export interface IGooeyCanvasProps {
62
74
  alphaGain?: number;
63
75
  /** Alpha offset override */
64
76
  alphaOffset?: number;
77
+ /** Expand animation duration in ms (default: 550) */
78
+ expandDuration?: number;
79
+ /** Collapse animation duration in ms (default: 400) */
80
+ collapseDuration?: number;
81
+ /** Expand easing override (default: SPRING_GENTLE) */
82
+ expandEasing?: string;
83
+ /** Collapse easing override (default: EASE_OUT_CUBIC) */
84
+ collapseEasing?: string;
65
85
  /** Extra className on the filter container */
66
86
  className?: string;
67
- /** Content to merge with the gooey filter */
68
- children: ReactNode;
69
87
  }
70
88
 
71
89
  // ---------------------------------------------------------------------------
@@ -1,77 +1,214 @@
1
1
  'use client';
2
2
 
3
3
  /**
4
- * GooeyCanvas — container that applies gooey SVG filter to children.
4
+ * GooeyCanvas — SVG-based gooey filter with animated pill + bubble rects.
5
5
  *
6
- * Renders a GooeyFilter + a wrapper div with `filter: url(#id)`.
7
- * All same-colored children inside merge organically. Optional drop-shadow
8
- * outline traces the merged shape's edge.
6
+ * Uses SVG `<rect>` elements inside a filtered container. The bubble rect
7
+ * animates via WAAPI for smooth cross-browser rendering.
9
8
  *
10
- * Blur auto-scales with height: `blur = Math.round(height * 0.15)`.
11
- *
12
- * @example
13
- * ```tsx
14
- * <div style={{ position: 'relative' }}>
15
- * <GooeyCanvas height={40}>
16
- * <div className="absolute inset-0 bg-card rounded-full" />
17
- * <div className="absolute bottom-full bg-card w-32 h-6 rounded-lg" />
18
- * </GooeyCanvas>
19
- * <div className="relative z-10">Content on top (not filtered)</div>
20
- * </div>
21
- * ```
9
+ * Key patterns (derived from Sileo):
10
+ * - Filter applied to canvas div, SVG content inside (not HTML children)
11
+ * - `transform: translateZ(0)` + `contain: layout style` on canvas
12
+ * - No DOM mutation during/after animation — `fill: 'forwards'` only
13
+ * - Readiness gate: 1-frame rAF delay before first animation (Sileo pattern)
22
14
  *
23
15
  * @module @mks2508/mks-ui/react/primitives/waapi/Gooey
24
16
  */
25
17
 
26
18
  import * as React from 'react';
27
19
  import { cn } from '@/react-ui/lib/utils';
28
- import { GooeyFilter } from './GooeyFilter';
29
- import { computeBlur, buildFilterString, GOOEY_DEFAULTS } from './gooey-utils';
20
+ import { EASINGS, getResponsiveDuration } from '@/react-ui/primitives/waapi/core/animationConstants';
21
+ import { computeBlur, GOOEY_TIMING } from './gooey-utils';
30
22
  import type { IGooeyCanvasProps } from './Gooey.types';
31
23
 
32
24
  /**
33
- * GooeyCanvas — container with gooey SVG filter applied.
25
+ * GooeyCanvas — SVG rects + gooey filter, WAAPI-animated bubble.
34
26
  */
35
27
  function GooeyCanvas({
36
28
  blur,
37
29
  height = 32,
30
+ width = 260,
31
+ radius = 9999,
32
+ fillColor = 'var(--card)',
33
+ bubbleHeight: bubbleHeightProp,
34
+ bubbleInset = 0.2,
35
+ expanded = false,
38
36
  outlineBlur = 0.5,
39
37
  outlineColor = 'var(--border)',
40
38
  outlineLayers = 2,
41
39
  alphaGain,
42
40
  alphaOffset,
41
+ expandDuration,
42
+ collapseDuration,
43
+ expandEasing,
44
+ collapseEasing,
43
45
  className,
44
- children,
45
46
  }: IGooeyCanvasProps) {
46
47
  const filterId = React.useId().replace(/:/g, '') + '-goo';
47
48
  const computedBlur = blur ?? computeBlur(height);
49
+ const bubbleRectRef = React.useRef<SVGRectElement>(null);
50
+ const animRef = React.useRef<Animation | null>(null);
51
+ const [ready, setReady] = React.useState(false);
52
+ const lastValues = React.useRef({ y: -1, h: -1 });
53
+
54
+ // Computed geometry
55
+ const pad = 2;
56
+ const effectiveR = Math.min(radius, height / 2);
57
+ const bubbleH = bubbleHeightProp ?? Math.round(height * 0.4);
58
+ const insetPx = width * bubbleInset;
59
+ const bubbleW = width - 2 * insetPx;
60
+ const bubbleR = Math.min(effectiveR * 0.6, bubbleH * 0.45, 12);
61
+ const totalH = height + bubbleH;
62
+
63
+ // Static filter string — never changes after mount (critical for Safari)
64
+ const filterUrl = React.useMemo(() => `url(#${filterId})`, [filterId]);
48
65
 
49
- const filterStyle = React.useMemo(
66
+ // Drop-shadow outline — separate from SVG filter for cleaner compositing
67
+ const outlineShadow = React.useMemo(() => {
68
+ const shadow = `drop-shadow(0 0 ${outlineBlur}px ${outlineColor})`;
69
+ return Array(outlineLayers).fill(shadow).join(' ');
70
+ }, [outlineBlur, outlineColor, outlineLayers]);
71
+
72
+ // Canvas style — Sileo pattern: translateZ(0) + contain + static filter
73
+ const canvasStyle = React.useMemo(
50
74
  () => ({
51
- filter: buildFilterString(filterId, outlineBlur, outlineColor, outlineLayers),
75
+ filter: `${filterUrl} ${outlineShadow}`,
76
+ transform: 'translateZ(0)',
77
+ contain: 'layout style' as const,
52
78
  }),
53
- [filterId, outlineBlur, outlineColor, outlineLayers],
79
+ [filterUrl, outlineShadow],
54
80
  );
55
81
 
82
+ // Readiness gate — Sileo pattern: defer first animation by 1 rAF frame.
83
+ // Ensures the initial DOM state is painted before any animation starts.
84
+ React.useEffect(() => {
85
+ const rect = bubbleRectRef.current;
86
+ if (!rect) return;
87
+
88
+ // Set initial visual state
89
+ const initY = expanded ? 0 : bubbleH;
90
+ const initH = expanded ? bubbleH : 0;
91
+ lastValues.current = { y: initY, h: initH };
92
+ if (expanded) {
93
+ rect.animate([{ y: '0px', height: `${bubbleH}px` }], { duration: 0, fill: 'forwards' });
94
+ }
95
+
96
+ // After 1 painted frame, mark as ready — triggers re-render so animation
97
+ // effect can pick up any expanded changes that arrived during the wait.
98
+ const raf = requestAnimationFrame(() => setReady(true));
99
+ return () => cancelAnimationFrame(raf);
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ }, []);
102
+
103
+ // WAAPI animation — tracks values in ref for smooth mid-animation reversals.
104
+ React.useEffect(() => {
105
+ const rect = bubbleRectRef.current;
106
+ if (!rect || !ready) return;
107
+
108
+ const isExpanding = expanded;
109
+
110
+ // Direction-aware timing
111
+ const baseDuration = isExpanding
112
+ ? (expandDuration ?? GOOEY_TIMING.EXPAND_DURATION)
113
+ : (collapseDuration ?? GOOEY_TIMING.COLLAPSE_DURATION);
114
+ const duration = getResponsiveDuration(baseDuration);
115
+ const easing = isExpanding
116
+ ? (expandEasing ?? EASINGS.SPRING_GENTLE)
117
+ : (collapseEasing ?? EASINGS.EASE_OUT_CUBIC);
118
+
119
+ // Target state
120
+ const toY = isExpanding ? 0 : bubbleH;
121
+ const toH = isExpanding ? bubbleH : 0;
122
+
123
+ // From state: use tracked values (survives animation cancel)
124
+ const fromY = lastValues.current.y;
125
+ const fromH = lastValues.current.h;
126
+
127
+ // Update tracked values to target
128
+ lastValues.current = { y: toY, h: toH };
129
+
130
+ // Reduced motion: instant
131
+ if (duration === 0) {
132
+ if (animRef.current) animRef.current.cancel();
133
+ rect.animate([{ y: `${toY}px`, height: `${toH}px` }], { duration: 0, fill: 'forwards' });
134
+ return;
135
+ }
136
+
137
+ // Cancel previous animation (visual state maintained by lastValues tracking)
138
+ if (animRef.current) animRef.current.cancel();
139
+
140
+ // 2-keyframe animation — fill:forwards, no DOM mutation.
141
+ // CRITICAL: Chrome WAAPI requires "px" units for SVG geometry props.
142
+ animRef.current = rect.animate(
143
+ [
144
+ { y: `${fromY}px`, height: `${fromH}px` },
145
+ { y: `${toY}px`, height: `${toH}px` },
146
+ ],
147
+ { duration, easing, fill: 'forwards' },
148
+ );
149
+ }, [ready, expanded, bubbleH, expandDuration, collapseDuration, expandEasing, collapseEasing]);
150
+
56
151
  return (
57
- <>
58
- <GooeyFilter
59
- id={filterId}
60
- blur={computedBlur}
61
- alphaGain={alphaGain}
62
- alphaOffset={alphaOffset}
63
- />
64
- <div
65
- data-slot="gooey-canvas"
66
- style={filterStyle}
67
- className={cn(
68
- 'absolute inset-0 rounded-[inherit] pointer-events-none z-0 overflow-visible',
69
- className,
70
- )}
152
+ <div
153
+ data-slot="gooey-canvas"
154
+ style={canvasStyle}
155
+ className={cn(
156
+ 'absolute inset-0 rounded-[inherit] pointer-events-none z-0 overflow-visible',
157
+ className,
158
+ )}
159
+ >
160
+ <svg
161
+ data-slot="gooey-svg"
162
+ width={width}
163
+ height={totalH}
164
+ viewBox={`0 0 ${width} ${totalH}`}
165
+ style={{ position: 'absolute', top: -bubbleH, left: 0, overflow: 'visible' }}
166
+ aria-hidden="true"
71
167
  >
72
- {children}
73
- </div>
74
- </>
168
+ <defs>
169
+ <filter
170
+ id={filterId}
171
+ x="-20%"
172
+ y="-20%"
173
+ width="140%"
174
+ height="140%"
175
+ colorInterpolationFilters="sRGB"
176
+ >
177
+ <feGaussianBlur in="SourceGraphic" stdDeviation={computedBlur} result="blur" />
178
+ <feColorMatrix
179
+ in="blur"
180
+ mode="matrix"
181
+ values={`1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${alphaGain ?? 20} ${alphaOffset ?? -10}`}
182
+ result="goo"
183
+ />
184
+ <feComposite in="SourceGraphic" in2="goo" operator="atop" />
185
+ </filter>
186
+ </defs>
187
+
188
+ {/* Pill rect — static background */}
189
+ <rect
190
+ x={pad}
191
+ y={bubbleH}
192
+ width={width - pad * 2}
193
+ height={height - pad * 2}
194
+ rx={effectiveR}
195
+ ry={effectiveR}
196
+ fill={fillColor}
197
+ />
198
+
199
+ {/* Bubble rect — always collapsed in DOM, WAAPI controls visual state */}
200
+ <rect
201
+ ref={bubbleRectRef}
202
+ x={insetPx}
203
+ y={bubbleH}
204
+ width={bubbleW}
205
+ height={0}
206
+ rx={bubbleR}
207
+ ry={bubbleR}
208
+ fill={fillColor}
209
+ />
210
+ </svg>
211
+ </div>
75
212
  );
76
213
  }
77
214
 
@@ -8,6 +8,14 @@
8
8
  // Constants
9
9
  // ---------------------------------------------------------------------------
10
10
 
11
+ /** Gooey animation timing constants. */
12
+ export const GOOEY_TIMING = {
13
+ /** Expand duration in ms — spring-like with room for overshoot */
14
+ EXPAND_DURATION: 550,
15
+ /** Collapse duration in ms — snappier settle, no overshoot */
16
+ COLLAPSE_DURATION: 400,
17
+ } as const;
18
+
11
19
  /** Default gooey filter parameters. */
12
20
  export const GOOEY_DEFAULTS = {
13
21
  /** Blur = height * BLUR_RATIO */
@@ -242,3 +250,4 @@ export function morphPathUp(
242
250
  /** Memoized versions for animation performance. */
243
251
  export const morphPathDownMemo = memoizePath(morphPathDown);
244
252
  export const morphPathUpMemo = memoizePath(morphPathUp);
253
+