@seedgrid/fe-components 0.2.9 → 2026.3.1
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/buttons/SgFloatActionButton.d.ts.map +1 -1
- package/dist/buttons/SgFloatActionButton.js +168 -38
- package/dist/commons/SgAvatar.d.ts +66 -0
- package/dist/commons/SgAvatar.d.ts.map +1 -0
- package/dist/commons/SgAvatar.js +136 -0
- package/dist/commons/SgSkeleton.d.ts +16 -0
- package/dist/commons/SgSkeleton.d.ts.map +1 -0
- package/dist/commons/SgSkeleton.js +58 -0
- package/dist/commons/SgToaster.d.ts +9 -0
- package/dist/commons/SgToaster.d.ts.map +1 -1
- package/dist/commons/SgToaster.js +86 -17
- package/dist/digits/discard-digit/SgDiscardDigit.d.ts +39 -0
- package/dist/digits/discard-digit/SgDiscardDigit.d.ts.map +1 -0
- package/dist/digits/discard-digit/SgDiscardDigit.js +303 -0
- package/dist/digits/discard-digit/index.d.ts +3 -0
- package/dist/digits/discard-digit/index.d.ts.map +1 -0
- package/dist/digits/discard-digit/index.js +1 -0
- package/dist/digits/fade-digit/SgFadeDigit.d.ts +27 -0
- package/dist/digits/fade-digit/SgFadeDigit.d.ts.map +1 -0
- package/dist/digits/fade-digit/SgFadeDigit.js +85 -0
- package/dist/digits/fade-digit/index.d.ts +3 -0
- package/dist/digits/fade-digit/index.d.ts.map +1 -0
- package/dist/digits/fade-digit/index.js +1 -0
- package/dist/digits/flip-digit/SgFlipDigit.d.ts +27 -0
- package/dist/digits/flip-digit/SgFlipDigit.d.ts.map +1 -0
- package/dist/digits/flip-digit/SgFlipDigit.js +70 -0
- package/dist/digits/flip-digit/index.d.ts.map +1 -0
- package/dist/digits/matrix-digit/SgMatrixDigit.d.ts +32 -0
- package/dist/digits/matrix-digit/SgMatrixDigit.d.ts.map +1 -0
- package/dist/digits/matrix-digit/SgMatrixDigit.js +86 -0
- package/dist/digits/matrix-digit/index.d.ts +3 -0
- package/dist/digits/matrix-digit/index.d.ts.map +1 -0
- package/dist/digits/matrix-digit/index.js +1 -0
- package/dist/digits/neon-digit/SgNeonDigit.d.ts +37 -0
- package/dist/digits/neon-digit/SgNeonDigit.d.ts.map +1 -0
- package/dist/digits/neon-digit/SgNeonDigit.js +59 -0
- package/dist/digits/neon-digit/index.d.ts +3 -0
- package/dist/digits/neon-digit/index.d.ts.map +1 -0
- package/dist/digits/neon-digit/index.js +1 -0
- package/dist/digits/roller3d-digit/SgRoller3DDigit.d.ts +37 -0
- package/dist/digits/roller3d-digit/SgRoller3DDigit.d.ts.map +1 -0
- package/dist/digits/roller3d-digit/SgRoller3DDigit.js +47 -0
- package/dist/digits/roller3d-digit/index.d.ts +3 -0
- package/dist/digits/roller3d-digit/index.d.ts.map +1 -0
- package/dist/digits/roller3d-digit/index.js +1 -0
- package/dist/environment/SgEnvironmentProvider.d.ts +1 -0
- package/dist/environment/SgEnvironmentProvider.d.ts.map +1 -1
- package/dist/environment/SgEnvironmentProvider.js +51 -12
- package/dist/gadgets/clock/SgClock.d.ts +3 -1
- package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
- package/dist/gadgets/clock/SgClock.js +111 -180
- package/dist/gadgets/clock/SgTimeProvider.d.ts +1 -0
- package/dist/gadgets/clock/SgTimeProvider.d.ts.map +1 -1
- package/dist/gadgets/clock/SgTimeProvider.js +11 -4
- package/dist/gadgets/gauge/SgLinearGauge.d.ts +59 -0
- package/dist/gadgets/gauge/SgLinearGauge.d.ts.map +1 -0
- package/dist/gadgets/gauge/SgLinearGauge.js +258 -0
- package/dist/gadgets/gauge/SgRadialGauge.d.ts +73 -0
- package/dist/gadgets/gauge/SgRadialGauge.d.ts.map +1 -0
- package/dist/gadgets/gauge/SgRadialGauge.js +311 -0
- package/dist/gadgets/gauge/index.d.ts +5 -0
- package/dist/gadgets/gauge/index.d.ts.map +1 -0
- package/dist/gadgets/gauge/index.js +2 -0
- package/dist/gadgets/qr-code/SgQRCode.d.ts +25 -0
- package/dist/gadgets/qr-code/SgQRCode.d.ts.map +1 -0
- package/dist/gadgets/qr-code/SgQRCode.js +75 -0
- package/dist/gadgets/qr-code/index.d.ts +3 -0
- package/dist/gadgets/qr-code/index.d.ts.map +1 -0
- package/dist/gadgets/qr-code/index.js +1 -0
- package/dist/gadgets/string-animator/SgStringAnimator.d.ts +91 -0
- package/dist/gadgets/string-animator/SgStringAnimator.d.ts.map +1 -0
- package/dist/gadgets/string-animator/SgStringAnimator.js +145 -0
- package/dist/gadgets/string-animator/index.d.ts +3 -0
- package/dist/gadgets/string-animator/index.d.ts.map +1 -0
- package/dist/gadgets/string-animator/index.js +1 -0
- package/dist/i18n/en-US.json +9 -1
- package/dist/i18n/es.json +55 -47
- package/dist/i18n/index.d.ts +32 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/pt-BR.json +9 -1
- package/dist/i18n/pt-PT.json +9 -1
- package/dist/index.d.ts +53 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/inputs/SgAutocomplete.js +21 -5
- package/dist/inputs/SgCombobox.d.ts +26 -0
- package/dist/inputs/SgCombobox.d.ts.map +1 -0
- package/dist/inputs/SgCombobox.js +354 -0
- package/dist/inputs/SgInputOTP.d.ts.map +1 -1
- package/dist/inputs/SgInputOTP.js +9 -2
- package/dist/inputs/SgRadioGroup.d.ts +37 -0
- package/dist/inputs/SgRadioGroup.d.ts.map +1 -0
- package/dist/inputs/SgRadioGroup.js +139 -0
- package/dist/inputs/SgRating.d.ts +55 -0
- package/dist/inputs/SgRating.d.ts.map +1 -0
- package/dist/inputs/SgRating.js +135 -0
- package/dist/inputs/SgSlider.d.ts +20 -0
- package/dist/inputs/SgSlider.d.ts.map +1 -0
- package/dist/inputs/SgSlider.js +40 -0
- package/dist/inputs/SgStepperInput.d.ts +22 -0
- package/dist/inputs/SgStepperInput.d.ts.map +1 -0
- package/dist/inputs/SgStepperInput.js +51 -0
- package/dist/inputs/SgTextEditor.d.ts +1 -0
- package/dist/inputs/SgTextEditor.d.ts.map +1 -1
- package/dist/inputs/SgTextEditor.js +19 -3
- package/dist/inputs/SgToggleSwitch.d.ts +36 -0
- package/dist/inputs/SgToggleSwitch.d.ts.map +1 -0
- package/dist/inputs/SgToggleSwitch.js +174 -0
- package/dist/layout/SgAccordion.d.ts +39 -0
- package/dist/layout/SgAccordion.d.ts.map +1 -0
- package/dist/layout/SgAccordion.js +116 -0
- package/dist/layout/SgBreadcrumb.d.ts +33 -0
- package/dist/layout/SgBreadcrumb.d.ts.map +1 -0
- package/dist/layout/SgBreadcrumb.js +121 -0
- package/dist/layout/SgCarousel.d.ts +43 -0
- package/dist/layout/SgCarousel.d.ts.map +1 -0
- package/dist/layout/SgCarousel.js +166 -0
- package/dist/layout/SgDockLayout.d.ts +14 -0
- package/dist/layout/SgDockLayout.d.ts.map +1 -1
- package/dist/layout/SgDockLayout.js +145 -13
- package/dist/layout/SgDockScreen.d.ts +15 -0
- package/dist/layout/SgDockScreen.d.ts.map +1 -0
- package/dist/layout/SgDockScreen.js +13 -0
- package/dist/layout/SgDockZone.d.ts.map +1 -1
- package/dist/layout/SgDockZone.js +36 -2
- package/dist/layout/SgExpandablePanel.d.ts +50 -0
- package/dist/layout/SgExpandablePanel.d.ts.map +1 -0
- package/dist/layout/SgExpandablePanel.js +302 -0
- package/dist/layout/SgMainPanel.d.ts.map +1 -1
- package/dist/layout/SgMainPanel.js +36 -14
- package/dist/layout/SgMenu.d.ts +91 -0
- package/dist/layout/SgMenu.d.ts.map +1 -0
- package/dist/layout/SgMenu.js +939 -0
- package/dist/layout/SgPageControl.d.ts +49 -0
- package/dist/layout/SgPageControl.d.ts.map +1 -0
- package/dist/layout/SgPageControl.js +152 -0
- package/dist/layout/SgPanel.d.ts.map +1 -1
- package/dist/layout/SgPanel.js +10 -1
- package/dist/layout/SgScreen.d.ts +2 -0
- package/dist/layout/SgScreen.d.ts.map +1 -1
- package/dist/layout/SgScreen.js +4 -2
- package/dist/layout/SgToolBar.d.ts +9 -3
- package/dist/layout/SgToolBar.d.ts.map +1 -1
- package/dist/layout/SgToolBar.js +461 -55
- package/dist/menus/SgDockMenu.d.ts +62 -0
- package/dist/menus/SgDockMenu.d.ts.map +1 -0
- package/dist/menus/SgDockMenu.js +480 -0
- package/dist/others/SgPlayground.js +73 -73
- package/package.json +72 -57
- package/dist/gadgets/flip-digit/SgFlipDigit.d.ts +0 -23
- package/dist/gadgets/flip-digit/SgFlipDigit.d.ts.map +0 -1
- package/dist/gadgets/flip-digit/SgFlipDigit.js +0 -118
- package/dist/gadgets/flip-digit/index.d.ts.map +0 -1
- /package/dist/{gadgets → digits}/flip-digit/index.d.ts +0 -0
- /package/dist/{gadgets → digits}/flip-digit/index.js +0 -0
|
@@ -9,11 +9,26 @@ export function useSgDockLayout() {
|
|
|
9
9
|
const EMPTY_STATE = { version: 1, toolbars: {} };
|
|
10
10
|
export function SgDockLayout(props) {
|
|
11
11
|
const { id, className, children, defaultState } = props;
|
|
12
|
-
const { value: persisted, setValue } = useSgPersistentState({
|
|
12
|
+
const { value: persisted, setValue, hydrated } = useSgPersistentState({
|
|
13
13
|
baseKey: `dock-layout:${id}`,
|
|
14
14
|
defaultValue: defaultState ?? EMPTY_STATE
|
|
15
15
|
});
|
|
16
|
+
const [isDropPreviewActive, setIsDropPreviewActive] = React.useState(false);
|
|
17
|
+
const [draggingToolbarId, setDraggingToolbarIdState] = React.useState(null);
|
|
18
|
+
const [dropIndicator, setDropIndicator] = React.useState(null);
|
|
16
19
|
const zonesRef = React.useRef(new Map());
|
|
20
|
+
const getSortedZoneToolbarIds = React.useCallback((state, zone, excludingId) => {
|
|
21
|
+
return Object.values(state)
|
|
22
|
+
.filter((tb) => tb.zone === zone && tb.id !== excludingId)
|
|
23
|
+
.sort((a, b) => {
|
|
24
|
+
const orderA = a.order ?? 0;
|
|
25
|
+
const orderB = b.order ?? 0;
|
|
26
|
+
if (orderA !== orderB)
|
|
27
|
+
return orderA - orderB;
|
|
28
|
+
return a.id.localeCompare(b.id);
|
|
29
|
+
})
|
|
30
|
+
.map((tb) => tb.id);
|
|
31
|
+
}, []);
|
|
17
32
|
const registerZone = React.useCallback((zone, el) => {
|
|
18
33
|
if (!el) {
|
|
19
34
|
zonesRef.current.delete(zone);
|
|
@@ -33,43 +48,150 @@ export function SgDockLayout(props) {
|
|
|
33
48
|
}
|
|
34
49
|
return null;
|
|
35
50
|
}, []);
|
|
51
|
+
const getDropPlacementAtPoint = React.useCallback((x, y, draggingToolbarId) => {
|
|
52
|
+
const zone = getZoneAtPoint(x, y);
|
|
53
|
+
if (!zone)
|
|
54
|
+
return null;
|
|
55
|
+
const zoneEl = zonesRef.current.get(zone);
|
|
56
|
+
if (!zoneEl)
|
|
57
|
+
return null;
|
|
58
|
+
const axisHorizontal = zone === "top" || zone === "bottom";
|
|
59
|
+
const toolbarEls = Array.from(zoneEl.querySelectorAll("[data-sg-toolbar-root='true'][data-sg-toolbar-id]"))
|
|
60
|
+
.filter((el) => el.dataset.sgToolbarId !== draggingToolbarId)
|
|
61
|
+
.sort((a, b) => {
|
|
62
|
+
const rectA = a.getBoundingClientRect();
|
|
63
|
+
const rectB = b.getBoundingClientRect();
|
|
64
|
+
if (axisHorizontal) {
|
|
65
|
+
if (rectA.top !== rectB.top)
|
|
66
|
+
return rectA.top - rectB.top;
|
|
67
|
+
return rectA.left - rectB.left;
|
|
68
|
+
}
|
|
69
|
+
if (rectA.left !== rectB.left)
|
|
70
|
+
return rectA.left - rectB.left;
|
|
71
|
+
return rectA.top - rectB.top;
|
|
72
|
+
});
|
|
73
|
+
const cursorMajor = axisHorizontal ? x : y;
|
|
74
|
+
let index = toolbarEls.length;
|
|
75
|
+
for (let i = 0; i < toolbarEls.length; i += 1) {
|
|
76
|
+
const toolbarEl = toolbarEls[i];
|
|
77
|
+
if (!toolbarEl)
|
|
78
|
+
continue;
|
|
79
|
+
const rect = toolbarEl.getBoundingClientRect();
|
|
80
|
+
const majorCenter = axisHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
81
|
+
if (cursorMajor < majorCenter) {
|
|
82
|
+
index = i;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { zone, index };
|
|
87
|
+
}, [getZoneAtPoint]);
|
|
88
|
+
const setDropPreviewActive = React.useCallback((next) => {
|
|
89
|
+
setIsDropPreviewActive((prev) => (prev === next ? prev : next));
|
|
90
|
+
if (!next) {
|
|
91
|
+
setDropIndicator(null);
|
|
92
|
+
setDraggingToolbarIdState(null);
|
|
93
|
+
}
|
|
94
|
+
}, []);
|
|
95
|
+
const setDraggingToolbarId = React.useCallback((toolbarId) => {
|
|
96
|
+
setDraggingToolbarIdState((prev) => (prev === toolbarId ? prev : toolbarId));
|
|
97
|
+
}, []);
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
if (!isDropPreviewActive)
|
|
100
|
+
return;
|
|
101
|
+
const clearPreview = () => setDropPreviewActive(false);
|
|
102
|
+
const handleVisibilityChange = () => {
|
|
103
|
+
if (document.hidden)
|
|
104
|
+
clearPreview();
|
|
105
|
+
};
|
|
106
|
+
window.addEventListener("pointerup", clearPreview);
|
|
107
|
+
window.addEventListener("pointercancel", clearPreview);
|
|
108
|
+
window.addEventListener("blur", clearPreview);
|
|
109
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
110
|
+
return () => {
|
|
111
|
+
window.removeEventListener("pointerup", clearPreview);
|
|
112
|
+
window.removeEventListener("pointercancel", clearPreview);
|
|
113
|
+
window.removeEventListener("blur", clearPreview);
|
|
114
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
115
|
+
};
|
|
116
|
+
}, [isDropPreviewActive, setDropPreviewActive]);
|
|
36
117
|
const getToolbarZone = React.useCallback((toolbarId) => persisted.toolbars[toolbarId]?.zone ?? null, [persisted]);
|
|
118
|
+
const getToolbarOrder = React.useCallback((toolbarId) => {
|
|
119
|
+
const toolbar = persisted.toolbars[toolbarId];
|
|
120
|
+
if (!toolbar)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (draggingToolbarId && toolbarId === draggingToolbarId)
|
|
123
|
+
return undefined;
|
|
124
|
+
const ids = getSortedZoneToolbarIds(persisted.toolbars, toolbar.zone, draggingToolbarId ?? undefined);
|
|
125
|
+
const index = ids.indexOf(toolbarId);
|
|
126
|
+
return index === -1 ? undefined : index;
|
|
127
|
+
}, [draggingToolbarId, getSortedZoneToolbarIds, persisted]);
|
|
128
|
+
const getZoneToolbarCount = React.useCallback((zone) => {
|
|
129
|
+
const zoneEl = zonesRef.current.get(zone);
|
|
130
|
+
if (!zoneEl)
|
|
131
|
+
return 0;
|
|
132
|
+
return Array.from(zoneEl.querySelectorAll("[data-sg-toolbar-root='true'][data-sg-toolbar-id]")).filter((el) => el.dataset.sgToolbarId !== draggingToolbarId).length;
|
|
133
|
+
}, [draggingToolbarId]);
|
|
37
134
|
const ensureToolbar = React.useCallback((toolbarId, state) => {
|
|
135
|
+
if (!hydrated)
|
|
136
|
+
return;
|
|
38
137
|
setValue((prev) => {
|
|
39
138
|
if (prev.toolbars[toolbarId])
|
|
40
139
|
return prev;
|
|
140
|
+
const targetZone = state.zone ?? "free";
|
|
141
|
+
const zoneIds = getSortedZoneToolbarIds(prev.toolbars, targetZone);
|
|
41
142
|
return {
|
|
42
143
|
...prev,
|
|
43
144
|
toolbars: {
|
|
44
145
|
...prev.toolbars,
|
|
45
146
|
[toolbarId]: {
|
|
46
147
|
id: toolbarId,
|
|
47
|
-
zone:
|
|
148
|
+
zone: targetZone,
|
|
48
149
|
collapsed: state.collapsed ?? false,
|
|
49
150
|
orientation: state.orientation,
|
|
50
|
-
order: state.order ??
|
|
151
|
+
order: state.order ?? zoneIds.length
|
|
51
152
|
}
|
|
52
153
|
}
|
|
53
154
|
};
|
|
54
155
|
});
|
|
55
|
-
}, [setValue]);
|
|
56
|
-
const
|
|
156
|
+
}, [getSortedZoneToolbarIds, hydrated, setValue]);
|
|
157
|
+
const placeToolbar = React.useCallback((toolbarId, zone, index) => {
|
|
57
158
|
setValue((prev) => {
|
|
58
159
|
const current = prev.toolbars[toolbarId];
|
|
59
160
|
if (!current)
|
|
60
161
|
return prev;
|
|
162
|
+
const sourceZone = current.zone;
|
|
163
|
+
const targetZone = zone;
|
|
164
|
+
const nextToolbars = {
|
|
165
|
+
...prev.toolbars
|
|
166
|
+
};
|
|
167
|
+
const targetIds = getSortedZoneToolbarIds(prev.toolbars, targetZone, toolbarId);
|
|
168
|
+
const safeIndex = Math.max(0, Math.min(index, targetIds.length));
|
|
169
|
+
targetIds.splice(safeIndex, 0, toolbarId);
|
|
170
|
+
targetIds.forEach((tbId, i) => {
|
|
171
|
+
const base = nextToolbars[tbId];
|
|
172
|
+
if (!base)
|
|
173
|
+
return;
|
|
174
|
+
nextToolbars[tbId] = { ...base, zone: targetZone, order: i };
|
|
175
|
+
});
|
|
176
|
+
if (sourceZone !== targetZone) {
|
|
177
|
+
const sourceIds = getSortedZoneToolbarIds(prev.toolbars, sourceZone, toolbarId);
|
|
178
|
+
sourceIds.forEach((tbId, i) => {
|
|
179
|
+
const base = nextToolbars[tbId];
|
|
180
|
+
if (!base)
|
|
181
|
+
return;
|
|
182
|
+
nextToolbars[tbId] = { ...base, order: i };
|
|
183
|
+
});
|
|
184
|
+
}
|
|
61
185
|
return {
|
|
62
186
|
...prev,
|
|
63
|
-
toolbars:
|
|
64
|
-
...prev.toolbars,
|
|
65
|
-
[toolbarId]: {
|
|
66
|
-
...current,
|
|
67
|
-
zone
|
|
68
|
-
}
|
|
69
|
-
}
|
|
187
|
+
toolbars: nextToolbars
|
|
70
188
|
};
|
|
71
189
|
});
|
|
72
|
-
}, [setValue]);
|
|
190
|
+
}, [getSortedZoneToolbarIds, setValue]);
|
|
191
|
+
const moveToolbar = React.useCallback((toolbarId, zone) => {
|
|
192
|
+
const currentZoneIds = getSortedZoneToolbarIds(persisted.toolbars, zone, toolbarId);
|
|
193
|
+
placeToolbar(toolbarId, zone, currentZoneIds.length);
|
|
194
|
+
}, [getSortedZoneToolbarIds, persisted.toolbars, placeToolbar]);
|
|
73
195
|
const getToolbarCollapsed = React.useCallback((toolbarId) => persisted.toolbars[toolbarId]?.collapsed, [persisted]);
|
|
74
196
|
const setToolbarCollapsed = React.useCallback((toolbarId, next) => {
|
|
75
197
|
setValue((prev) => {
|
|
@@ -90,8 +212,18 @@ export function SgDockLayout(props) {
|
|
|
90
212
|
getZoneElement,
|
|
91
213
|
registerZone,
|
|
92
214
|
getZoneAtPoint,
|
|
215
|
+
getDropPlacementAtPoint,
|
|
216
|
+
getZoneToolbarCount,
|
|
217
|
+
isDropPreviewActive,
|
|
218
|
+
setDropPreviewActive,
|
|
219
|
+
draggingToolbarId,
|
|
220
|
+
setDraggingToolbarId,
|
|
221
|
+
dropIndicator,
|
|
222
|
+
setDropIndicator,
|
|
93
223
|
getToolbarZone,
|
|
224
|
+
getToolbarOrder,
|
|
94
225
|
moveToolbar,
|
|
226
|
+
placeToolbar,
|
|
95
227
|
ensureToolbar,
|
|
96
228
|
getToolbarCollapsed,
|
|
97
229
|
setToolbarCollapsed
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type SgDockLayoutState } from "./SgDockLayout";
|
|
3
|
+
import { type SgScreenProps } from "./SgScreen";
|
|
4
|
+
export type SgDockScreenProps = Omit<SgScreenProps, "children" | "id"> & {
|
|
5
|
+
id: string;
|
|
6
|
+
screenId?: string;
|
|
7
|
+
defaultState?: SgDockLayoutState;
|
|
8
|
+
layoutClassName?: string;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export declare function SgDockScreen(props: Readonly<SgDockScreenProps>): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare namespace SgDockScreen {
|
|
13
|
+
var displayName: string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=SgDockScreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgDockScreen.d.ts","sourceRoot":"","sources":["../../src/layout/SgDockScreen.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAM1D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAIF,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,iBAAiB,CAAC,2CAqB9D;yBArBe,YAAY"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { SgDockLayout } from "./SgDockLayout";
|
|
4
|
+
import { SgScreen } from "./SgScreen";
|
|
5
|
+
function cn(...parts) {
|
|
6
|
+
return parts.filter(Boolean).join(" ");
|
|
7
|
+
}
|
|
8
|
+
const AUTO_LAYOUT_ROOT_CLASS = "relative grid h-full w-full min-h-0 min-w-0 grid-cols-[12rem_1fr_12rem] grid-rows-[auto_1fr_auto]";
|
|
9
|
+
export function SgDockScreen(props) {
|
|
10
|
+
const { id, screenId, defaultState, layoutClassName, children, ...screenProps } = props;
|
|
11
|
+
return (_jsx(SgScreen, { ...screenProps, id: screenId, children: _jsx(SgDockLayout, { id: id, defaultState: defaultState, className: cn(AUTO_LAYOUT_ROOT_CLASS, layoutClassName), children: children }) }));
|
|
12
|
+
}
|
|
13
|
+
SgDockScreen.displayName = "SgDockScreen";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgDockZone.d.ts","sourceRoot":"","sources":["../../src/layout/SgDockZone.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"SgDockZone.d.ts","sourceRoot":"","sources":["../../src/layout/SgDockZone.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAeF,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CA0E1D;yBA1Ee,UAAU"}
|
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useSgDockLayout } from "./SgDockLayout";
|
|
5
|
+
import { t, useComponentsI18n } from "../i18n";
|
|
5
6
|
function cn(...parts) {
|
|
6
7
|
return parts.filter(Boolean).join(" ");
|
|
7
8
|
}
|
|
9
|
+
const POSITION_CLASS_PATTERN = /(?:^|\s)!?(?:(?:[a-z0-9-]+:)+)?(?:static|fixed|absolute|relative|sticky)(?=\s|$)/i;
|
|
10
|
+
const DEFAULT_ZONE_LAYOUT_CLASS = {
|
|
11
|
+
top: "col-span-3 row-start-1 items-start",
|
|
12
|
+
bottom: "col-span-3 row-start-3 items-end",
|
|
13
|
+
left: "col-start-1 row-start-2 items-start",
|
|
14
|
+
right: "col-start-3 row-start-2 items-start",
|
|
15
|
+
free: "col-start-2 row-start-2 items-center justify-center"
|
|
16
|
+
};
|
|
8
17
|
export function SgDockZone(props) {
|
|
9
18
|
const { zone, className, children } = props;
|
|
10
19
|
const dock = useSgDockLayout();
|
|
20
|
+
const i18n = useComponentsI18n();
|
|
11
21
|
const ref = React.useRef(null);
|
|
22
|
+
const showDropPreview = Boolean(dock?.isDropPreviewActive);
|
|
23
|
+
const dropIndicator = dock?.dropIndicator ?? null;
|
|
24
|
+
const zoneToolbarCount = dock?.getZoneToolbarCount(zone) ?? 0;
|
|
25
|
+
const showSlotPreview = showDropPreview && zone !== "free" && Boolean(dock?.draggingToolbarId);
|
|
26
|
+
const dropSlotIndexes = React.useMemo(() => {
|
|
27
|
+
if (!showSlotPreview)
|
|
28
|
+
return [];
|
|
29
|
+
const slotCount = Math.max(1, zoneToolbarCount + 1);
|
|
30
|
+
return Array.from({ length: slotCount }, (_, i) => i);
|
|
31
|
+
}, [showSlotPreview, zoneToolbarCount]);
|
|
32
|
+
const isHorizontalZone = zone === "top" || zone === "bottom";
|
|
33
|
+
const isVerticalZone = zone === "left" || zone === "right";
|
|
34
|
+
const hasExplicitPositionClass = POSITION_CLASS_PATTERN.test(className ?? "");
|
|
35
|
+
const hasCustomClass = Boolean(className?.trim());
|
|
36
|
+
const zoneDefaultLayoutClass = hasCustomClass ? null : DEFAULT_ZONE_LAYOUT_CLASS[zone];
|
|
12
37
|
React.useEffect(() => {
|
|
13
38
|
if (!dock)
|
|
14
39
|
return;
|
|
15
40
|
dock.registerZone(zone, ref.current);
|
|
16
41
|
return () => dock.registerZone(zone, null);
|
|
17
42
|
}, [dock, zone]);
|
|
18
|
-
return (
|
|
43
|
+
return (_jsxs("div", { ref: ref, "data-sg-dock-zone": zone, className: cn(hasExplicitPositionClass ? "flex min-h-0 min-w-0 gap-3 p-2" : "relative flex min-h-0 min-w-0 gap-3 p-2", isHorizontalZone
|
|
44
|
+
? "flex-row flex-wrap items-start content-start"
|
|
45
|
+
: isVerticalZone
|
|
46
|
+
? "flex-col flex-wrap items-start content-start"
|
|
47
|
+
: "flex-col items-center", showDropPreview ? "rounded-xl border-2 border-dashed border-border/70 bg-background/40 p-3 transition-colors duration-150" : null, showDropPreview && isHorizontalZone ? "min-h-16" : null, showDropPreview && isVerticalZone ? "min-w-24" : null, zoneDefaultLayoutClass, className), children: [children, dropSlotIndexes.map((slotIndex) => {
|
|
48
|
+
const isActiveSlot = dropIndicator?.zone === zone && dropIndicator.index === slotIndex;
|
|
49
|
+
return (_jsx("span", { className: cn("pointer-events-none relative z-[2] inline-flex shrink-0 items-center justify-center rounded border border-dashed text-[11px] font-semibold uppercase tracking-wide transition-colors duration-100", isHorizontalZone ? "h-10 w-16" : "h-8 w-20 self-center", isActiveSlot
|
|
50
|
+
? "border-primary/70 bg-background/95 text-primary"
|
|
51
|
+
: "border-border/60 bg-background/70 text-foreground/50"), style: { order: slotIndex * 2 - 1 }, "aria-hidden": "true", children: "><" }, `drop-slot-${zone}-${slotIndex}`));
|
|
52
|
+
}), showDropPreview && !showSlotPreview ? (_jsx("span", { className: "pointer-events-none absolute inset-0 z-[1] flex items-center justify-center text-xs font-semibold uppercase tracking-wide text-foreground/70", "aria-hidden": "true", children: t(i18n, "components.dock.dropHere") })) : null] }));
|
|
19
53
|
}
|
|
20
54
|
SgDockZone.displayName = "SgDockZone";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SgExpandablePanelDirection = "left" | "right" | "top" | "bottom";
|
|
3
|
+
export type SgExpandablePanelPlacement = "start" | "center" | "end";
|
|
4
|
+
export type SgExpandablePanelMode = "inline" | "overlay";
|
|
5
|
+
export type SgExpandablePanelElevation = "none" | "sm" | "md" | "lg";
|
|
6
|
+
export type SgExpandablePanelRounded = "none" | "md" | "lg" | "xl";
|
|
7
|
+
export type SgExpandablePanelRole = "dialog" | "region";
|
|
8
|
+
export type SgExpandablePanelSize = {
|
|
9
|
+
min?: number | string;
|
|
10
|
+
max?: number | string;
|
|
11
|
+
default?: number | string;
|
|
12
|
+
};
|
|
13
|
+
export type SgExpandablePanelAnimation = {
|
|
14
|
+
type?: "slide" | "fade" | "none";
|
|
15
|
+
durationMs?: number;
|
|
16
|
+
};
|
|
17
|
+
export type SgExpandablePanelProps = {
|
|
18
|
+
header?: React.ReactNode;
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
footer?: React.ReactNode;
|
|
21
|
+
handle?: React.ReactNode;
|
|
22
|
+
open?: boolean;
|
|
23
|
+
defaultOpen?: boolean;
|
|
24
|
+
onOpenChange?: (open: boolean) => void;
|
|
25
|
+
expandTo: SgExpandablePanelDirection;
|
|
26
|
+
placement?: SgExpandablePanelPlacement;
|
|
27
|
+
mode?: SgExpandablePanelMode;
|
|
28
|
+
size?: SgExpandablePanelSize;
|
|
29
|
+
resizable?: boolean;
|
|
30
|
+
onSizeChange?: (size: number | string) => void;
|
|
31
|
+
closeOnOutsideClick?: boolean;
|
|
32
|
+
closeOnEsc?: boolean;
|
|
33
|
+
trapFocus?: boolean;
|
|
34
|
+
showBackdrop?: boolean;
|
|
35
|
+
animation?: SgExpandablePanelAnimation;
|
|
36
|
+
elevation?: SgExpandablePanelElevation;
|
|
37
|
+
border?: boolean;
|
|
38
|
+
rounded?: SgExpandablePanelRounded;
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
role?: SgExpandablePanelRole;
|
|
41
|
+
className?: string;
|
|
42
|
+
contentClassName?: string;
|
|
43
|
+
headerClassName?: string;
|
|
44
|
+
bodyClassName?: string;
|
|
45
|
+
footerClassName?: string;
|
|
46
|
+
overlayClassName?: string;
|
|
47
|
+
style?: React.CSSProperties;
|
|
48
|
+
};
|
|
49
|
+
export declare const SgExpandablePanel: React.ForwardRefExoticComponent<SgExpandablePanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
50
|
+
//# sourceMappingURL=SgExpandablePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgExpandablePanel.d.ts","sourceRoot":"","sources":["../../src/layout/SgExpandablePanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC7E,MAAM,MAAM,0BAA0B,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,SAAS,CAAC;AACzD,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACrE,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,SAAS,CAAC,EAAE,0BAA0B,CAAC;IACvC,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAE7B,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAE/C,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,SAAS,CAAC,EAAE,0BAA0B,CAAC;IAEvC,SAAS,CAAC,EAAE,0BAA0B,CAAC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,wBAAwB,CAAC;IAEnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAE7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AA6DF,eAAO,MAAM,iBAAiB,+FA4Y7B,CAAC"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
function cn(...parts) {
|
|
6
|
+
return parts.filter(Boolean).join(" ");
|
|
7
|
+
}
|
|
8
|
+
function clamp(value, min, max) {
|
|
9
|
+
return Math.max(min, Math.min(value, max));
|
|
10
|
+
}
|
|
11
|
+
function toCssSize(value, fallback) {
|
|
12
|
+
if (value === undefined || value === null)
|
|
13
|
+
return `${fallback}px`;
|
|
14
|
+
if (typeof value === "number")
|
|
15
|
+
return `${value}px`;
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
return trimmed.length > 0 ? trimmed : `${fallback}px`;
|
|
18
|
+
}
|
|
19
|
+
function parsePx(value) {
|
|
20
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
21
|
+
return value;
|
|
22
|
+
if (typeof value !== "string")
|
|
23
|
+
return undefined;
|
|
24
|
+
const trimmed = value.trim().toLowerCase();
|
|
25
|
+
if (!trimmed)
|
|
26
|
+
return undefined;
|
|
27
|
+
const match = /^(-?\d+(?:\.\d+)?)(px)?$/.exec(trimmed);
|
|
28
|
+
if (!match)
|
|
29
|
+
return undefined;
|
|
30
|
+
const parsed = Number(match[1]);
|
|
31
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
32
|
+
}
|
|
33
|
+
function axisForDirection(direction) {
|
|
34
|
+
return direction === "left" || direction === "right" ? "x" : "y";
|
|
35
|
+
}
|
|
36
|
+
function closedTransform(direction) {
|
|
37
|
+
if (direction === "right")
|
|
38
|
+
return "-translate-x-full";
|
|
39
|
+
if (direction === "left")
|
|
40
|
+
return "translate-x-full";
|
|
41
|
+
if (direction === "bottom")
|
|
42
|
+
return "-translate-y-full";
|
|
43
|
+
return "translate-y-full";
|
|
44
|
+
}
|
|
45
|
+
function elevationClass(elevation) {
|
|
46
|
+
if (elevation === "sm")
|
|
47
|
+
return "shadow-sm";
|
|
48
|
+
if (elevation === "md")
|
|
49
|
+
return "shadow-md";
|
|
50
|
+
if (elevation === "lg")
|
|
51
|
+
return "shadow-lg";
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
function roundedClass(rounded) {
|
|
55
|
+
if (rounded === "none")
|
|
56
|
+
return "rounded-none";
|
|
57
|
+
if (rounded === "md")
|
|
58
|
+
return "rounded-md";
|
|
59
|
+
if (rounded === "lg")
|
|
60
|
+
return "rounded-lg";
|
|
61
|
+
return "rounded-xl";
|
|
62
|
+
}
|
|
63
|
+
function getFocusableElements(root) {
|
|
64
|
+
return Array.from(root.querySelectorAll(`button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])`)).filter((el) => !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true");
|
|
65
|
+
}
|
|
66
|
+
export const SgExpandablePanel = React.forwardRef(function SgExpandablePanel(props, ref) {
|
|
67
|
+
const { header, children, footer, handle, open, defaultOpen = false, onOpenChange, expandTo, placement = "start", mode = "inline", size, resizable = false, onSizeChange, closeOnOutsideClick, closeOnEsc = true, trapFocus, showBackdrop = true, animation, elevation = "md", border = true, rounded = "lg", ariaLabel, role, className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, style } = props;
|
|
68
|
+
const isControlled = open !== undefined;
|
|
69
|
+
const [openUncontrolled, setOpenUncontrolled] = React.useState(defaultOpen);
|
|
70
|
+
const isOpen = isControlled ? !!open : openUncontrolled;
|
|
71
|
+
const setOpen = React.useCallback((next) => {
|
|
72
|
+
if (!isControlled)
|
|
73
|
+
setOpenUncontrolled(next);
|
|
74
|
+
onOpenChange?.(next);
|
|
75
|
+
}, [isControlled, onOpenChange]);
|
|
76
|
+
const axis = axisForDirection(expandTo);
|
|
77
|
+
const fallbackMainSize = axis === "x" ? 320 : 280;
|
|
78
|
+
const [mainSize, setMainSize] = React.useState(size?.default ?? fallbackMainSize);
|
|
79
|
+
React.useEffect(() => {
|
|
80
|
+
if (size?.default !== undefined) {
|
|
81
|
+
setMainSize(size.default);
|
|
82
|
+
}
|
|
83
|
+
}, [size?.default]);
|
|
84
|
+
const minPx = parsePx(size?.min) ?? (axis === "x" ? 180 : 140);
|
|
85
|
+
const maxPx = parsePx(size?.max) ?? Number.POSITIVE_INFINITY;
|
|
86
|
+
const mainSizeCss = toCssSize(mainSize, fallbackMainSize);
|
|
87
|
+
const minSizeCss = size?.min !== undefined ? toCssSize(size.min, minPx) : undefined;
|
|
88
|
+
const maxSizeCss = size?.max !== undefined && Number.isFinite(maxPx)
|
|
89
|
+
? toCssSize(size.max, maxPx)
|
|
90
|
+
: size?.max !== undefined
|
|
91
|
+
? toCssSize(size.max, fallbackMainSize)
|
|
92
|
+
: undefined;
|
|
93
|
+
const resolvedTrapFocus = trapFocus ?? mode === "overlay";
|
|
94
|
+
const resolvedCloseOnOutsideClick = closeOnOutsideClick ?? mode === "overlay";
|
|
95
|
+
const shouldRenderBackdrop = showBackdrop || resolvedCloseOnOutsideClick;
|
|
96
|
+
const animationType = animation?.type ?? "slide";
|
|
97
|
+
const durationMs = animation?.durationMs ?? 180;
|
|
98
|
+
const [mounted, setMounted] = React.useState(false);
|
|
99
|
+
const [present, setPresent] = React.useState(isOpen);
|
|
100
|
+
const [entered, setEntered] = React.useState(isOpen);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
setMounted(true);
|
|
103
|
+
}, []);
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
if (mode !== "overlay") {
|
|
106
|
+
setEntered(isOpen);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (isOpen) {
|
|
110
|
+
setPresent(true);
|
|
111
|
+
const id = window.requestAnimationFrame(() => setEntered(true));
|
|
112
|
+
return () => window.cancelAnimationFrame(id);
|
|
113
|
+
}
|
|
114
|
+
setEntered(false);
|
|
115
|
+
const delay = animationType === "none" ? 0 : durationMs;
|
|
116
|
+
const id = window.setTimeout(() => setPresent(false), delay);
|
|
117
|
+
return () => window.clearTimeout(id);
|
|
118
|
+
}, [mode, isOpen, animationType, durationMs]);
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (mode !== "overlay" || !present || !isOpen)
|
|
121
|
+
return;
|
|
122
|
+
const original = document.body.style.overflow;
|
|
123
|
+
document.body.style.overflow = "hidden";
|
|
124
|
+
return () => {
|
|
125
|
+
document.body.style.overflow = original;
|
|
126
|
+
};
|
|
127
|
+
}, [mode, present, isOpen]);
|
|
128
|
+
const panelRef = React.useRef(null);
|
|
129
|
+
const overlayRef = React.useRef(null);
|
|
130
|
+
const mergedRef = React.useMemo(() => (node) => {
|
|
131
|
+
panelRef.current = node;
|
|
132
|
+
if (typeof ref === "function")
|
|
133
|
+
ref(node);
|
|
134
|
+
else if (ref)
|
|
135
|
+
ref.current = node;
|
|
136
|
+
}, [ref]);
|
|
137
|
+
const lastActiveRef = React.useRef(null);
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
if (mode !== "overlay" || !isOpen)
|
|
140
|
+
return;
|
|
141
|
+
lastActiveRef.current = document.activeElement;
|
|
142
|
+
const id = window.setTimeout(() => {
|
|
143
|
+
const root = panelRef.current;
|
|
144
|
+
if (!root)
|
|
145
|
+
return;
|
|
146
|
+
const focusables = getFocusableElements(root);
|
|
147
|
+
(focusables[0] ?? root).focus?.();
|
|
148
|
+
}, 0);
|
|
149
|
+
return () => window.clearTimeout(id);
|
|
150
|
+
}, [mode, isOpen]);
|
|
151
|
+
React.useEffect(() => {
|
|
152
|
+
if (mode !== "overlay")
|
|
153
|
+
return;
|
|
154
|
+
if (isOpen || present)
|
|
155
|
+
return;
|
|
156
|
+
lastActiveRef.current?.focus?.();
|
|
157
|
+
}, [mode, isOpen, present]);
|
|
158
|
+
React.useEffect(() => {
|
|
159
|
+
if (mode !== "overlay" || !isOpen)
|
|
160
|
+
return;
|
|
161
|
+
const onKeyDown = (event) => {
|
|
162
|
+
if (closeOnEsc && event.key === "Escape") {
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
setOpen(false);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!resolvedTrapFocus || event.key !== "Tab")
|
|
168
|
+
return;
|
|
169
|
+
const root = panelRef.current;
|
|
170
|
+
if (!root)
|
|
171
|
+
return;
|
|
172
|
+
const focusables = getFocusableElements(root);
|
|
173
|
+
if (focusables.length === 0)
|
|
174
|
+
return;
|
|
175
|
+
const first = focusables[0];
|
|
176
|
+
const last = focusables[focusables.length - 1];
|
|
177
|
+
if (!first || !last)
|
|
178
|
+
return;
|
|
179
|
+
const active = document.activeElement;
|
|
180
|
+
if (!event.shiftKey && active === last) {
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
first.focus();
|
|
183
|
+
}
|
|
184
|
+
else if (event.shiftKey && active === first) {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
last.focus();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
document.addEventListener("keydown", onKeyDown);
|
|
190
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
191
|
+
}, [mode, isOpen, closeOnEsc, resolvedTrapFocus, setOpen]);
|
|
192
|
+
const resizeState = React.useRef(null);
|
|
193
|
+
const onResizeStart = React.useCallback((event) => {
|
|
194
|
+
if (!resizable)
|
|
195
|
+
return;
|
|
196
|
+
const panel = panelRef.current;
|
|
197
|
+
if (!panel)
|
|
198
|
+
return;
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
event.stopPropagation();
|
|
201
|
+
const rect = panel.getBoundingClientRect();
|
|
202
|
+
resizeState.current = {
|
|
203
|
+
startX: event.clientX,
|
|
204
|
+
startY: event.clientY,
|
|
205
|
+
startSize: axis === "x" ? rect.width : rect.height
|
|
206
|
+
};
|
|
207
|
+
const onMove = (moveEvent) => {
|
|
208
|
+
if (!resizeState.current)
|
|
209
|
+
return;
|
|
210
|
+
const dx = moveEvent.clientX - resizeState.current.startX;
|
|
211
|
+
const dy = moveEvent.clientY - resizeState.current.startY;
|
|
212
|
+
const delta = axis === "x"
|
|
213
|
+
? expandTo === "right"
|
|
214
|
+
? dx
|
|
215
|
+
: -dx
|
|
216
|
+
: expandTo === "bottom"
|
|
217
|
+
? dy
|
|
218
|
+
: -dy;
|
|
219
|
+
const next = clamp(resizeState.current.startSize + delta, minPx, maxPx);
|
|
220
|
+
setMainSize(next);
|
|
221
|
+
onSizeChange?.(next);
|
|
222
|
+
};
|
|
223
|
+
const onUp = () => {
|
|
224
|
+
resizeState.current = null;
|
|
225
|
+
window.removeEventListener("pointermove", onMove);
|
|
226
|
+
window.removeEventListener("pointerup", onUp);
|
|
227
|
+
window.removeEventListener("pointercancel", onUp);
|
|
228
|
+
};
|
|
229
|
+
window.addEventListener("pointermove", onMove);
|
|
230
|
+
window.addEventListener("pointerup", onUp);
|
|
231
|
+
window.addEventListener("pointercancel", onUp);
|
|
232
|
+
}, [axis, expandTo, maxPx, minPx, onSizeChange, resizable]);
|
|
233
|
+
const panelBaseClass = cn("relative min-h-0 min-w-0 overflow-hidden bg-background text-foreground", "flex flex-col", border ? "border border-border" : "", roundedClass(rounded), elevationClass(elevation), animationType === "none"
|
|
234
|
+
? ""
|
|
235
|
+
: "transition-[transform,opacity,width,height] ease-out", contentClassName);
|
|
236
|
+
const panelRole = role ?? (mode === "overlay" ? "dialog" : "region");
|
|
237
|
+
const panelAriaLabel = ariaLabel ?? "Expandable panel";
|
|
238
|
+
const transitionStyle = animationType === "none" ? { transitionDuration: "0ms" } : { transitionDuration: `${durationMs}ms` };
|
|
239
|
+
const resizeHandleClass = expandTo === "right"
|
|
240
|
+
? "absolute inset-y-0 right-0 w-1 cursor-ew-resize"
|
|
241
|
+
: expandTo === "left"
|
|
242
|
+
? "absolute inset-y-0 left-0 w-1 cursor-ew-resize"
|
|
243
|
+
: expandTo === "bottom"
|
|
244
|
+
? "absolute inset-x-0 bottom-0 h-1 cursor-ns-resize"
|
|
245
|
+
: "absolute inset-x-0 top-0 h-1 cursor-ns-resize";
|
|
246
|
+
const renderPanelContent = (extraStyle, stateClass) => (_jsxs("div", { ref: mergedRef, role: panelRole, "aria-modal": mode === "overlay" ? true : undefined, "aria-label": panelAriaLabel, tabIndex: -1, className: cn(panelBaseClass, stateClass, className), style: { ...extraStyle, ...style, ...transitionStyle }, children: [handle ? _jsx("div", { className: "px-3 pt-2", children: handle }) : null, header ? (_jsx("div", { className: cn("shrink-0 border-b border-border px-4 py-3", headerClassName), children: header })) : null, _jsx("div", { className: cn("min-h-0 flex-1 overflow-auto px-4 py-3", bodyClassName), children: children }), footer ? (_jsx("div", { className: cn("shrink-0 border-t border-border px-4 py-3", footerClassName), children: footer })) : null, resizable ? (_jsx("div", { className: cn(resizeHandleClass, "z-20 bg-transparent transition-colors hover:bg-primary/20 active:bg-primary/30"), onPointerDown: onResizeStart, "aria-hidden": "true" })) : null] }));
|
|
247
|
+
if (mode === "inline") {
|
|
248
|
+
const wrapperStyle = axis === "x"
|
|
249
|
+
? {
|
|
250
|
+
width: isOpen ? mainSizeCss : "0px",
|
|
251
|
+
minWidth: isOpen ? minSizeCss : undefined,
|
|
252
|
+
maxWidth: maxSizeCss
|
|
253
|
+
}
|
|
254
|
+
: {
|
|
255
|
+
height: isOpen ? mainSizeCss : "0px",
|
|
256
|
+
minHeight: isOpen ? minSizeCss : undefined,
|
|
257
|
+
maxHeight: maxSizeCss
|
|
258
|
+
};
|
|
259
|
+
const inlineStateClass = animationType === "fade"
|
|
260
|
+
? isOpen
|
|
261
|
+
? "opacity-100"
|
|
262
|
+
: "opacity-0"
|
|
263
|
+
: animationType === "slide"
|
|
264
|
+
? cn(isOpen ? "opacity-100 translate-x-0 translate-y-0" : "opacity-0", !isOpen ? closedTransform(expandTo) : "")
|
|
265
|
+
: "";
|
|
266
|
+
return (_jsx("div", { className: cn("relative min-h-0 min-w-0 overflow-hidden", animationType === "none" ? "" : "transition-[width,height,opacity] ease-out"), style: { ...wrapperStyle, ...transitionStyle }, children: renderPanelContent(axis === "x"
|
|
267
|
+
? { width: "100%", minWidth: 0, height: "100%" }
|
|
268
|
+
: { height: "100%", minHeight: 0, width: "100%" }, inlineStateClass) }));
|
|
269
|
+
}
|
|
270
|
+
if (!mounted || !present)
|
|
271
|
+
return null;
|
|
272
|
+
const overlayContainerClass = axis === "x"
|
|
273
|
+
? cn("fixed inset-0 z-[1000] flex pointer-events-none", expandTo === "right" ? "justify-start" : "justify-end", placement === "start" ? "items-start" : placement === "center" ? "items-center" : "items-end")
|
|
274
|
+
: cn("fixed inset-0 z-[1000] flex flex-col pointer-events-none", expandTo === "bottom" ? "justify-start" : "justify-end", placement === "start" ? "items-start" : placement === "center" ? "items-center" : "items-end");
|
|
275
|
+
const overlayPanelStyle = axis === "x"
|
|
276
|
+
? {
|
|
277
|
+
width: mainSizeCss,
|
|
278
|
+
minWidth: minSizeCss,
|
|
279
|
+
maxWidth: maxSizeCss,
|
|
280
|
+
height: placement === "center" ? "calc(100% - 2rem)" : "100%"
|
|
281
|
+
}
|
|
282
|
+
: {
|
|
283
|
+
height: mainSizeCss,
|
|
284
|
+
minHeight: minSizeCss,
|
|
285
|
+
maxHeight: maxSizeCss,
|
|
286
|
+
width: placement === "center" ? "calc(100% - 2rem)" : "100%"
|
|
287
|
+
};
|
|
288
|
+
const overlayStateClass = animationType === "fade"
|
|
289
|
+
? entered
|
|
290
|
+
? "opacity-100"
|
|
291
|
+
: "opacity-0"
|
|
292
|
+
: animationType === "slide"
|
|
293
|
+
? cn(entered ? "opacity-100 translate-x-0 translate-y-0" : "opacity-0", !entered ? closedTransform(expandTo) : "")
|
|
294
|
+
: "";
|
|
295
|
+
return createPortal(_jsxs("div", { className: "fixed inset-0 z-[1000] pointer-events-none", children: [shouldRenderBackdrop ? (_jsx("div", { ref: overlayRef, className: cn("absolute inset-0 pointer-events-auto", showBackdrop ? "bg-black/40 backdrop-blur-[1px]" : "bg-transparent", animationType === "none" ? "" : "transition-opacity ease-out", entered ? "opacity-100" : "opacity-0", overlayClassName), style: transitionStyle, onMouseDown: (event) => {
|
|
296
|
+
if (!resolvedCloseOnOutsideClick)
|
|
297
|
+
return;
|
|
298
|
+
if (event.target === overlayRef.current)
|
|
299
|
+
setOpen(false);
|
|
300
|
+
} })) : null, _jsx("div", { className: overlayContainerClass, children: _jsx("div", { className: "pointer-events-auto", children: renderPanelContent(overlayPanelStyle, overlayStateClass) }) })] }), document.body);
|
|
301
|
+
});
|
|
302
|
+
SgExpandablePanel.displayName = "SgExpandablePanel";
|