@mks2508/mks-ui 0.5.2 → 0.5.4
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 +103 -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 +59 -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 +87 -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 +177 -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-Cm6-VceQ.css +304 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +303 -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 +63 -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 +115 -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 +123 -0
- package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +80 -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 +244 -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 +244 -91
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +60 -40
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +95 -14
- package/src/react-ui/ui/DynamicToggle/index.tsx +150 -96
- package/src/react-ui/ui/DynamicToggle/prototype-v7.html +615 -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,52 @@
|
|
|
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
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Context
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
12
50
|
|
|
13
51
|
/**
|
|
14
52
|
* Context shared between DynamicToggle root and its children.
|
|
@@ -22,37 +60,66 @@ export type DynamicToggleContextType = {
|
|
|
22
60
|
groupName: string;
|
|
23
61
|
/** Whether a group child is active */
|
|
24
62
|
groupActive: boolean;
|
|
63
|
+
/** Whether the toggle is disabled */
|
|
64
|
+
disabled: boolean;
|
|
65
|
+
/** Register group info (called by DynamicToggleGroup on mount) */
|
|
66
|
+
registerGroup: (
|
|
67
|
+
label: string,
|
|
68
|
+
values: string[],
|
|
69
|
+
position: 'top' | 'bottom' | 'hidden',
|
|
70
|
+
collapsedMode: DynamicToggleCollapsedMode,
|
|
71
|
+
) => void;
|
|
25
72
|
};
|
|
26
73
|
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Root
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
27
78
|
/**
|
|
28
79
|
* Props for the DynamicToggle root container.
|
|
29
80
|
*
|
|
81
|
+
* Supports exactly one `DynamicToggleOption` + one `DynamicToggleGroup`.
|
|
82
|
+
* The group expands into sub-options when one of its children is active.
|
|
83
|
+
*
|
|
30
84
|
* @example
|
|
31
85
|
* ```tsx
|
|
32
|
-
* <DynamicToggle
|
|
86
|
+
* <DynamicToggle
|
|
87
|
+
* value="tree" onValueChange={setVal}
|
|
88
|
+
* size="sm" config={{ morphMode: 'filter' }}
|
|
89
|
+
* >
|
|
33
90
|
* <DynamicToggleOption value="tree">Tree</DynamicToggleOption>
|
|
34
|
-
* <DynamicToggleGroup label="Changes">
|
|
91
|
+
* <DynamicToggleGroup label="Changes" collapsedMode="title">
|
|
35
92
|
* <DynamicToggleOption value="flat">Flat</DynamicToggleOption>
|
|
36
93
|
* <DynamicToggleOption value="grouped">Grouped</DynamicToggleOption>
|
|
37
94
|
* </DynamicToggleGroup>
|
|
38
95
|
* </DynamicToggle>
|
|
39
96
|
* ```
|
|
40
97
|
*/
|
|
41
|
-
export interface IDynamicToggleProps {
|
|
98
|
+
export interface IDynamicToggleProps extends DynamicToggleVariantProps {
|
|
42
99
|
/** Controlled value */
|
|
43
100
|
value?: string;
|
|
44
101
|
/** Uncontrolled default value */
|
|
45
102
|
defaultValue?: string;
|
|
46
103
|
/** Change callback */
|
|
47
104
|
onValueChange?: (value: string) => void;
|
|
105
|
+
/** Disable the entire toggle */
|
|
106
|
+
disabled?: boolean;
|
|
48
107
|
/** Slot class overrides */
|
|
49
108
|
slots?: SlotOverrides<DynamicToggleSlot>;
|
|
109
|
+
/** Animation/behavior configuration */
|
|
110
|
+
config?: IDynamicToggleConfig;
|
|
111
|
+
/** Accessible label for the radio group */
|
|
112
|
+
'aria-label'?: string;
|
|
50
113
|
/** Additional class for the root */
|
|
51
114
|
className?: string;
|
|
52
115
|
/** DynamicToggleOption and DynamicToggleGroup children */
|
|
53
116
|
children: React.ReactNode;
|
|
54
117
|
}
|
|
55
118
|
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Option
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
56
123
|
/**
|
|
57
124
|
* Props for a single toggle option (top-level or inside a group).
|
|
58
125
|
*
|
|
@@ -70,23 +137,37 @@ export interface IDynamicToggleOptionProps {
|
|
|
70
137
|
className?: string;
|
|
71
138
|
}
|
|
72
139
|
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Group
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
73
144
|
/**
|
|
74
145
|
* Props for an expandable group of options.
|
|
75
|
-
*
|
|
76
|
-
* When
|
|
146
|
+
*
|
|
147
|
+
* When none of the group's options are active, shows collapsed content
|
|
148
|
+
* based on `collapsedMode`:
|
|
149
|
+
* - `'title'` — only the group label
|
|
150
|
+
* - `'opts'` — only the combined sub-option text ("Solo · Team")
|
|
151
|
+
* - `'title-opts'` — WIP: currently falls back to 'title' behavior
|
|
152
|
+
*
|
|
153
|
+
* When one is active, expands with a clip-path reveal animation.
|
|
77
154
|
*
|
|
78
155
|
* @example
|
|
79
156
|
* ```tsx
|
|
80
|
-
* <DynamicToggleGroup label="
|
|
81
|
-
* <DynamicToggleOption value="
|
|
82
|
-
* <DynamicToggleOption value="
|
|
157
|
+
* <DynamicToggleGroup label="Premium" collapsedMode="title">
|
|
158
|
+
* <DynamicToggleOption value="solo">Solo</DynamicToggleOption>
|
|
159
|
+
* <DynamicToggleOption value="team">Team</DynamicToggleOption>
|
|
83
160
|
* </DynamicToggleGroup>
|
|
84
161
|
* ```
|
|
85
162
|
*/
|
|
86
163
|
export interface IDynamicToggleGroupProps {
|
|
87
|
-
/** Label shown
|
|
164
|
+
/** Label shown as group title / group label text */
|
|
88
165
|
label: string;
|
|
89
|
-
/**
|
|
166
|
+
/** Group label position relative to the pill (default: 'top') */
|
|
167
|
+
labelPosition?: 'top' | 'bottom' | 'hidden';
|
|
168
|
+
/** How the group appears when collapsed (default: 'title') */
|
|
169
|
+
collapsedMode?: DynamicToggleCollapsedMode;
|
|
170
|
+
/** Exactly 2 DynamicToggleOption children */
|
|
90
171
|
children: React.ReactNode;
|
|
91
172
|
/** Additional class */
|
|
92
173
|
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,42 @@ 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
|
+
|
|
87
65
|
// ---------------------------------------------------------------------------
|
|
88
66
|
// DynamicToggle (Root)
|
|
89
67
|
// ---------------------------------------------------------------------------
|
|
90
68
|
|
|
91
69
|
/**
|
|
92
|
-
* Root container — pill-shaped toggle
|
|
93
|
-
*
|
|
94
|
-
* @param props - Component props
|
|
95
|
-
* @returns React component
|
|
70
|
+
* Root container — pill-shaped toggle with expanding sub-options and group label.
|
|
96
71
|
*/
|
|
97
72
|
function DynamicToggle({
|
|
73
|
+
variant,
|
|
74
|
+
size,
|
|
75
|
+
shape,
|
|
98
76
|
slots,
|
|
77
|
+
config,
|
|
78
|
+
disabled = false,
|
|
99
79
|
className,
|
|
100
80
|
children,
|
|
81
|
+
'aria-label': ariaLabel,
|
|
101
82
|
...props
|
|
102
83
|
}: IDynamicToggleProps) {
|
|
103
|
-
useDynamicToggleCSS();
|
|
104
|
-
|
|
105
84
|
const [value, setValue] = useControlledState({
|
|
106
85
|
value: props.value,
|
|
107
86
|
defaultValue: props.defaultValue ?? '',
|
|
@@ -110,35 +89,75 @@ function DynamicToggle({
|
|
|
110
89
|
|
|
111
90
|
const groupName = React.useId();
|
|
112
91
|
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
92
|
+
// Group info — registered by DynamicToggleGroup child via context + useLayoutEffect
|
|
93
|
+
const [groupLabel, setGroupLabel] = React.useState('');
|
|
94
|
+
const [groupValues, setGroupValues] = React.useState<string[]>([]);
|
|
95
|
+
const [groupPosition, setGroupPosition] = React.useState<'top' | 'bottom' | 'hidden'>('top');
|
|
96
|
+
const [groupCollapsedMode, setGroupCollapsedMode] = React.useState<DynamicToggleCollapsedMode>('title');
|
|
97
|
+
|
|
98
|
+
const registerGroup = React.useCallback(
|
|
99
|
+
(label: string, values: string[], position: 'top' | 'bottom' | 'hidden', collapsedMode: DynamicToggleCollapsedMode) => {
|
|
100
|
+
setGroupLabel(label);
|
|
101
|
+
setGroupValues(values);
|
|
102
|
+
setGroupPosition(position);
|
|
103
|
+
setGroupCollapsedMode(collapsedMode);
|
|
104
|
+
},
|
|
105
|
+
[],
|
|
106
|
+
);
|
|
127
107
|
|
|
128
108
|
const groupActive = groupValues.includes(value);
|
|
129
109
|
|
|
110
|
+
// Config
|
|
111
|
+
const morphMode = config?.morphMode ?? 'none';
|
|
112
|
+
const resolvedVariant = variant ?? 'default';
|
|
113
|
+
const effectiveMorphMode = (resolvedVariant === 'ghost' || resolvedVariant === 'outline') ? 'none' : morphMode;
|
|
114
|
+
const labelAnimation = config?.labelAnimation ?? 'morph';
|
|
115
|
+
const showGroupLabel = labelAnimation !== 'none' && groupPosition !== 'hidden' && groupLabel;
|
|
116
|
+
const heightPx = SIZE_HEIGHT_PX[size ?? 'default'] ?? 32;
|
|
117
|
+
|
|
118
|
+
const style = config?.duration
|
|
119
|
+
? { '--dt-dur': `${config.duration}s` } as React.CSSProperties
|
|
120
|
+
: undefined;
|
|
121
|
+
|
|
122
|
+
// Group label element (shared between modes)
|
|
123
|
+
const groupLabelElement = showGroupLabel ? (
|
|
124
|
+
<div
|
|
125
|
+
data-slot="dt-group-label"
|
|
126
|
+
data-position={groupPosition}
|
|
127
|
+
className={cn(dynamicToggleStyles.groupLabel, slots?.groupLabel)}
|
|
128
|
+
>
|
|
129
|
+
<span>{groupLabel || '\u00A0'}</span>
|
|
130
|
+
</div>
|
|
131
|
+
) : null;
|
|
132
|
+
|
|
130
133
|
return (
|
|
131
|
-
<DynamicToggleProvider value={{ value, setValue, groupName, groupActive }}>
|
|
134
|
+
<DynamicToggleProvider value={{ value, setValue, groupName, groupActive, disabled, registerGroup }}>
|
|
132
135
|
<div
|
|
133
136
|
data-slot="dt-root"
|
|
137
|
+
data-morph={effectiveMorphMode !== 'none' ? effectiveMorphMode : undefined}
|
|
134
138
|
data-group-active={groupActive || undefined}
|
|
135
|
-
|
|
139
|
+
data-disabled={disabled || undefined}
|
|
140
|
+
role="radiogroup"
|
|
141
|
+
aria-label={ariaLabel}
|
|
142
|
+
style={style}
|
|
143
|
+
className={cn(dynamicToggleVariants({ variant, size, shape }), slots?.root, className)}
|
|
136
144
|
>
|
|
145
|
+
{/* Filter morph: GooeyCanvas wraps backgrounds only */}
|
|
146
|
+
{effectiveMorphMode === 'filter' && (
|
|
147
|
+
<GooeyCanvas height={heightPx}>
|
|
148
|
+
<div className="absolute inset-0 rounded-[inherit] bg-card" />
|
|
149
|
+
{groupLabelElement}
|
|
150
|
+
</GooeyCanvas>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Path morph: group label rendered directly (no gooey filter) */}
|
|
154
|
+
{effectiveMorphMode === 'path' && groupLabelElement}
|
|
155
|
+
|
|
156
|
+
{/* Track — always rendered, z-indexed above gooey layer */}
|
|
137
157
|
<div
|
|
138
158
|
data-slot="dt-track"
|
|
139
159
|
className={cn(dynamicToggleStyles.track, slots?.track)}
|
|
140
160
|
>
|
|
141
|
-
{/* Main sliding indicator */}
|
|
142
161
|
<div
|
|
143
162
|
data-slot="dt-indicator"
|
|
144
163
|
className={cn(dynamicToggleStyles.indicator, slots?.indicator)}
|
|
@@ -155,11 +174,7 @@ function DynamicToggle({
|
|
|
155
174
|
// ---------------------------------------------------------------------------
|
|
156
175
|
|
|
157
176
|
/**
|
|
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
|
|
177
|
+
* A single toggle option — hidden radio + visible label.
|
|
163
178
|
*/
|
|
164
179
|
function DynamicToggleOption({ value, children, className }: IDynamicToggleOptionProps) {
|
|
165
180
|
const ctx = useDynamicToggle();
|
|
@@ -177,12 +192,13 @@ function DynamicToggleOption({ value, children, className }: IDynamicToggleOptio
|
|
|
177
192
|
<span>{children}</span>
|
|
178
193
|
</label>
|
|
179
194
|
<input
|
|
180
|
-
|
|
195
|
+
className="sr-only"
|
|
181
196
|
type="radio"
|
|
182
197
|
name={ctx.groupName}
|
|
183
198
|
id={id}
|
|
184
199
|
value={value}
|
|
185
200
|
checked={isActive}
|
|
201
|
+
disabled={ctx.disabled}
|
|
186
202
|
onChange={() => ctx.setValue(value)}
|
|
187
203
|
/>
|
|
188
204
|
</>
|
|
@@ -194,26 +210,50 @@ function DynamicToggleOption({ value, children, className }: IDynamicToggleOptio
|
|
|
194
210
|
// ---------------------------------------------------------------------------
|
|
195
211
|
|
|
196
212
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* @param props - Component props
|
|
201
|
-
* @returns React component
|
|
213
|
+
* Expandable group — registers with root on mount, renders sub-options.
|
|
214
|
+
* The group label is rendered by the root based on registered info.
|
|
202
215
|
*/
|
|
203
|
-
function DynamicToggleGroup({
|
|
216
|
+
function DynamicToggleGroup({
|
|
217
|
+
label,
|
|
218
|
+
labelPosition = 'top',
|
|
219
|
+
collapsedMode = 'title',
|
|
220
|
+
children,
|
|
221
|
+
className,
|
|
222
|
+
}: IDynamicToggleGroupProps) {
|
|
223
|
+
const ctx = useDynamicToggle();
|
|
224
|
+
|
|
225
|
+
// Build combined opts text from children
|
|
226
|
+
const optsText = React.useMemo(() => {
|
|
227
|
+
const labels: string[] = [];
|
|
228
|
+
React.Children.forEach(children, (child) => {
|
|
229
|
+
if (React.isValidElement(child)) {
|
|
230
|
+
const p = child.props as { children?: React.ReactNode };
|
|
231
|
+
if (p.children) labels.push(String(p.children));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return labels.join(' · ');
|
|
235
|
+
}, [children]);
|
|
236
|
+
|
|
237
|
+
// Register group info with root — useLayoutEffect prevents visual flash
|
|
238
|
+
useIsomorphicLayoutEffect(() => {
|
|
239
|
+
const values: string[] = [];
|
|
240
|
+
React.Children.forEach(children, (child) => {
|
|
241
|
+
if (React.isValidElement(child)) {
|
|
242
|
+
const p = child.props as { value?: string };
|
|
243
|
+
if (p.value) values.push(p.value);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
ctx.registerGroup(label, values, labelPosition, collapsedMode);
|
|
247
|
+
}, [label, labelPosition, collapsedMode, children, ctx.registerGroup]);
|
|
248
|
+
|
|
204
249
|
return (
|
|
205
250
|
<div
|
|
206
251
|
data-slot="dt-group"
|
|
252
|
+
data-collapsed={collapsedMode}
|
|
253
|
+
data-label={label}
|
|
254
|
+
data-opts={collapsedMode !== 'title' ? optsText : undefined}
|
|
207
255
|
className={cn(dynamicToggleStyles.group, className)}
|
|
208
256
|
>
|
|
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
257
|
<div
|
|
218
258
|
data-slot="dt-group-indicator"
|
|
219
259
|
className={dynamicToggleStyles.groupIndicator}
|
|
@@ -223,18 +263,32 @@ function DynamicToggleGroup({ label, children, className }: IDynamicToggleGroupP
|
|
|
223
263
|
);
|
|
224
264
|
}
|
|
225
265
|
|
|
266
|
+
// Display names
|
|
267
|
+
DynamicToggle.displayName = 'DynamicToggle';
|
|
268
|
+
DynamicToggleOption.displayName = 'DynamicToggleOption';
|
|
269
|
+
DynamicToggleGroup.displayName = 'DynamicToggleGroup';
|
|
270
|
+
|
|
226
271
|
// ---------------------------------------------------------------------------
|
|
227
272
|
// Exports
|
|
228
273
|
// ---------------------------------------------------------------------------
|
|
229
274
|
|
|
230
|
-
export {
|
|
275
|
+
export {
|
|
276
|
+
DynamicToggle,
|
|
277
|
+
DynamicToggleOption,
|
|
278
|
+
DynamicToggleGroup,
|
|
279
|
+
useDynamicToggle,
|
|
280
|
+
dynamicToggleVariants,
|
|
281
|
+
};
|
|
231
282
|
|
|
232
283
|
export type {
|
|
233
284
|
IDynamicToggleProps,
|
|
234
285
|
IDynamicToggleOptionProps,
|
|
235
286
|
IDynamicToggleGroupProps,
|
|
287
|
+
IDynamicToggleConfig,
|
|
236
288
|
DynamicToggleContextType,
|
|
289
|
+
DynamicToggleCollapsedMode,
|
|
290
|
+
DynamicToggleMorphMode,
|
|
237
291
|
} from './DynamicToggle.types';
|
|
238
292
|
|
|
239
|
-
export type { DynamicToggleSlot } from './DynamicToggle.styles';
|
|
293
|
+
export type { DynamicToggleSlot, DynamicToggleVariantProps } from './DynamicToggle.styles';
|
|
240
294
|
export { dynamicToggleStyles } from './DynamicToggle.styles';
|