@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.
- package/dist/react-ui/index.js +8 -3
- package/dist/react-ui/primitives/index.js +5 -0
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +120 -0
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +10 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +190 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts +7 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.js +78 -0
- package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts +7 -0
- package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/MorphPath.js +51 -0
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +94 -0
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +182 -0
- package/dist/react-ui/primitives/waapi/Gooey/index.d.ts +28 -0
- package/dist/react-ui/primitives/waapi/Gooey/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/index.js +5 -0
- package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts +7 -0
- package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.js +47 -0
- package/dist/react-ui/primitives/waapi/index.d.ts +2 -0
- package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/index.js +6 -0
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +26 -16
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -1
- package/dist/react-ui/ui/DataCard/DataCard.styles.js +36 -74
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +50 -70
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -1
- package/dist/react-ui/ui/DataCard/index.d.ts +24 -93
- package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -1
- package/dist/react-ui/ui/DataCard/index.js +76 -118
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle-DOR3Ld-k.css +376 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +376 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.js +0 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts +20 -8
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +55 -27
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +69 -14
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.d.ts +22 -20
- package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.js +133 -96
- package/dist/react-ui/ui/Switch/index.js +1 -1
- package/dist/react-ui/ui/index.js +2 -2
- package/package.json +2 -2
- package/src/css.d.ts +1 -0
- package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +141 -0
- package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +217 -0
- package/src/react-ui/primitives/waapi/Gooey/GooeyFilter.tsx +77 -0
- package/src/react-ui/primitives/waapi/Gooey/MorphPath.tsx +58 -0
- package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +253 -0
- package/src/react-ui/primitives/waapi/Gooey/index.ts +50 -0
- package/src/react-ui/primitives/waapi/Gooey/useMorphPath.ts +48 -0
- package/src/react-ui/primitives/waapi/index.ts +23 -0
- package/src/react-ui/ui/DataCard/DataCard.styles.ts +45 -101
- package/src/react-ui/ui/DataCard/DataCard.types.ts +52 -73
- package/src/react-ui/ui/DataCard/index.tsx +118 -184
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +320 -94
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +60 -40
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +101 -14
- package/src/react-ui/ui/DynamicToggle/index.tsx +172 -96
- package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +413 -0
- package/src/react-ui/ui/DynamicToggle/prototype-v7.html +615 -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
- package/src/react-ui/ui/DynamicToggle/prototype.html +419 -0
- package/src/react-ui/ui/Switch/index.tsx +1 -1
- /package/dist/react-ui/blocks/Terminal/panel/{terminal-filter-dropdown.module-DAcl_XQZ.css → terminal-filter-dropdown.module-C6oDcFBS.css} +0 -0
- /package/dist/react-ui/blocks/Terminal/panel/{terminal-session-tabs.module-DNAop5e3.css → terminal-session-tabs.module-D_-sgyza.css} +0 -0
- /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-BJrjXisF.css → morphing-popover.module-B1ftlaYj.css} +0 -0
|
@@ -1,14 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DynamicToggle type definitions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Uses hidden
|
|
4
|
+
* CSS-animated toggle where one option expands into sub-options.
|
|
5
|
+
* Uses hidden radios + `:has(:checked)` for zero-JS animation.
|
|
6
|
+
* Optional gooey morph via `GooeyCanvas` or `MorphPath` primitives.
|
|
6
7
|
*
|
|
7
8
|
* @module @mks2508/mks-ui/react/ui/DynamicToggle
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import type { SlotOverrides } from '@/core/types';
|
|
11
|
-
import type { DynamicToggleSlot } from './DynamicToggle.styles';
|
|
11
|
+
import type { SlotOverrides, IBaseConfig } from '@/core/types';
|
|
12
|
+
import type { DynamicToggleSlot, DynamicToggleVariantProps } from './DynamicToggle.styles';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Shared types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** How the group appears when collapsed (standalone option active). */
|
|
19
|
+
export type DynamicToggleCollapsedMode = 'title' | 'opts' | 'title-opts';
|
|
20
|
+
|
|
21
|
+
/** Morph technique for the pill↔groupLabel junction. */
|
|
22
|
+
export type DynamicToggleMorphMode = 'none' | 'filter' | 'path';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Config
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for DynamicToggle animation behavior.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <DynamicToggle config={{ morphMode: 'filter', duration: 0.3 }}>
|
|
34
|
+
* ...
|
|
35
|
+
* </DynamicToggle>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface IDynamicToggleConfig extends IBaseConfig {
|
|
39
|
+
/** CSS transition duration in seconds (default: 0.22) */
|
|
40
|
+
duration?: number;
|
|
41
|
+
/** Label animation style (default: 'morph') */
|
|
42
|
+
labelAnimation?: 'morph' | 'float' | 'none';
|
|
43
|
+
/** Gooey morph mode for the pill↔groupLabel junction (default: 'none') */
|
|
44
|
+
morphMode?: DynamicToggleMorphMode;
|
|
45
|
+
/** Indicator slide duration in seconds (default: 0.3) */
|
|
46
|
+
indicatorDuration?: number;
|
|
47
|
+
/** Indicator slide easing (default: material standard cubic-bezier) */
|
|
48
|
+
indicatorEasing?: string;
|
|
49
|
+
/** Force translate-based indicator instead of CSS Anchor (debug/compat) */
|
|
50
|
+
forceTranslateIndicator?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Context
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
12
56
|
|
|
13
57
|
/**
|
|
14
58
|
* Context shared between DynamicToggle root and its children.
|
|
@@ -22,37 +66,66 @@ export type DynamicToggleContextType = {
|
|
|
22
66
|
groupName: string;
|
|
23
67
|
/** Whether a group child is active */
|
|
24
68
|
groupActive: boolean;
|
|
69
|
+
/** Whether the toggle is disabled */
|
|
70
|
+
disabled: boolean;
|
|
71
|
+
/** Register group info (called by DynamicToggleGroup on mount) */
|
|
72
|
+
registerGroup: (
|
|
73
|
+
label: string,
|
|
74
|
+
values: string[],
|
|
75
|
+
position: 'top' | 'bottom' | 'hidden',
|
|
76
|
+
collapsedMode: DynamicToggleCollapsedMode,
|
|
77
|
+
) => void;
|
|
25
78
|
};
|
|
26
79
|
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Root
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
27
84
|
/**
|
|
28
85
|
* Props for the DynamicToggle root container.
|
|
29
86
|
*
|
|
87
|
+
* Supports exactly one `DynamicToggleOption` + one `DynamicToggleGroup`.
|
|
88
|
+
* The group expands into sub-options when one of its children is active.
|
|
89
|
+
*
|
|
30
90
|
* @example
|
|
31
91
|
* ```tsx
|
|
32
|
-
* <DynamicToggle
|
|
92
|
+
* <DynamicToggle
|
|
93
|
+
* value="tree" onValueChange={setVal}
|
|
94
|
+
* size="sm" config={{ morphMode: 'filter' }}
|
|
95
|
+
* >
|
|
33
96
|
* <DynamicToggleOption value="tree">Tree</DynamicToggleOption>
|
|
34
|
-
* <DynamicToggleGroup label="Changes">
|
|
97
|
+
* <DynamicToggleGroup label="Changes" collapsedMode="title">
|
|
35
98
|
* <DynamicToggleOption value="flat">Flat</DynamicToggleOption>
|
|
36
99
|
* <DynamicToggleOption value="grouped">Grouped</DynamicToggleOption>
|
|
37
100
|
* </DynamicToggleGroup>
|
|
38
101
|
* </DynamicToggle>
|
|
39
102
|
* ```
|
|
40
103
|
*/
|
|
41
|
-
export interface IDynamicToggleProps {
|
|
104
|
+
export interface IDynamicToggleProps extends DynamicToggleVariantProps {
|
|
42
105
|
/** Controlled value */
|
|
43
106
|
value?: string;
|
|
44
107
|
/** Uncontrolled default value */
|
|
45
108
|
defaultValue?: string;
|
|
46
109
|
/** Change callback */
|
|
47
110
|
onValueChange?: (value: string) => void;
|
|
111
|
+
/** Disable the entire toggle */
|
|
112
|
+
disabled?: boolean;
|
|
48
113
|
/** Slot class overrides */
|
|
49
114
|
slots?: SlotOverrides<DynamicToggleSlot>;
|
|
115
|
+
/** Animation/behavior configuration */
|
|
116
|
+
config?: IDynamicToggleConfig;
|
|
117
|
+
/** Accessible label for the radio group */
|
|
118
|
+
'aria-label'?: string;
|
|
50
119
|
/** Additional class for the root */
|
|
51
120
|
className?: string;
|
|
52
121
|
/** DynamicToggleOption and DynamicToggleGroup children */
|
|
53
122
|
children: React.ReactNode;
|
|
54
123
|
}
|
|
55
124
|
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Option
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
56
129
|
/**
|
|
57
130
|
* Props for a single toggle option (top-level or inside a group).
|
|
58
131
|
*
|
|
@@ -70,23 +143,37 @@ export interface IDynamicToggleOptionProps {
|
|
|
70
143
|
className?: string;
|
|
71
144
|
}
|
|
72
145
|
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Group
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
73
150
|
/**
|
|
74
151
|
* Props for an expandable group of options.
|
|
75
|
-
*
|
|
76
|
-
* When
|
|
152
|
+
*
|
|
153
|
+
* When none of the group's options are active, shows collapsed content
|
|
154
|
+
* based on `collapsedMode`:
|
|
155
|
+
* - `'title'` — only the group label
|
|
156
|
+
* - `'opts'` — only the combined sub-option text ("Solo · Team")
|
|
157
|
+
* - `'title-opts'` — WIP: currently falls back to 'title' behavior
|
|
158
|
+
*
|
|
159
|
+
* When one is active, expands with a clip-path reveal animation.
|
|
77
160
|
*
|
|
78
161
|
* @example
|
|
79
162
|
* ```tsx
|
|
80
|
-
* <DynamicToggleGroup label="
|
|
81
|
-
* <DynamicToggleOption value="
|
|
82
|
-
* <DynamicToggleOption value="
|
|
163
|
+
* <DynamicToggleGroup label="Premium" collapsedMode="title">
|
|
164
|
+
* <DynamicToggleOption value="solo">Solo</DynamicToggleOption>
|
|
165
|
+
* <DynamicToggleOption value="team">Team</DynamicToggleOption>
|
|
83
166
|
* </DynamicToggleGroup>
|
|
84
167
|
* ```
|
|
85
168
|
*/
|
|
86
169
|
export interface IDynamicToggleGroupProps {
|
|
87
|
-
/** Label shown
|
|
170
|
+
/** Label shown as group title / group label text */
|
|
88
171
|
label: string;
|
|
89
|
-
/**
|
|
172
|
+
/** Group label position relative to the pill (default: 'top') */
|
|
173
|
+
labelPosition?: 'top' | 'bottom' | 'hidden';
|
|
174
|
+
/** How the group appears when collapsed (default: 'title') */
|
|
175
|
+
collapsedMode?: DynamicToggleCollapsedMode;
|
|
176
|
+
/** Exactly 2 DynamicToggleOption children */
|
|
90
177
|
children: React.ReactNode;
|
|
91
178
|
/** Additional class */
|
|
92
179
|
className?: string;
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* DynamicToggle — CSS-animated toggle
|
|
4
|
+
* DynamicToggle — CSS-animated toggle with expanding sub-options and group label.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Pure CSS animation via `:has(:checked)` on hidden radio inputs.
|
|
7
|
+
* When the group is active, a group label grows out of the pill.
|
|
8
|
+
* Optional gooey morph via `config.morphMode` for organic junction.
|
|
9
|
+
*
|
|
10
|
+
* Supports exactly 1 `DynamicToggleOption` + 1 `DynamicToggleGroup` (with 2 sub-options).
|
|
9
11
|
*
|
|
10
12
|
* @module @mks2508/mks-ui/react/ui/DynamicToggle
|
|
11
13
|
*
|
|
12
14
|
* @example
|
|
13
15
|
* ```tsx
|
|
14
|
-
* <DynamicToggle value="tree" onValueChange={setVal}>
|
|
16
|
+
* <DynamicToggle value="tree" onValueChange={setVal} size="sm" shape="pill">
|
|
15
17
|
* <DynamicToggleOption value="tree">Tree</DynamicToggleOption>
|
|
16
|
-
* <DynamicToggleGroup label="Changes">
|
|
18
|
+
* <DynamicToggleGroup label="Changes" collapsedMode="title" labelPosition="top">
|
|
17
19
|
* <DynamicToggleOption value="flat">Flat</DynamicToggleOption>
|
|
18
20
|
* <DynamicToggleOption value="grouped">Grouped</DynamicToggleOption>
|
|
19
21
|
* </DynamicToggleGroup>
|
|
@@ -25,57 +27,16 @@ import * as React from 'react';
|
|
|
25
27
|
import { useControlledState } from '@/react-ui/hooks/State/UseControlledState';
|
|
26
28
|
import { getStrictContext } from '@/react-ui/lib/get-strict-context';
|
|
27
29
|
import { cn } from '@/react-ui/lib/utils';
|
|
28
|
-
import {
|
|
30
|
+
import { GooeyCanvas } from '@/react-ui/primitives/waapi/Gooey/GooeyCanvas';
|
|
31
|
+
import './DynamicToggle.css';
|
|
32
|
+
import { dynamicToggleStyles, dynamicToggleVariants } from './DynamicToggle.styles';
|
|
29
33
|
import type {
|
|
30
34
|
DynamicToggleContextType,
|
|
35
|
+
DynamicToggleCollapsedMode,
|
|
31
36
|
IDynamicToggleProps,
|
|
32
37
|
IDynamicToggleOptionProps,
|
|
33
38
|
IDynamicToggleGroupProps,
|
|
34
39
|
} from './DynamicToggle.types';
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// CSS injection (same pattern as Tabs — inject once into document head)
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
const DT_CSS_ID = 'mks-dynamic-toggle-css';
|
|
40
|
-
|
|
41
|
-
/** Reads the CSS file content at build time via rolldown */
|
|
42
|
-
const DT_CSS = `
|
|
43
|
-
:root{--dt-duration:0.22s;--dt-ease:cubic-bezier(0.22,0.61,0.36,1);--dt-drop-off:0.45}
|
|
44
|
-
[data-slot="dt-track"]{grid-template-columns:repeat(4,1fr)}
|
|
45
|
-
[data-slot="dt-track"]>label:first-of-type,[data-slot="dt-group"]{grid-column:span 2}
|
|
46
|
-
[data-slot="dt-indicator"]{transition:translate var(--dt-duration) var(--dt-ease)}
|
|
47
|
-
[data-slot="dt-track"]:has(>input:checked) [data-slot="dt-indicator"]{translate:0 0}
|
|
48
|
-
[data-slot="dt-track"]:not(:has(>input:checked)) [data-slot="dt-indicator"]{translate:100% 0}
|
|
49
|
-
[data-slot="dt-track"]:has(>input:checked)>label{color:var(--card)}
|
|
50
|
-
[data-slot="dt-track"]:not(:has(>input:checked))>label{color:var(--foreground);opacity:var(--dt-drop-off)}
|
|
51
|
-
[data-slot="dt-group"]{container-type:size;grid-template-columns:1fr 1fr}
|
|
52
|
-
[data-slot="dt-group-label"]{translate:-50% -80%;transition:translate var(--dt-duration) var(--dt-ease),scale var(--dt-duration) var(--dt-ease)}
|
|
53
|
-
[data-slot="dt-group"]:has(input:checked) [data-slot="dt-group-label"]{translate:-50% -250%;scale:0.85}
|
|
54
|
-
[data-slot="dt-group-indicator"]{transition:translate var(--dt-duration) var(--dt-ease),clip-path var(--dt-duration) var(--dt-ease),background var(--dt-duration) var(--dt-ease);clip-path:inset(73cqh calc(50% + 1px) calc(27cqh - 2px) calc(50% - 3px) round 100px);translate:-50% 0;background:var(--foreground)}
|
|
55
|
-
[data-slot="dt-group"]:has(input:checked) [data-slot="dt-group-indicator"]{background:var(--card);clip-path:inset(0 0 0 0 round 100px)}
|
|
56
|
-
[data-slot="dt-group"]:has(input:nth-of-type(1):checked) [data-slot="dt-group-indicator"]{translate:-100% 0}
|
|
57
|
-
[data-slot="dt-group"]:has(input:nth-of-type(2):checked) [data-slot="dt-group-indicator"]{translate:0 0}
|
|
58
|
-
[data-slot="dt-group"] label{color:var(--muted-foreground);transition:color var(--dt-duration) var(--dt-ease),opacity var(--dt-duration) var(--dt-ease)}
|
|
59
|
-
[data-slot="dt-group"] label span{transition:scale var(--dt-duration) var(--dt-ease)}
|
|
60
|
-
[data-slot="dt-track"]:has(>input:checked) [data-slot="dt-group"] label{color:var(--muted-foreground)}
|
|
61
|
-
[data-slot="dt-group"]:has(input:checked) label{color:var(--muted-foreground);opacity:0.75}
|
|
62
|
-
[data-slot="dt-group"]:has(input:nth-of-type(1):checked) label:nth-of-type(1),[data-slot="dt-group"]:has(input:nth-of-type(2):checked) label:nth-of-type(2){color:var(--foreground);opacity:1}
|
|
63
|
-
[data-slot="dt-group"] label:nth-of-type(1) span{scale:0.75;transform-origin:150% 150%}
|
|
64
|
-
[data-slot="dt-group"] label:nth-of-type(2) span{scale:0.75;transform-origin:-65% 150%}
|
|
65
|
-
[data-slot="dt-group"]:has(input:checked) label span{scale:1}
|
|
66
|
-
[data-slot="dt-radio"]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}
|
|
67
|
-
`;
|
|
68
|
-
|
|
69
|
-
function useDynamicToggleCSS() {
|
|
70
|
-
React.useEffect(() => {
|
|
71
|
-
if (typeof document === 'undefined') return;
|
|
72
|
-
if (document.getElementById(DT_CSS_ID)) return;
|
|
73
|
-
const style = document.createElement('style');
|
|
74
|
-
style.id = DT_CSS_ID;
|
|
75
|
-
style.textContent = DT_CSS;
|
|
76
|
-
document.head.appendChild(style);
|
|
77
|
-
}, []);
|
|
78
|
-
}
|
|
79
40
|
|
|
80
41
|
// ---------------------------------------------------------------------------
|
|
81
42
|
// Context
|
|
@@ -84,24 +45,54 @@ function useDynamicToggleCSS() {
|
|
|
84
45
|
const [DynamicToggleProvider, useDynamicToggle] =
|
|
85
46
|
getStrictContext<DynamicToggleContextType>('DynamicToggleContext');
|
|
86
47
|
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// SSR-safe layout effect — useLayoutEffect on client, useEffect on server
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
const useIsomorphicLayoutEffect =
|
|
53
|
+
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Size height map (for auto-blur calculation)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const SIZE_HEIGHT_PX: Record<string, number> = {
|
|
60
|
+
sm: 30,
|
|
61
|
+
default: 38,
|
|
62
|
+
lg: 44,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const SIZE_WIDTH_PX: Record<string, number> = {
|
|
66
|
+
sm: 210,
|
|
67
|
+
default: 260,
|
|
68
|
+
lg: 320,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const SHAPE_RADIUS: Record<string, number> = {
|
|
72
|
+
pill: 9999,
|
|
73
|
+
rounded: 12,
|
|
74
|
+
square: 6,
|
|
75
|
+
};
|
|
76
|
+
|
|
87
77
|
// ---------------------------------------------------------------------------
|
|
88
78
|
// DynamicToggle (Root)
|
|
89
79
|
// ---------------------------------------------------------------------------
|
|
90
80
|
|
|
91
81
|
/**
|
|
92
|
-
* Root container — pill-shaped toggle
|
|
93
|
-
*
|
|
94
|
-
* @param props - Component props
|
|
95
|
-
* @returns React component
|
|
82
|
+
* Root container — pill-shaped toggle with expanding sub-options and group label.
|
|
96
83
|
*/
|
|
97
84
|
function DynamicToggle({
|
|
85
|
+
variant,
|
|
86
|
+
size,
|
|
87
|
+
shape,
|
|
98
88
|
slots,
|
|
89
|
+
config,
|
|
90
|
+
disabled = false,
|
|
99
91
|
className,
|
|
100
92
|
children,
|
|
93
|
+
'aria-label': ariaLabel,
|
|
101
94
|
...props
|
|
102
95
|
}: IDynamicToggleProps) {
|
|
103
|
-
useDynamicToggleCSS();
|
|
104
|
-
|
|
105
96
|
const [value, setValue] = useControlledState({
|
|
106
97
|
value: props.value,
|
|
107
98
|
defaultValue: props.defaultValue ?? '',
|
|
@@ -110,35 +101,85 @@ function DynamicToggle({
|
|
|
110
101
|
|
|
111
102
|
const groupName = React.useId();
|
|
112
103
|
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
104
|
+
// Group info — registered by DynamicToggleGroup child via context + useLayoutEffect
|
|
105
|
+
const [groupLabel, setGroupLabel] = React.useState('');
|
|
106
|
+
const [groupValues, setGroupValues] = React.useState<string[]>([]);
|
|
107
|
+
const [groupPosition, setGroupPosition] = React.useState<'top' | 'bottom' | 'hidden'>('top');
|
|
108
|
+
const [groupCollapsedMode, setGroupCollapsedMode] = React.useState<DynamicToggleCollapsedMode>('title');
|
|
109
|
+
|
|
110
|
+
const registerGroup = React.useCallback(
|
|
111
|
+
(label: string, values: string[], position: 'top' | 'bottom' | 'hidden', collapsedMode: DynamicToggleCollapsedMode) => {
|
|
112
|
+
setGroupLabel(label);
|
|
113
|
+
setGroupValues(values);
|
|
114
|
+
setGroupPosition(position);
|
|
115
|
+
setGroupCollapsedMode(collapsedMode);
|
|
116
|
+
},
|
|
117
|
+
[],
|
|
118
|
+
);
|
|
127
119
|
|
|
128
120
|
const groupActive = groupValues.includes(value);
|
|
129
121
|
|
|
122
|
+
// Config
|
|
123
|
+
const morphMode = config?.morphMode ?? 'none';
|
|
124
|
+
const resolvedVariant = variant ?? 'default';
|
|
125
|
+
const effectiveMorphMode = (resolvedVariant === 'ghost' || resolvedVariant === 'outline') ? 'none' : morphMode;
|
|
126
|
+
const labelAnimation = config?.labelAnimation ?? 'morph';
|
|
127
|
+
const showGroupLabel = labelAnimation !== 'none' && groupPosition !== 'hidden' && groupLabel;
|
|
128
|
+
const heightPx = SIZE_HEIGHT_PX[size ?? 'default'] ?? 32;
|
|
129
|
+
|
|
130
|
+
const style = {
|
|
131
|
+
...(config?.duration && { '--dt-dur': `${config.duration}s` }),
|
|
132
|
+
...(config?.indicatorDuration && { '--dt-indicator-dur': `${config.indicatorDuration}s` }),
|
|
133
|
+
...(config?.indicatorEasing && { '--dt-indicator-ease': config.indicatorEasing }),
|
|
134
|
+
} as React.CSSProperties;
|
|
135
|
+
const hasStyle = Object.keys(style).length > 0;
|
|
136
|
+
|
|
137
|
+
// Group label element (shared between modes)
|
|
138
|
+
const groupLabelElement = showGroupLabel ? (
|
|
139
|
+
<div
|
|
140
|
+
data-slot="dt-group-label"
|
|
141
|
+
data-position={groupPosition}
|
|
142
|
+
className={cn(dynamicToggleStyles.groupLabel, slots?.groupLabel)}
|
|
143
|
+
>
|
|
144
|
+
<span>{groupLabel || '\u00A0'}</span>
|
|
145
|
+
</div>
|
|
146
|
+
) : null;
|
|
147
|
+
|
|
130
148
|
return (
|
|
131
|
-
<DynamicToggleProvider value={{ value, setValue, groupName, groupActive }}>
|
|
149
|
+
<DynamicToggleProvider value={{ value, setValue, groupName, groupActive, disabled, registerGroup }}>
|
|
132
150
|
<div
|
|
133
151
|
data-slot="dt-root"
|
|
152
|
+
data-morph={effectiveMorphMode !== 'none' ? effectiveMorphMode : undefined}
|
|
153
|
+
data-indicator={config?.forceTranslateIndicator ? 'translate' : undefined}
|
|
134
154
|
data-group-active={groupActive || undefined}
|
|
135
|
-
|
|
155
|
+
data-disabled={disabled || undefined}
|
|
156
|
+
role="radiogroup"
|
|
157
|
+
aria-label={ariaLabel}
|
|
158
|
+
style={hasStyle ? style : undefined}
|
|
159
|
+
className={cn(dynamicToggleVariants({ variant, size, shape }), slots?.root, className)}
|
|
136
160
|
>
|
|
161
|
+
{/* Filter morph: GooeyCanvas with SVG rects (Safari-safe) */}
|
|
162
|
+
{effectiveMorphMode === 'filter' && (
|
|
163
|
+
<>
|
|
164
|
+
<GooeyCanvas
|
|
165
|
+
height={heightPx}
|
|
166
|
+
width={SIZE_WIDTH_PX[size ?? 'default'] ?? 260}
|
|
167
|
+
radius={SHAPE_RADIUS[shape ?? 'pill'] ?? 9999}
|
|
168
|
+
expanded={groupActive}
|
|
169
|
+
/>
|
|
170
|
+
{/* Group label text — outside the filtered canvas */}
|
|
171
|
+
{groupLabelElement}
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Path morph: group label rendered directly (no gooey filter) */}
|
|
176
|
+
{effectiveMorphMode === 'path' && groupLabelElement}
|
|
177
|
+
|
|
178
|
+
{/* Track — always rendered, z-indexed above gooey layer */}
|
|
137
179
|
<div
|
|
138
180
|
data-slot="dt-track"
|
|
139
181
|
className={cn(dynamicToggleStyles.track, slots?.track)}
|
|
140
182
|
>
|
|
141
|
-
{/* Main sliding indicator */}
|
|
142
183
|
<div
|
|
143
184
|
data-slot="dt-indicator"
|
|
144
185
|
className={cn(dynamicToggleStyles.indicator, slots?.indicator)}
|
|
@@ -155,11 +196,7 @@ function DynamicToggle({
|
|
|
155
196
|
// ---------------------------------------------------------------------------
|
|
156
197
|
|
|
157
198
|
/**
|
|
158
|
-
* A single toggle option —
|
|
159
|
-
* Can be used at the top level or inside a DynamicToggleGroup.
|
|
160
|
-
*
|
|
161
|
-
* @param props - Component props
|
|
162
|
-
* @returns React component
|
|
199
|
+
* A single toggle option — hidden radio + visible label.
|
|
163
200
|
*/
|
|
164
201
|
function DynamicToggleOption({ value, children, className }: IDynamicToggleOptionProps) {
|
|
165
202
|
const ctx = useDynamicToggle();
|
|
@@ -177,12 +214,13 @@ function DynamicToggleOption({ value, children, className }: IDynamicToggleOptio
|
|
|
177
214
|
<span>{children}</span>
|
|
178
215
|
</label>
|
|
179
216
|
<input
|
|
180
|
-
|
|
217
|
+
className="sr-only"
|
|
181
218
|
type="radio"
|
|
182
219
|
name={ctx.groupName}
|
|
183
220
|
id={id}
|
|
184
221
|
value={value}
|
|
185
222
|
checked={isActive}
|
|
223
|
+
disabled={ctx.disabled}
|
|
186
224
|
onChange={() => ctx.setValue(value)}
|
|
187
225
|
/>
|
|
188
226
|
</>
|
|
@@ -194,26 +232,50 @@ function DynamicToggleOption({ value, children, className }: IDynamicToggleOptio
|
|
|
194
232
|
// ---------------------------------------------------------------------------
|
|
195
233
|
|
|
196
234
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* @param props - Component props
|
|
201
|
-
* @returns React component
|
|
235
|
+
* Expandable group — registers with root on mount, renders sub-options.
|
|
236
|
+
* The group label is rendered by the root based on registered info.
|
|
202
237
|
*/
|
|
203
|
-
function DynamicToggleGroup({
|
|
238
|
+
function DynamicToggleGroup({
|
|
239
|
+
label,
|
|
240
|
+
labelPosition = 'top',
|
|
241
|
+
collapsedMode = 'title',
|
|
242
|
+
children,
|
|
243
|
+
className,
|
|
244
|
+
}: IDynamicToggleGroupProps) {
|
|
245
|
+
const ctx = useDynamicToggle();
|
|
246
|
+
|
|
247
|
+
// Build combined opts text from children
|
|
248
|
+
const optsText = React.useMemo(() => {
|
|
249
|
+
const labels: string[] = [];
|
|
250
|
+
React.Children.forEach(children, (child) => {
|
|
251
|
+
if (React.isValidElement(child)) {
|
|
252
|
+
const p = child.props as { children?: React.ReactNode };
|
|
253
|
+
if (p.children) labels.push(String(p.children));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return labels.join(' · ');
|
|
257
|
+
}, [children]);
|
|
258
|
+
|
|
259
|
+
// Register group info with root — useLayoutEffect prevents visual flash
|
|
260
|
+
useIsomorphicLayoutEffect(() => {
|
|
261
|
+
const values: string[] = [];
|
|
262
|
+
React.Children.forEach(children, (child) => {
|
|
263
|
+
if (React.isValidElement(child)) {
|
|
264
|
+
const p = child.props as { value?: string };
|
|
265
|
+
if (p.value) values.push(p.value);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
ctx.registerGroup(label, values, labelPosition, collapsedMode);
|
|
269
|
+
}, [label, labelPosition, collapsedMode, children, ctx.registerGroup]);
|
|
270
|
+
|
|
204
271
|
return (
|
|
205
272
|
<div
|
|
206
273
|
data-slot="dt-group"
|
|
274
|
+
data-collapsed={collapsedMode}
|
|
275
|
+
data-label={label}
|
|
276
|
+
data-opts={collapsedMode !== 'title' ? optsText : undefined}
|
|
207
277
|
className={cn(dynamicToggleStyles.group, className)}
|
|
208
278
|
>
|
|
209
|
-
{/* Collapsed label */}
|
|
210
|
-
<span
|
|
211
|
-
data-slot="dt-group-label"
|
|
212
|
-
className={dynamicToggleStyles.groupLabel}
|
|
213
|
-
>
|
|
214
|
-
{label}
|
|
215
|
-
</span>
|
|
216
|
-
{/* Internal sliding indicator */}
|
|
217
279
|
<div
|
|
218
280
|
data-slot="dt-group-indicator"
|
|
219
281
|
className={dynamicToggleStyles.groupIndicator}
|
|
@@ -223,18 +285,32 @@ function DynamicToggleGroup({ label, children, className }: IDynamicToggleGroupP
|
|
|
223
285
|
);
|
|
224
286
|
}
|
|
225
287
|
|
|
288
|
+
// Display names
|
|
289
|
+
DynamicToggle.displayName = 'DynamicToggle';
|
|
290
|
+
DynamicToggleOption.displayName = 'DynamicToggleOption';
|
|
291
|
+
DynamicToggleGroup.displayName = 'DynamicToggleGroup';
|
|
292
|
+
|
|
226
293
|
// ---------------------------------------------------------------------------
|
|
227
294
|
// Exports
|
|
228
295
|
// ---------------------------------------------------------------------------
|
|
229
296
|
|
|
230
|
-
export {
|
|
297
|
+
export {
|
|
298
|
+
DynamicToggle,
|
|
299
|
+
DynamicToggleOption,
|
|
300
|
+
DynamicToggleGroup,
|
|
301
|
+
useDynamicToggle,
|
|
302
|
+
dynamicToggleVariants,
|
|
303
|
+
};
|
|
231
304
|
|
|
232
305
|
export type {
|
|
233
306
|
IDynamicToggleProps,
|
|
234
307
|
IDynamicToggleOptionProps,
|
|
235
308
|
IDynamicToggleGroupProps,
|
|
309
|
+
IDynamicToggleConfig,
|
|
236
310
|
DynamicToggleContextType,
|
|
311
|
+
DynamicToggleCollapsedMode,
|
|
312
|
+
DynamicToggleMorphMode,
|
|
237
313
|
} from './DynamicToggle.types';
|
|
238
314
|
|
|
239
|
-
export type { DynamicToggleSlot } from './DynamicToggle.styles';
|
|
315
|
+
export type { DynamicToggleSlot, DynamicToggleVariantProps } from './DynamicToggle.styles';
|
|
240
316
|
export { dynamicToggleStyles } from './DynamicToggle.styles';
|