@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.
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +21 -4
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +2 -2
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +163 -32
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +7 -0
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +6 -1
- package/dist/react-ui/ui/DynamicToggle/{DynamicToggle-Cm6-VceQ.css → DynamicToggle-DOR3Ld-k.css} +104 -32
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +105 -32
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +2 -2
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +6 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.js +23 -5
- package/package.json +1 -1
- package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +21 -3
- package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +177 -40
- package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +9 -0
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +105 -32
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +2 -2
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +6 -0
- package/src/react-ui/ui/DynamicToggle/index.tsx +30 -8
- package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +413 -0
- package/src/react-ui/ui/DynamicToggle/prototype-v8-gooey-safari.html +560 -0
- 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(--
|
|
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
|
-
/*
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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-
|
|
69
|
-
|
|
70
|
-
|
|
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-
|
|
73
|
-
|
|
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-
|
|
76
|
-
|
|
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-
|
|
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-
|
|
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;
|
|
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;
|
|
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 =
|
|
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
|
-
|
|
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
|
@@ -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
|
-
/**
|
|
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 —
|
|
4
|
+
* GooeyCanvas — SVG-based gooey filter with animated pill + bubble rects.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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 {
|
|
29
|
-
import { computeBlur,
|
|
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 —
|
|
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
|
-
|
|
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:
|
|
75
|
+
filter: `${filterUrl} ${outlineShadow}`,
|
|
76
|
+
transform: 'translateZ(0)',
|
|
77
|
+
contain: 'layout style' as const,
|
|
52
78
|
}),
|
|
53
|
-
[
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
+
|