@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.
Files changed (155) hide show
  1. package/dist/buttons/SgFloatActionButton.d.ts.map +1 -1
  2. package/dist/buttons/SgFloatActionButton.js +168 -38
  3. package/dist/commons/SgAvatar.d.ts +66 -0
  4. package/dist/commons/SgAvatar.d.ts.map +1 -0
  5. package/dist/commons/SgAvatar.js +136 -0
  6. package/dist/commons/SgSkeleton.d.ts +16 -0
  7. package/dist/commons/SgSkeleton.d.ts.map +1 -0
  8. package/dist/commons/SgSkeleton.js +58 -0
  9. package/dist/commons/SgToaster.d.ts +9 -0
  10. package/dist/commons/SgToaster.d.ts.map +1 -1
  11. package/dist/commons/SgToaster.js +86 -17
  12. package/dist/digits/discard-digit/SgDiscardDigit.d.ts +39 -0
  13. package/dist/digits/discard-digit/SgDiscardDigit.d.ts.map +1 -0
  14. package/dist/digits/discard-digit/SgDiscardDigit.js +303 -0
  15. package/dist/digits/discard-digit/index.d.ts +3 -0
  16. package/dist/digits/discard-digit/index.d.ts.map +1 -0
  17. package/dist/digits/discard-digit/index.js +1 -0
  18. package/dist/digits/fade-digit/SgFadeDigit.d.ts +27 -0
  19. package/dist/digits/fade-digit/SgFadeDigit.d.ts.map +1 -0
  20. package/dist/digits/fade-digit/SgFadeDigit.js +85 -0
  21. package/dist/digits/fade-digit/index.d.ts +3 -0
  22. package/dist/digits/fade-digit/index.d.ts.map +1 -0
  23. package/dist/digits/fade-digit/index.js +1 -0
  24. package/dist/digits/flip-digit/SgFlipDigit.d.ts +27 -0
  25. package/dist/digits/flip-digit/SgFlipDigit.d.ts.map +1 -0
  26. package/dist/digits/flip-digit/SgFlipDigit.js +70 -0
  27. package/dist/digits/flip-digit/index.d.ts.map +1 -0
  28. package/dist/digits/matrix-digit/SgMatrixDigit.d.ts +32 -0
  29. package/dist/digits/matrix-digit/SgMatrixDigit.d.ts.map +1 -0
  30. package/dist/digits/matrix-digit/SgMatrixDigit.js +86 -0
  31. package/dist/digits/matrix-digit/index.d.ts +3 -0
  32. package/dist/digits/matrix-digit/index.d.ts.map +1 -0
  33. package/dist/digits/matrix-digit/index.js +1 -0
  34. package/dist/digits/neon-digit/SgNeonDigit.d.ts +37 -0
  35. package/dist/digits/neon-digit/SgNeonDigit.d.ts.map +1 -0
  36. package/dist/digits/neon-digit/SgNeonDigit.js +59 -0
  37. package/dist/digits/neon-digit/index.d.ts +3 -0
  38. package/dist/digits/neon-digit/index.d.ts.map +1 -0
  39. package/dist/digits/neon-digit/index.js +1 -0
  40. package/dist/digits/roller3d-digit/SgRoller3DDigit.d.ts +37 -0
  41. package/dist/digits/roller3d-digit/SgRoller3DDigit.d.ts.map +1 -0
  42. package/dist/digits/roller3d-digit/SgRoller3DDigit.js +47 -0
  43. package/dist/digits/roller3d-digit/index.d.ts +3 -0
  44. package/dist/digits/roller3d-digit/index.d.ts.map +1 -0
  45. package/dist/digits/roller3d-digit/index.js +1 -0
  46. package/dist/environment/SgEnvironmentProvider.d.ts +1 -0
  47. package/dist/environment/SgEnvironmentProvider.d.ts.map +1 -1
  48. package/dist/environment/SgEnvironmentProvider.js +51 -12
  49. package/dist/gadgets/clock/SgClock.d.ts +3 -1
  50. package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
  51. package/dist/gadgets/clock/SgClock.js +111 -180
  52. package/dist/gadgets/clock/SgTimeProvider.d.ts +1 -0
  53. package/dist/gadgets/clock/SgTimeProvider.d.ts.map +1 -1
  54. package/dist/gadgets/clock/SgTimeProvider.js +11 -4
  55. package/dist/gadgets/gauge/SgLinearGauge.d.ts +59 -0
  56. package/dist/gadgets/gauge/SgLinearGauge.d.ts.map +1 -0
  57. package/dist/gadgets/gauge/SgLinearGauge.js +258 -0
  58. package/dist/gadgets/gauge/SgRadialGauge.d.ts +73 -0
  59. package/dist/gadgets/gauge/SgRadialGauge.d.ts.map +1 -0
  60. package/dist/gadgets/gauge/SgRadialGauge.js +311 -0
  61. package/dist/gadgets/gauge/index.d.ts +5 -0
  62. package/dist/gadgets/gauge/index.d.ts.map +1 -0
  63. package/dist/gadgets/gauge/index.js +2 -0
  64. package/dist/gadgets/qr-code/SgQRCode.d.ts +25 -0
  65. package/dist/gadgets/qr-code/SgQRCode.d.ts.map +1 -0
  66. package/dist/gadgets/qr-code/SgQRCode.js +75 -0
  67. package/dist/gadgets/qr-code/index.d.ts +3 -0
  68. package/dist/gadgets/qr-code/index.d.ts.map +1 -0
  69. package/dist/gadgets/qr-code/index.js +1 -0
  70. package/dist/gadgets/string-animator/SgStringAnimator.d.ts +91 -0
  71. package/dist/gadgets/string-animator/SgStringAnimator.d.ts.map +1 -0
  72. package/dist/gadgets/string-animator/SgStringAnimator.js +145 -0
  73. package/dist/gadgets/string-animator/index.d.ts +3 -0
  74. package/dist/gadgets/string-animator/index.d.ts.map +1 -0
  75. package/dist/gadgets/string-animator/index.js +1 -0
  76. package/dist/i18n/en-US.json +9 -1
  77. package/dist/i18n/es.json +55 -47
  78. package/dist/i18n/index.d.ts +32 -0
  79. package/dist/i18n/index.d.ts.map +1 -1
  80. package/dist/i18n/pt-BR.json +9 -1
  81. package/dist/i18n/pt-PT.json +9 -1
  82. package/dist/index.d.ts +53 -5
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +25 -1
  85. package/dist/inputs/SgAutocomplete.js +21 -5
  86. package/dist/inputs/SgCombobox.d.ts +26 -0
  87. package/dist/inputs/SgCombobox.d.ts.map +1 -0
  88. package/dist/inputs/SgCombobox.js +354 -0
  89. package/dist/inputs/SgInputOTP.d.ts.map +1 -1
  90. package/dist/inputs/SgInputOTP.js +9 -2
  91. package/dist/inputs/SgRadioGroup.d.ts +37 -0
  92. package/dist/inputs/SgRadioGroup.d.ts.map +1 -0
  93. package/dist/inputs/SgRadioGroup.js +139 -0
  94. package/dist/inputs/SgRating.d.ts +55 -0
  95. package/dist/inputs/SgRating.d.ts.map +1 -0
  96. package/dist/inputs/SgRating.js +135 -0
  97. package/dist/inputs/SgSlider.d.ts +20 -0
  98. package/dist/inputs/SgSlider.d.ts.map +1 -0
  99. package/dist/inputs/SgSlider.js +40 -0
  100. package/dist/inputs/SgStepperInput.d.ts +22 -0
  101. package/dist/inputs/SgStepperInput.d.ts.map +1 -0
  102. package/dist/inputs/SgStepperInput.js +51 -0
  103. package/dist/inputs/SgTextEditor.d.ts +1 -0
  104. package/dist/inputs/SgTextEditor.d.ts.map +1 -1
  105. package/dist/inputs/SgTextEditor.js +19 -3
  106. package/dist/inputs/SgToggleSwitch.d.ts +36 -0
  107. package/dist/inputs/SgToggleSwitch.d.ts.map +1 -0
  108. package/dist/inputs/SgToggleSwitch.js +174 -0
  109. package/dist/layout/SgAccordion.d.ts +39 -0
  110. package/dist/layout/SgAccordion.d.ts.map +1 -0
  111. package/dist/layout/SgAccordion.js +116 -0
  112. package/dist/layout/SgBreadcrumb.d.ts +33 -0
  113. package/dist/layout/SgBreadcrumb.d.ts.map +1 -0
  114. package/dist/layout/SgBreadcrumb.js +121 -0
  115. package/dist/layout/SgCarousel.d.ts +43 -0
  116. package/dist/layout/SgCarousel.d.ts.map +1 -0
  117. package/dist/layout/SgCarousel.js +166 -0
  118. package/dist/layout/SgDockLayout.d.ts +14 -0
  119. package/dist/layout/SgDockLayout.d.ts.map +1 -1
  120. package/dist/layout/SgDockLayout.js +145 -13
  121. package/dist/layout/SgDockScreen.d.ts +15 -0
  122. package/dist/layout/SgDockScreen.d.ts.map +1 -0
  123. package/dist/layout/SgDockScreen.js +13 -0
  124. package/dist/layout/SgDockZone.d.ts.map +1 -1
  125. package/dist/layout/SgDockZone.js +36 -2
  126. package/dist/layout/SgExpandablePanel.d.ts +50 -0
  127. package/dist/layout/SgExpandablePanel.d.ts.map +1 -0
  128. package/dist/layout/SgExpandablePanel.js +302 -0
  129. package/dist/layout/SgMainPanel.d.ts.map +1 -1
  130. package/dist/layout/SgMainPanel.js +36 -14
  131. package/dist/layout/SgMenu.d.ts +91 -0
  132. package/dist/layout/SgMenu.d.ts.map +1 -0
  133. package/dist/layout/SgMenu.js +939 -0
  134. package/dist/layout/SgPageControl.d.ts +49 -0
  135. package/dist/layout/SgPageControl.d.ts.map +1 -0
  136. package/dist/layout/SgPageControl.js +152 -0
  137. package/dist/layout/SgPanel.d.ts.map +1 -1
  138. package/dist/layout/SgPanel.js +10 -1
  139. package/dist/layout/SgScreen.d.ts +2 -0
  140. package/dist/layout/SgScreen.d.ts.map +1 -1
  141. package/dist/layout/SgScreen.js +4 -2
  142. package/dist/layout/SgToolBar.d.ts +9 -3
  143. package/dist/layout/SgToolBar.d.ts.map +1 -1
  144. package/dist/layout/SgToolBar.js +461 -55
  145. package/dist/menus/SgDockMenu.d.ts +62 -0
  146. package/dist/menus/SgDockMenu.d.ts.map +1 -0
  147. package/dist/menus/SgDockMenu.js +480 -0
  148. package/dist/others/SgPlayground.js +73 -73
  149. package/package.json +72 -57
  150. package/dist/gadgets/flip-digit/SgFlipDigit.d.ts +0 -23
  151. package/dist/gadgets/flip-digit/SgFlipDigit.d.ts.map +0 -1
  152. package/dist/gadgets/flip-digit/SgFlipDigit.js +0 -118
  153. package/dist/gadgets/flip-digit/index.d.ts.map +0 -1
  154. /package/dist/{gadgets → digits}/flip-digit/index.d.ts +0 -0
  155. /package/dist/{gadgets → digits}/flip-digit/index.js +0 -0
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { useSgDockLayout } from "./SgDockLayout";
6
+ import { useHasSgEnvironmentProvider, useSgPersistence } from "../environment/SgEnvironmentProvider";
6
7
  function cn(...parts) {
7
8
  return parts.filter(Boolean).join(" ");
8
9
  }
@@ -51,6 +52,50 @@ const BTN_COLORS = {
51
52
  function clamp(n, min, max) {
52
53
  return Math.max(min, Math.min(max, n));
53
54
  }
55
+ function parseStoredDragPosition(raw) {
56
+ const value = typeof raw === "string" ? (() => {
57
+ try {
58
+ return JSON.parse(raw);
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ })() : raw;
64
+ if (!value ||
65
+ typeof value !== "object" ||
66
+ typeof value.x !== "number" ||
67
+ typeof value.y !== "number" ||
68
+ !Number.isFinite(value.x) ||
69
+ !Number.isFinite(value.y)) {
70
+ return null;
71
+ }
72
+ return {
73
+ x: value.x,
74
+ y: value.y
75
+ };
76
+ }
77
+ function resolveOrientationDirection(orientationDirection) {
78
+ switch (orientationDirection) {
79
+ case "horizontal-right":
80
+ return { orientation: "horizontal", direction: "left" };
81
+ case "horizontal-left":
82
+ return { orientation: "horizontal", direction: "right" };
83
+ case "vertical-up":
84
+ return { orientation: "vertical", direction: "up" };
85
+ case "vertical-down":
86
+ default:
87
+ return { orientation: "vertical", direction: "down" };
88
+ }
89
+ }
90
+ function resolveDockOrientationDirection(orientationDirection, inDock, zone) {
91
+ if (!inDock || zone === "free")
92
+ return orientationDirection;
93
+ if (zone === "top" || zone === "bottom") {
94
+ return orientationDirection.startsWith("horizontal") ? orientationDirection : "horizontal-left";
95
+ }
96
+ return orientationDirection.startsWith("vertical") ? orientationDirection : "vertical-down";
97
+ }
98
+ const SgToolbarOrientationContext = React.createContext("vertical");
54
99
  function useControlledState(args) {
55
100
  const { value, defaultValue, onChange } = args;
56
101
  const [internal, setInternal] = React.useState(defaultValue);
@@ -64,11 +109,21 @@ function useControlledState(args) {
64
109
  return [current, set];
65
110
  }
66
111
  export function SgToolBar(props) {
67
- const { id, title, orientation = "vertical", size, className, style, dockZone, draggable = true, freeDrag = true, defaultPosition, collapsible = true, collapsed, defaultCollapsed = false, collapseDirection, onCollapsedChange, children } = props;
112
+ const hasEnvironmentProvider = useHasSgEnvironmentProvider();
113
+ const { load: loadPersistedState, save: savePersistedState, clear: clearPersistedState } = useSgPersistence();
114
+ const { id, title, orientationDirection, buttonsPerDirection, bgColorTitle, bgColor, size, className, style, dockZone, draggable = false, freeDrag = false, defaultPosition, collapsible = true, collapsed, defaultCollapsed = false, onCollapsedChange, children } = props;
68
115
  const dock = useSgDockLayout();
69
116
  const inDock = !!dock;
70
117
  const assignedZone = inDock ? dock.getToolbarZone(id) : null;
71
118
  const effectiveZone = assignedZone ?? dockZone ?? "free";
119
+ const toolbarOrder = inDock ? dock.getToolbarOrder(id) : undefined;
120
+ const toolbarRenderOrder = toolbarOrder !== undefined ? toolbarOrder * 2 : undefined;
121
+ const [dragHoverZone, setDragHoverZone] = React.useState(null);
122
+ const dragHoverZoneRef = React.useRef(null);
123
+ const dragPlacementRef = React.useRef(null);
124
+ const zoneForOrientation = inDock && !freeDrag && dragHoverZone ? dragHoverZone : effectiveZone;
125
+ const resolvedOrientationDirection = resolveDockOrientationDirection(orientationDirection ?? "vertical-down", inDock, zoneForOrientation);
126
+ const { orientation, direction } = resolveOrientationDirection(resolvedOrientationDirection);
72
127
  const portalTarget = inDock ? dock.getZoneElement(effectiveZone) : null;
73
128
  const [isCollapsed, setIsCollapsed] = useControlledState({
74
129
  value: collapsed ?? (inDock ? dock.getToolbarCollapsed(id) : undefined),
@@ -79,10 +134,106 @@ export function SgToolBar(props) {
79
134
  dock.setToolbarCollapsed(id, next);
80
135
  }
81
136
  });
82
- const [dragPos, setDragPos] = React.useState(defaultPosition ?? null);
137
+ const [dragPos, setDragPos] = React.useState(null);
138
+ const dragPosRef = React.useRef(null);
139
+ const [dragActive, setDragActive] = React.useState(false);
83
140
  const dragStart = React.useRef(null);
84
141
  const dragMoved = React.useRef(false);
85
142
  const containerRef = React.useRef(null);
143
+ const storageKey = React.useMemo(() => `sg-toolbar-pos:${id}`, [id]);
144
+ const setDragHoverZoneSafe = React.useCallback((next) => {
145
+ if (dragHoverZoneRef.current === next)
146
+ return;
147
+ dragHoverZoneRef.current = next;
148
+ setDragHoverZone(next);
149
+ }, []);
150
+ const loadStoredPosition = React.useCallback(async () => {
151
+ if (!storageKey)
152
+ return null;
153
+ if (hasEnvironmentProvider) {
154
+ try {
155
+ const loaded = await loadPersistedState(storageKey);
156
+ if (loaded === null || loaded === undefined)
157
+ return null;
158
+ const parsed = parseStoredDragPosition(loaded);
159
+ if (!parsed) {
160
+ await clearPersistedState(storageKey);
161
+ return null;
162
+ }
163
+ return parsed;
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ try {
170
+ const raw = localStorage.getItem(storageKey);
171
+ if (!raw)
172
+ return null;
173
+ const parsed = parseStoredDragPosition(raw);
174
+ if (!parsed) {
175
+ localStorage.removeItem(storageKey);
176
+ return null;
177
+ }
178
+ return parsed;
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ }, [clearPersistedState, hasEnvironmentProvider, loadPersistedState, storageKey]);
184
+ const saveStoredPosition = React.useCallback(async (nextPos) => {
185
+ if (!storageKey)
186
+ return;
187
+ if (hasEnvironmentProvider) {
188
+ try {
189
+ await savePersistedState(storageKey, nextPos);
190
+ }
191
+ catch {
192
+ // ignore
193
+ }
194
+ return;
195
+ }
196
+ try {
197
+ localStorage.setItem(storageKey, JSON.stringify(nextPos));
198
+ }
199
+ catch {
200
+ // ignore
201
+ }
202
+ }, [hasEnvironmentProvider, savePersistedState, storageKey]);
203
+ const resolveFreeDragMode = React.useCallback(() => {
204
+ const container = containerRef.current;
205
+ if (!container)
206
+ return "fixed";
207
+ const offsetParent = container.offsetParent;
208
+ if (offsetParent instanceof HTMLElement &&
209
+ offsetParent !== document.body &&
210
+ offsetParent !== document.documentElement) {
211
+ return "absolute";
212
+ }
213
+ return "fixed";
214
+ }, []);
215
+ const getDragBounds = React.useCallback((mode) => {
216
+ if (!containerRef.current)
217
+ return null;
218
+ const rect = containerRef.current.getBoundingClientRect();
219
+ if (mode === "absolute") {
220
+ const offsetParent = containerRef.current.offsetParent;
221
+ if (offsetParent instanceof HTMLElement) {
222
+ return {
223
+ minX: 0,
224
+ minY: 0,
225
+ maxX: Math.max(0, offsetParent.clientWidth - rect.width),
226
+ maxY: Math.max(0, offsetParent.clientHeight - rect.height)
227
+ };
228
+ }
229
+ }
230
+ return {
231
+ minX: 0,
232
+ minY: 0,
233
+ maxX: Math.max(0, window.innerWidth - rect.width),
234
+ maxY: Math.max(0, window.innerHeight - rect.height)
235
+ };
236
+ }, []);
86
237
  React.useEffect(() => {
87
238
  if (!inDock)
88
239
  return;
@@ -92,6 +243,57 @@ export function SgToolBar(props) {
92
243
  orientation
93
244
  });
94
245
  }, [dock, id, effectiveZone, defaultCollapsed, inDock, orientation]);
246
+ React.useEffect(() => {
247
+ dragPosRef.current = dragPos;
248
+ }, [dragPos]);
249
+ React.useEffect(() => {
250
+ if (!freeDrag)
251
+ return;
252
+ let alive = true;
253
+ (async () => {
254
+ const parsed = await loadStoredPosition();
255
+ if (!alive || !parsed)
256
+ return;
257
+ const mode = resolveFreeDragMode();
258
+ const bounds = getDragBounds(mode);
259
+ if (!bounds)
260
+ return;
261
+ const next = {
262
+ x: clamp(parsed.x, bounds.minX, bounds.maxX),
263
+ y: clamp(parsed.y, bounds.minY, bounds.maxY),
264
+ mode
265
+ };
266
+ dragPosRef.current = next;
267
+ setDragPos(next);
268
+ })();
269
+ return () => {
270
+ alive = false;
271
+ };
272
+ }, [freeDrag, getDragBounds, loadStoredPosition, resolveFreeDragMode]);
273
+ React.useEffect(() => {
274
+ const defaultX = defaultPosition?.x;
275
+ const defaultY = defaultPosition?.y;
276
+ if (defaultX === undefined || defaultY === undefined || !freeDrag)
277
+ return;
278
+ const nextMode = resolveFreeDragMode();
279
+ setDragPos((prev) => {
280
+ if (prev)
281
+ return prev;
282
+ return { x: defaultX, y: defaultY, mode: nextMode };
283
+ });
284
+ }, [defaultPosition?.x, defaultPosition?.y, freeDrag, resolveFreeDragMode]);
285
+ React.useEffect(() => {
286
+ if (!dragActive)
287
+ return;
288
+ const previousBodyCursor = document.body.style.cursor;
289
+ const previousHtmlCursor = document.documentElement.style.cursor;
290
+ document.body.style.cursor = "grabbing";
291
+ document.documentElement.style.cursor = "grabbing";
292
+ return () => {
293
+ document.body.style.cursor = previousBodyCursor;
294
+ document.documentElement.style.cursor = previousHtmlCursor;
295
+ };
296
+ }, [dragActive]);
95
297
  const handlePointerDown = React.useCallback((event) => {
96
298
  if (event.button !== 0)
97
299
  return;
@@ -103,12 +305,66 @@ export function SgToolBar(props) {
103
305
  return;
104
306
  event.preventDefault();
105
307
  const rect = containerRef.current.getBoundingClientRect();
308
+ const dockInstance = dock;
309
+ const resolveDockPlacement = (clientX, clientY, toolbarLeft, toolbarTop, toolbarWidth, toolbarHeight) => {
310
+ if (!dockInstance)
311
+ return { zone: null, placement: null };
312
+ const zone = dockInstance.getZoneAtPoint(clientX, clientY);
313
+ if (!zone)
314
+ return { zone: null, placement: null };
315
+ const zoneEl = dockInstance.getZoneElement(zone);
316
+ const zoneRect = zoneEl?.getBoundingClientRect();
317
+ const isHorizontalZone = zone === "top" || zone === "bottom";
318
+ const isVerticalZone = zone === "left" || zone === "right";
319
+ const rawX = isHorizontalZone
320
+ ? toolbarLeft
321
+ : isVerticalZone
322
+ ? toolbarLeft + toolbarWidth / 2
323
+ : clientX;
324
+ const rawY = isVerticalZone
325
+ ? toolbarTop
326
+ : isHorizontalZone
327
+ ? toolbarTop + toolbarHeight / 2
328
+ : clientY;
329
+ const sampleX = zoneRect ? clamp(rawX, zoneRect.left + 1, zoneRect.right - 1) : rawX;
330
+ const sampleY = zoneRect ? clamp(rawY, zoneRect.top + 1, zoneRect.bottom - 1) : rawY;
331
+ return {
332
+ zone,
333
+ placement: dockInstance.getDropPlacementAtPoint(sampleX, sampleY, id)
334
+ };
335
+ };
336
+ if (inDock && !freeDrag && dockInstance) {
337
+ dockInstance.setDraggingToolbarId(id);
338
+ dockInstance.setDropPreviewActive(true);
339
+ const initial = resolveDockPlacement(event.clientX, event.clientY, rect.left, rect.top, rect.width, rect.height);
340
+ dragPlacementRef.current = initial.placement;
341
+ dockInstance.setDropIndicator(initial.placement);
342
+ const initialZone = initial.zone ?? effectiveZone;
343
+ setDragHoverZoneSafe(initialZone);
344
+ }
345
+ const dragMode = freeDrag
346
+ ? (inDock ? "fixed" : resolveFreeDragMode())
347
+ : "fixed";
348
+ let initialLeft = rect.left;
349
+ let initialTop = rect.top;
350
+ if (dragMode === "absolute") {
351
+ const offsetParent = containerRef.current.offsetParent;
352
+ if (offsetParent instanceof HTMLElement) {
353
+ const parentRect = offsetParent.getBoundingClientRect();
354
+ initialLeft = rect.left - parentRect.left;
355
+ initialTop = rect.top - parentRect.top;
356
+ }
357
+ }
106
358
  dragStart.current = {
107
359
  x: event.clientX,
108
360
  y: event.clientY,
109
- left: rect.left,
110
- top: rect.top
361
+ left: initialLeft,
362
+ top: initialTop,
363
+ mode: dragMode,
364
+ width: rect.width,
365
+ height: rect.height
111
366
  };
367
+ setDragActive(true);
112
368
  dragMoved.current = false;
113
369
  const handleMove = (moveEvent) => {
114
370
  if (!dragStart.current)
@@ -117,83 +373,233 @@ export function SgToolBar(props) {
117
373
  const dy = moveEvent.clientY - dragStart.current.y;
118
374
  if (Math.abs(dx) > 3 || Math.abs(dy) > 3)
119
375
  dragMoved.current = true;
120
- const next = { x: dragStart.current.left + dx, y: dragStart.current.top + dy };
376
+ const bounds = getDragBounds(dragStart.current.mode);
377
+ const next = {
378
+ x: bounds ? clamp(dragStart.current.left + dx, bounds.minX, bounds.maxX) : dragStart.current.left + dx,
379
+ y: bounds ? clamp(dragStart.current.top + dy, bounds.minY, bounds.maxY) : dragStart.current.top + dy,
380
+ mode: dragStart.current.mode
381
+ };
382
+ dragPosRef.current = next;
121
383
  setDragPos(next);
384
+ if (inDock && !freeDrag && dockInstance) {
385
+ const placementSourceLeft = dragStart.current.mode === "fixed" ? next.x : moveEvent.clientX;
386
+ const placementSourceTop = dragStart.current.mode === "fixed" ? next.y : moveEvent.clientY;
387
+ const resolved = resolveDockPlacement(moveEvent.clientX, moveEvent.clientY, placementSourceLeft, placementSourceTop, dragStart.current.width, dragStart.current.height);
388
+ dragPlacementRef.current = resolved.placement;
389
+ dockInstance.setDropIndicator(resolved.placement);
390
+ setDragHoverZoneSafe(resolved.zone);
391
+ }
122
392
  };
123
393
  const handleUp = (upEvent) => {
124
394
  window.removeEventListener("pointermove", handleMove);
125
395
  window.removeEventListener("pointerup", handleUp);
126
396
  window.removeEventListener("pointercancel", handleUp);
397
+ const activeDrag = dragStart.current;
398
+ const currentDragPos = dragPosRef.current;
399
+ const placementSourceLeft = inDock && !freeDrag && activeDrag && currentDragPos?.mode === "fixed"
400
+ ? currentDragPos.x
401
+ : upEvent.clientX;
402
+ const placementSourceTop = inDock && !freeDrag && activeDrag && currentDragPos?.mode === "fixed"
403
+ ? currentDragPos.y
404
+ : upEvent.clientY;
405
+ const resolvedFinal = inDock && !freeDrag && dockInstance
406
+ ? resolveDockPlacement(upEvent.clientX, upEvent.clientY, placementSourceLeft, placementSourceTop, activeDrag?.width ?? rect.width, activeDrag?.height ?? rect.height)
407
+ : { zone: null, placement: null };
408
+ const finalPlacement = inDock && !freeDrag && dockInstance
409
+ ? resolvedFinal.placement ?? dragPlacementRef.current
410
+ : null;
411
+ const finalZone = finalPlacement?.zone ??
412
+ (inDock && !freeDrag && dockInstance ? resolvedFinal.zone : null);
413
+ dragPlacementRef.current = null;
414
+ setDragActive(false);
415
+ setDragHoverZoneSafe(null);
416
+ if (inDock && !freeDrag && dockInstance) {
417
+ dockInstance.setDropIndicator(null);
418
+ dockInstance.setDraggingToolbarId(null);
419
+ dockInstance.setDropPreviewActive(false);
420
+ }
127
421
  if (!dragStart.current)
128
422
  return;
423
+ const start = dragStart.current;
129
424
  dragStart.current = null;
130
425
  if (!dragMoved.current)
131
426
  return;
132
- if (inDock) {
133
- const zone = dock.getZoneAtPoint(upEvent.clientX, upEvent.clientY);
134
- if (zone)
135
- dock.moveToolbar(id, zone);
136
- setDragPos(null);
427
+ if (freeDrag) {
428
+ const current = dragPosRef.current ?? {
429
+ x: start.left,
430
+ y: start.top,
431
+ mode: start.mode
432
+ };
433
+ const bounds = getDragBounds(current.mode);
434
+ const clamped = bounds
435
+ ? {
436
+ x: clamp(current.x, bounds.minX, bounds.maxX),
437
+ y: clamp(current.y, bounds.minY, bounds.maxY),
438
+ mode: current.mode
439
+ }
440
+ : current;
441
+ dragPosRef.current = clamped;
442
+ setDragPos(clamped);
443
+ void saveStoredPosition({ x: clamped.x, y: clamped.y });
137
444
  }
138
- else if (freeDrag) {
139
- const wh = containerRef.current?.getBoundingClientRect();
140
- if (wh) {
141
- const maxX = Math.max(0, window.innerWidth - wh.width);
142
- const maxY = Math.max(0, window.innerHeight - wh.height);
143
- setDragPos({
144
- x: clamp(dragPos?.x ?? wh.left, 0, maxX),
145
- y: clamp(dragPos?.y ?? wh.top, 0, maxY)
146
- });
445
+ else if (inDock && dockInstance) {
446
+ if (finalPlacement) {
447
+ dockInstance.placeToolbar(id, finalPlacement.zone, finalPlacement.index);
147
448
  }
449
+ else if (finalZone) {
450
+ dockInstance.moveToolbar(id, finalZone);
451
+ }
452
+ dragPosRef.current = null;
453
+ setDragPos(null);
148
454
  }
149
455
  };
150
456
  window.addEventListener("pointermove", handleMove);
151
457
  window.addEventListener("pointerup", handleUp);
152
458
  window.addEventListener("pointercancel", handleUp);
153
- }, [draggable, freeDrag, inDock, dock, id, dragPos]);
154
- const direction = collapseDirection ??
155
- (orientation === "horizontal"
156
- ? "left"
157
- : "top");
158
- const toolbar = (_jsxs("div", { ref: containerRef, className: cn("select-none rounded-xl border border-border bg-background shadow-sm", "flex", orientation === "horizontal" ? "flex-row items-center" : "flex-col items-center", className), style: {
159
- width: size?.w,
160
- height: size?.h,
161
- position: dragPos ? "fixed" : undefined,
162
- left: dragPos?.x,
163
- top: dragPos?.y,
164
- zIndex: dragPos ? 1000 : undefined,
165
- ...style
166
- }, onPointerDown: handlePointerDown, children: [(title || collapsible) && (_jsxs("div", { className: cn("flex items-center gap-2 px-2 py-1 border-b border-border w-full", orientation === "horizontal" ? "border-b-0 border-r" : ""), children: [title ? (_jsx("span", { className: cn("text-xs font-semibold text-foreground truncate", isCollapsed ? "hidden" : ""), children: title })) : null, collapsible ? (_jsx("button", { type: "button", className: "ml-auto inline-flex size-6 items-center justify-center rounded-md hover:bg-muted", onClick: () => setIsCollapsed(!isCollapsed), onPointerDown: (e) => e.stopPropagation(), "aria-label": "Toggle toolbar", children: _jsx(CollapseIcon, { direction: direction, collapsed: isCollapsed }) })) : null] })), _jsx("div", { className: cn("flex gap-2 p-2", orientation === "horizontal" ? "flex-row" : "flex-col", isCollapsed ? "opacity-90" : ""), children: React.Children.map(children, (child) => {
167
- if (!isCollapsed)
168
- return child;
169
- if (!React.isValidElement(child))
170
- return child;
171
- return React.cloneElement(child, { hideLabel: true });
172
- }) })] }));
459
+ }, [
460
+ draggable,
461
+ freeDrag,
462
+ inDock,
463
+ dock,
464
+ id,
465
+ effectiveZone,
466
+ resolveFreeDragMode,
467
+ getDragBounds,
468
+ saveStoredPosition,
469
+ setDragHoverZoneSafe
470
+ ]);
471
+ const showContent = !isCollapsed;
472
+ const openUp = orientation === "vertical" && direction === "up";
473
+ const openLeft = orientation === "horizontal" && direction === "left";
474
+ const detachFromDockFlow = dragActive && inDock && !freeDrag;
475
+ const normalizedButtonsPerDirection = Number.isFinite(buttonsPerDirection) && typeof buttonsPerDirection === "number" && buttonsPerDirection > 0
476
+ ? Math.floor(buttonsPerDirection)
477
+ : undefined;
478
+ const showLeadingCollapseButton = orientation === "horizontal" && openLeft && showContent;
479
+ const collapseIconDirection = resolvedOrientationDirection === "horizontal-right" ? "right" : direction;
480
+ const content = showContent ? (_jsx("div", { className: cn("gap-2 p-2", normalizedButtonsPerDirection
481
+ ? "grid"
482
+ : orientation === "horizontal"
483
+ ? "flex flex-row"
484
+ : "flex flex-col"), style: normalizedButtonsPerDirection
485
+ ? orientation === "horizontal"
486
+ ? { gridTemplateColumns: `repeat(${normalizedButtonsPerDirection}, max-content)` }
487
+ : {
488
+ gridTemplateRows: `repeat(${normalizedButtonsPerDirection}, max-content)`,
489
+ gridAutoFlow: "column"
490
+ }
491
+ : undefined, children: children })) : null;
492
+ const toolbar = (_jsx(SgToolbarOrientationContext.Provider, { value: orientation, children: _jsxs("div", { ref: containerRef, "data-sg-toolbar-root": "true", "data-sg-toolbar-id": id, className: cn("select-none rounded-xl border border-border bg-background shadow-sm", orientation === "horizontal" ? "inline-flex flex-row items-center" : "inline-flex flex-col items-center", className), style: {
493
+ width: size?.w,
494
+ height: size?.h,
495
+ backgroundColor: bgColor,
496
+ cursor: draggable ? (dragActive ? "grabbing" : "grab") : undefined,
497
+ position: dragPos?.mode,
498
+ left: dragPos?.x,
499
+ top: dragPos?.y,
500
+ order: detachFromDockFlow ? undefined : toolbarRenderOrder,
501
+ zIndex: dragPos ? 1000 : undefined,
502
+ ...style
503
+ }, onPointerDown: handlePointerDown, children: [openUp || openLeft ? content : null, (title || collapsible) && (_jsxs("div", { className: cn("flex items-center gap-2 px-2 py-1", orientation === "horizontal" ? "w-auto" : "w-full", orientation === "horizontal"
504
+ ? showContent
505
+ ? (openLeft ? "border-l border-border" : "border-r border-border")
506
+ : ""
507
+ : showContent
508
+ ? (openUp ? "border-t border-border" : "border-b border-border")
509
+ : ""), style: { backgroundColor: bgColorTitle }, children: [collapsible && showLeadingCollapseButton ? (_jsx("button", { type: "button", className: "inline-flex size-6 items-center justify-center rounded-md hover:bg-muted", onClick: () => setIsCollapsed(!isCollapsed), onPointerDown: (e) => e.stopPropagation(), "aria-label": "Toggle toolbar", children: _jsx(CollapseIcon, { direction: collapseIconDirection, collapsed: isCollapsed }) })) : null, title ? (_jsx("span", { className: "text-xs font-semibold text-foreground truncate", children: title })) : null, collapsible && !showLeadingCollapseButton ? (_jsx("button", { type: "button", className: "ml-auto inline-flex size-6 items-center justify-center rounded-md hover:bg-muted", onClick: () => setIsCollapsed(!isCollapsed), onPointerDown: (e) => e.stopPropagation(), "aria-label": "Toggle toolbar", children: _jsx(CollapseIcon, { direction: collapseIconDirection, collapsed: isCollapsed }) })) : null] })), openUp || openLeft ? null : content] }) }));
510
+ const needsCenterWrapper = inDock && (effectiveZone === "right" || effectiveZone === "left");
511
+ const toolbarForRender = needsCenterWrapper && !detachFromDockFlow ? (_jsx("div", { style: { width: "100%", display: "flex", justifyContent: "center", order: toolbarRenderOrder }, children: toolbar })) : toolbar;
173
512
  if (portalTarget) {
174
- return createPortal(toolbar, portalTarget);
513
+ return createPortal(toolbarForRender, portalTarget);
175
514
  }
176
- return toolbar;
515
+ return toolbarForRender;
177
516
  }
178
517
  export function SgToolbarIconButton(props) {
179
- const { icon, hint, severity = "plain", disabled, onClick, hideLabel } = props;
518
+ const { icon, label, showLabel = true, hint, loading = false, severity = "plain", disabled, onClick, hideLabel } = props;
519
+ const toolbarOrientation = React.useContext(SgToolbarOrientationContext);
520
+ const isHorizontalToolbar = toolbarOrientation === "horizontal";
180
521
  const c = BTN_COLORS[severity];
181
522
  const text = typeof icon === "string" ? icon : null;
182
- return (_jsxs("button", { type: "button", disabled: disabled, onClick: onClick, title: hint, className: cn("group", "relative inline-flex items-center justify-center rounded-lg", "transition-[transform,filter] duration-150", "hover:brightness-95 active:brightness-90", "focus-visible:outline-none focus-visible:ring-4", "disabled:opacity-50 disabled:cursor-not-allowed"), style: {
183
- width: 40,
184
- height: 40,
185
- backgroundColor: c.bg,
186
- color: c.fg,
187
- ["--tw-ring-color"]: c.ring
188
- }, children: [icon && typeof icon !== "string" ? (_jsx("span", { className: "inline-flex", children: icon })) : (_jsx("span", { className: "text-[10px] font-semibold", children: text?.slice(0, 2) })), !hideLabel && hint ? (_jsx("span", { className: "pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-foreground/90 px-2 py-1 text-[11px] text-background opacity-0 transition-opacity duration-150 group-hover:opacity-100", children: hint })) : null] }));
523
+ const hasVisibleLabel = Boolean(label) && showLabel && !hideLabel;
524
+ const [isPending, setIsPending] = React.useState(false);
525
+ const isLoading = loading || isPending;
526
+ const showHintTooltip = Boolean(hint) && !hideLabel;
527
+ const [hintPosition, setHintPosition] = React.useState({ x: 0, y: 0 });
528
+ const [isHintHovered, setIsHintHovered] = React.useState(false);
529
+ const buttonRef = React.useRef(null);
530
+ const handleClick = React.useCallback(() => {
531
+ if (!onClick || disabled || isLoading)
532
+ return;
533
+ const result = onClick();
534
+ if (!result || typeof result.then !== "function")
535
+ return;
536
+ setIsPending(true);
537
+ void result.finally(() => {
538
+ setIsPending(false);
539
+ });
540
+ }, [disabled, isLoading, onClick]);
541
+ const updateHintPosition = React.useCallback(() => {
542
+ if (!buttonRef.current)
543
+ return;
544
+ const buttonRect = buttonRef.current.getBoundingClientRect();
545
+ const toolbarRoot = buttonRef.current.closest("[data-sg-toolbar-root='true']");
546
+ const toolbarRect = toolbarRoot instanceof HTMLElement ? toolbarRoot.getBoundingClientRect() : null;
547
+ if (isHorizontalToolbar) {
548
+ const baseY = toolbarRect ? toolbarRect.top : buttonRect.top;
549
+ setHintPosition({
550
+ x: buttonRect.left + (buttonRect.width / 2),
551
+ y: baseY - 8
552
+ });
553
+ return;
554
+ }
555
+ const baseX = toolbarRect ? toolbarRect.right : buttonRect.right;
556
+ setHintPosition({
557
+ x: baseX + 8,
558
+ y: buttonRect.top + (buttonRect.height / 2)
559
+ });
560
+ }, [isHorizontalToolbar]);
561
+ React.useEffect(() => {
562
+ if (!showHintTooltip || !isHintHovered)
563
+ return;
564
+ updateHintPosition();
565
+ const handleReposition = () => updateHintPosition();
566
+ window.addEventListener("scroll", handleReposition, true);
567
+ window.addEventListener("resize", handleReposition);
568
+ return () => {
569
+ window.removeEventListener("scroll", handleReposition, true);
570
+ window.removeEventListener("resize", handleReposition);
571
+ };
572
+ }, [isHintHovered, showHintTooltip, updateHintPosition]);
573
+ const hintNode = showHintTooltip && isHintHovered && typeof document !== "undefined"
574
+ ? createPortal(_jsx("span", { className: "pointer-events-none fixed z-[1200] whitespace-nowrap rounded bg-foreground/90 px-2 py-1 text-[11px] text-background", style: {
575
+ left: hintPosition.x,
576
+ top: hintPosition.y,
577
+ transform: isHorizontalToolbar ? "translate(-50%, -100%)" : "translateY(-50%)"
578
+ }, children: hint }), document.body)
579
+ : null;
580
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: buttonRef, type: "button", disabled: disabled || isLoading, onClick: handleClick, "aria-busy": isLoading || undefined, onMouseEnter: showHintTooltip ? () => {
581
+ updateHintPosition();
582
+ setIsHintHovered(true);
583
+ } : undefined, onMouseLeave: showHintTooltip ? () => setIsHintHovered(false) : undefined, "aria-label": hint ?? label ?? text ?? undefined, className: cn("group", "relative inline-flex items-center justify-center rounded-lg", "transition-[transform,filter] duration-150", "hover:brightness-95 active:brightness-90", "focus-visible:outline-none focus-visible:ring-4", "disabled:opacity-50 disabled:cursor-not-allowed", hasVisibleLabel ? "gap-2 px-2 pr-3" : ""), style: {
584
+ width: hasVisibleLabel ? undefined : 40,
585
+ minWidth: 40,
586
+ height: 40,
587
+ backgroundColor: c.bg,
588
+ color: c.fg,
589
+ ["--tw-ring-color"]: c.ring
590
+ }, children: [isLoading ? (_jsx("span", { className: "inline-flex size-4 shrink-0 animate-spin rounded-full border-2 border-current border-r-transparent" })) : icon && typeof icon !== "string" ? (_jsx("span", { className: "inline-flex shrink-0", children: icon })) : (_jsx("span", { className: "shrink-0 text-[10px] font-semibold", children: text?.slice(0, 2) })), hasVisibleLabel ? (_jsx("span", { className: "text-xs font-medium leading-none", children: label })) : null] }), hintNode] }));
189
591
  }
190
592
  function CollapseIcon(props) {
191
593
  const { direction, collapsed } = props;
192
- const rot = direction === "left" ? (collapsed ? 180 : 0)
193
- : direction === "right" ? (collapsed ? 0 : 180)
194
- : direction === "top" ? (collapsed ? 180 : 0)
195
- : (collapsed ? 0 : 180);
196
- return (_jsx("svg", { viewBox: "0 0 24 24", className: "size-4", style: { transform: `rotate(${rot}deg)` }, "aria-hidden": "true", children: _jsx("path", { d: "M8 5l8 7-8 7", fill: "currentColor" }) }));
594
+ const arrowUp = "M6 15l6-6 6 6";
595
+ const arrowDown = "M6 9l6 6 6-6";
596
+ const arrowLeft = "M15 6l-6 6 6 6";
597
+ const arrowRight = "M9 6l6 6-6 6";
598
+ const path = direction === "down" ? (collapsed ? arrowDown : arrowUp)
599
+ : direction === "up" ? (collapsed ? arrowUp : arrowDown)
600
+ : direction === "left" ? (collapsed ? arrowLeft : arrowRight)
601
+ : (collapsed ? arrowRight : arrowLeft);
602
+ return (_jsx("svg", { viewBox: "0 0 24 24", className: "size-4", "aria-hidden": "true", children: _jsx("path", { d: path, fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
197
603
  }
198
604
  SgToolBar.displayName = "SgToolBar";
199
605
  SgToolbarIconButton.displayName = "SgToolbarIconButton";
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ export type SgDockMenuPosition = "left-top" | "left-center" | "left-bottom" | "center-top" | "center-bottom" | "right-top" | "right-center" | "right-bottom";
3
+ export type SgDockMenuOrientation = "horizontal" | "vertical";
4
+ export type SgDockMenuItem = {
5
+ /** Unique identifier */
6
+ id: string;
7
+ /** Icon to display */
8
+ icon: React.ReactNode;
9
+ /** Label/tooltip text */
10
+ label: string;
11
+ /** Click handler */
12
+ onClick?: () => void;
13
+ /** Disabled state */
14
+ disabled?: boolean;
15
+ /** Badge content */
16
+ badge?: string | number;
17
+ /** Custom class name */
18
+ className?: string;
19
+ };
20
+ export interface SgDockMenuProps {
21
+ /** Unique identifier for the dock */
22
+ id?: string;
23
+ /** Items to display in the dock */
24
+ items: SgDockMenuItem[];
25
+ /** Position of the dock */
26
+ position?: SgDockMenuPosition;
27
+ /** Enable drag and drop repositioning */
28
+ enableDragDrop?: boolean;
29
+ /** ID for persisting drag position */
30
+ dragId?: string;
31
+ /** Offset from the edge */
32
+ offset?: {
33
+ x?: number;
34
+ y?: number;
35
+ };
36
+ /** Custom class name for container */
37
+ className?: string;
38
+ /** Custom class name for items */
39
+ itemClassName?: string;
40
+ /** Z-index */
41
+ zIndex?: number;
42
+ /** Item size in pixels */
43
+ itemSize?: number;
44
+ /** Gap between items in pixels */
45
+ gap?: number;
46
+ /** Background color */
47
+ backgroundColor?: string;
48
+ /** Show labels on hover */
49
+ showLabels?: boolean;
50
+ /** Magnification effect on hover */
51
+ magnify?: boolean;
52
+ /** Magnification scale */
53
+ magnifyScale?: number;
54
+ /** Border radius */
55
+ borderRadius?: number;
56
+ /** Shadow elevation */
57
+ elevation?: "none" | "sm" | "md" | "lg";
58
+ /** Custom style */
59
+ style?: React.CSSProperties;
60
+ }
61
+ export declare function SgDockMenu(props: Readonly<SgDockMenuProps>): import("react/jsx-runtime").JSX.Element;
62
+ //# sourceMappingURL=SgDockMenu.d.ts.map