@seedgrid/fe-components 0.2.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/blocked-email-domains.json +41 -0
- package/dist/buttons/SgButton.d.ts +43 -0
- package/dist/buttons/SgButton.d.ts.map +1 -0
- package/dist/buttons/SgButton.js +123 -0
- package/dist/buttons/SgFloatActionButton.d.ts +60 -0
- package/dist/buttons/SgFloatActionButton.d.ts.map +1 -0
- package/dist/buttons/SgFloatActionButton.js +532 -0
- package/dist/buttons/SgSpeedDial.d.ts +40 -0
- package/dist/buttons/SgSpeedDial.d.ts.map +1 -0
- package/dist/buttons/SgSpeedDial.js +149 -0
- package/dist/buttons/SgSplitButton.d.ts +32 -0
- package/dist/buttons/SgSplitButton.d.ts.map +1 -0
- package/dist/buttons/SgSplitButton.js +81 -0
- package/dist/clock/SgClock.d.ts +28 -0
- package/dist/clock/SgClock.d.ts.map +1 -0
- package/dist/clock/SgClock.js +280 -0
- package/dist/clock/SgTimeProvider.d.ts +13 -0
- package/dist/clock/SgTimeProvider.d.ts.map +1 -0
- package/dist/clock/SgTimeProvider.js +44 -0
- package/dist/clock/themes/SgClockThemePicker.d.ts +14 -0
- package/dist/clock/themes/SgClockThemePicker.d.ts.map +1 -0
- package/dist/clock/themes/SgClockThemePicker.js +71 -0
- package/dist/clock/themes/SgClockThemePreview.d.ts +7 -0
- package/dist/clock/themes/SgClockThemePreview.d.ts.map +1 -0
- package/dist/clock/themes/SgClockThemePreview.js +11 -0
- package/dist/clock/themes/builtins.d.ts +3 -0
- package/dist/clock/themes/builtins.d.ts.map +1 -0
- package/dist/clock/themes/builtins.js +241 -0
- package/dist/clock/themes/index.d.ts +9 -0
- package/dist/clock/themes/index.d.ts.map +1 -0
- package/dist/clock/themes/index.js +7 -0
- package/dist/clock/themes/provider.d.ts +19 -0
- package/dist/clock/themes/provider.d.ts.map +1 -0
- package/dist/clock/themes/provider.js +54 -0
- package/dist/clock/themes/registry.d.ts +9 -0
- package/dist/clock/themes/registry.d.ts.map +1 -0
- package/dist/clock/themes/registry.js +25 -0
- package/dist/clock/themes/renderTheme.d.ts +7 -0
- package/dist/clock/themes/renderTheme.d.ts.map +1 -0
- package/dist/clock/themes/renderTheme.js +41 -0
- package/dist/clock/themes/types.d.ts +21 -0
- package/dist/clock/themes/types.d.ts.map +1 -0
- package/dist/clock/themes/types.js +1 -0
- package/dist/clock/themes/urlThemeCache.d.ts +2 -0
- package/dist/clock/themes/urlThemeCache.d.ts.map +1 -0
- package/dist/clock/themes/urlThemeCache.js +11 -0
- package/dist/clock/themes/useDarkFlag.d.ts +2 -0
- package/dist/clock/themes/useDarkFlag.d.ts.map +1 -0
- package/dist/clock/themes/useDarkFlag.js +14 -0
- package/dist/commons/SgBadge.d.ts +51 -0
- package/dist/commons/SgBadge.d.ts.map +1 -0
- package/dist/commons/SgBadge.js +141 -0
- package/dist/commons/SgBadgeOverlay.d.ts +13 -0
- package/dist/commons/SgBadgeOverlay.d.ts.map +1 -0
- package/dist/commons/SgBadgeOverlay.js +20 -0
- package/dist/commons/SgButton.d.ts +39 -0
- package/dist/commons/SgButton.d.ts.map +1 -0
- package/dist/commons/SgButton.js +116 -0
- package/dist/commons/SgPopup.d.ts +42 -0
- package/dist/commons/SgPopup.d.ts.map +1 -0
- package/dist/commons/SgPopup.js +218 -0
- package/dist/commons/SgToast.d.ts +44 -0
- package/dist/commons/SgToast.d.ts.map +1 -0
- package/dist/commons/SgToast.js +97 -0
- package/dist/commons/SgToaster.d.ts +11 -0
- package/dist/commons/SgToaster.d.ts.map +1 -0
- package/dist/commons/SgToaster.js +85 -0
- package/dist/commons/common-passwords.d.ts +2 -0
- package/dist/commons/common-passwords.d.ts.map +1 -0
- package/dist/commons/common-passwords.js +167 -0
- package/dist/environment/SgEnvironmentProvider.d.ts +31 -0
- package/dist/environment/SgEnvironmentProvider.d.ts.map +1 -0
- package/dist/environment/SgEnvironmentProvider.js +120 -0
- package/dist/environment/persistence.d.ts +44 -0
- package/dist/environment/persistence.d.ts.map +1 -0
- package/dist/environment/persistence.js +149 -0
- package/dist/gadgets/clock/SgClock.d.ts +18 -0
- package/dist/gadgets/clock/SgClock.d.ts.map +1 -0
- package/dist/gadgets/clock/SgClock.js +407 -0
- package/dist/gadgets/clock/SgTimeProvider.d.ts +13 -0
- package/dist/gadgets/clock/SgTimeProvider.d.ts.map +1 -0
- package/dist/gadgets/clock/SgTimeProvider.js +44 -0
- package/dist/gadgets/clock/themes/SgClockThemePicker.d.ts +14 -0
- package/dist/gadgets/clock/themes/SgClockThemePicker.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/SgClockThemePicker.js +71 -0
- package/dist/gadgets/clock/themes/SgClockThemePreview.d.ts +7 -0
- package/dist/gadgets/clock/themes/SgClockThemePreview.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/SgClockThemePreview.js +11 -0
- package/dist/gadgets/clock/themes/builtins.d.ts +3 -0
- package/dist/gadgets/clock/themes/builtins.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/builtins.js +241 -0
- package/dist/gadgets/clock/themes/index.d.ts +9 -0
- package/dist/gadgets/clock/themes/index.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/index.js +7 -0
- package/dist/gadgets/clock/themes/provider.d.ts +19 -0
- package/dist/gadgets/clock/themes/provider.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/provider.js +54 -0
- package/dist/gadgets/clock/themes/registry.d.ts +9 -0
- package/dist/gadgets/clock/themes/registry.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/registry.js +25 -0
- package/dist/gadgets/clock/themes/renderTheme.d.ts +7 -0
- package/dist/gadgets/clock/themes/renderTheme.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/renderTheme.js +41 -0
- package/dist/gadgets/clock/themes/types.d.ts +21 -0
- package/dist/gadgets/clock/themes/types.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/types.js +1 -0
- package/dist/gadgets/clock/themes/urlThemeCache.d.ts +2 -0
- package/dist/gadgets/clock/themes/urlThemeCache.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/urlThemeCache.js +11 -0
- package/dist/gadgets/clock/themes/useDarkFlag.d.ts +2 -0
- package/dist/gadgets/clock/themes/useDarkFlag.d.ts.map +1 -0
- package/dist/gadgets/clock/themes/useDarkFlag.js +14 -0
- package/dist/gadgets/flip-digit/SgFlipDigit.d.ts +23 -0
- package/dist/gadgets/flip-digit/SgFlipDigit.d.ts.map +1 -0
- package/dist/gadgets/flip-digit/SgFlipDigit.js +118 -0
- package/dist/gadgets/flip-digit/index.d.ts +3 -0
- package/dist/gadgets/flip-digit/index.d.ts.map +1 -0
- package/dist/gadgets/flip-digit/index.js +1 -0
- package/dist/i18n/en-US.json +76 -0
- package/dist/i18n/es.json +76 -0
- package/dist/i18n/index.d.ts +328 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +87 -0
- package/dist/i18n/pt-BR.json +76 -0
- package/dist/i18n/pt-PT.json +76 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/inputs/FloatingInput.d.ts +13 -0
- package/dist/inputs/FloatingInput.d.ts.map +1 -0
- package/dist/inputs/FloatingInput.js +53 -0
- package/dist/inputs/FloatingSelect.d.ts +15 -0
- package/dist/inputs/FloatingSelect.d.ts.map +1 -0
- package/dist/inputs/FloatingSelect.js +52 -0
- package/dist/inputs/FloatingTextArea.d.ts +11 -0
- package/dist/inputs/FloatingTextArea.d.ts.map +1 -0
- package/dist/inputs/FloatingTextArea.js +34 -0
- package/dist/inputs/InputBirthDate.d.ts +13 -0
- package/dist/inputs/InputBirthDate.d.ts.map +1 -0
- package/dist/inputs/InputBirthDate.js +46 -0
- package/dist/inputs/InputDate.d.ts +8 -0
- package/dist/inputs/InputDate.d.ts.map +1 -0
- package/dist/inputs/InputDate.js +23 -0
- package/dist/inputs/InputEmail.d.ts +14 -0
- package/dist/inputs/InputEmail.d.ts.map +1 -0
- package/dist/inputs/InputEmail.js +43 -0
- package/dist/inputs/InputPassword.d.ts +12 -0
- package/dist/inputs/InputPassword.d.ts.map +1 -0
- package/dist/inputs/InputPassword.js +42 -0
- package/dist/inputs/MaskedInputs.d.ts +27 -0
- package/dist/inputs/MaskedInputs.d.ts.map +1 -0
- package/dist/inputs/MaskedInputs.js +161 -0
- package/dist/inputs/SgAutocomplete.d.ts +42 -0
- package/dist/inputs/SgAutocomplete.d.ts.map +1 -0
- package/dist/inputs/SgAutocomplete.js +241 -0
- package/dist/inputs/SgCurrencyEdit.d.ts +56 -0
- package/dist/inputs/SgCurrencyEdit.d.ts.map +1 -0
- package/dist/inputs/SgCurrencyEdit.js +496 -0
- package/dist/inputs/SgInputBirthDate.d.ts +13 -0
- package/dist/inputs/SgInputBirthDate.d.ts.map +1 -0
- package/dist/inputs/SgInputBirthDate.js +48 -0
- package/dist/inputs/SgInputCEP.d.ts +33 -0
- package/dist/inputs/SgInputCEP.d.ts.map +1 -0
- package/dist/inputs/SgInputCEP.js +117 -0
- package/dist/inputs/SgInputCNPJ.d.ts +20 -0
- package/dist/inputs/SgInputCNPJ.d.ts.map +1 -0
- package/dist/inputs/SgInputCNPJ.js +133 -0
- package/dist/inputs/SgInputCPF.d.ts +15 -0
- package/dist/inputs/SgInputCPF.d.ts.map +1 -0
- package/dist/inputs/SgInputCPF.js +70 -0
- package/dist/inputs/SgInputCPFCNPJ.d.ts +15 -0
- package/dist/inputs/SgInputCPFCNPJ.d.ts.map +1 -0
- package/dist/inputs/SgInputCPFCNPJ.js +92 -0
- package/dist/inputs/SgInputDate.d.ts +8 -0
- package/dist/inputs/SgInputDate.d.ts.map +1 -0
- package/dist/inputs/SgInputDate.js +120 -0
- package/dist/inputs/SgInputEmail.d.ts +16 -0
- package/dist/inputs/SgInputEmail.d.ts.map +1 -0
- package/dist/inputs/SgInputEmail.js +74 -0
- package/dist/inputs/SgInputFone.d.ts +15 -0
- package/dist/inputs/SgInputFone.d.ts.map +1 -0
- package/dist/inputs/SgInputFone.js +60 -0
- package/dist/inputs/SgInputMasked.d.ts +27 -0
- package/dist/inputs/SgInputMasked.d.ts.map +1 -0
- package/dist/inputs/SgInputMasked.js +161 -0
- package/dist/inputs/SgInputNumber.d.ts +49 -0
- package/dist/inputs/SgInputNumber.d.ts.map +1 -0
- package/dist/inputs/SgInputNumber.js +438 -0
- package/dist/inputs/SgInputPassword.d.ts +26 -0
- package/dist/inputs/SgInputPassword.d.ts.map +1 -0
- package/dist/inputs/SgInputPassword.js +278 -0
- package/dist/inputs/SgInputPhone.d.ts +15 -0
- package/dist/inputs/SgInputPhone.d.ts.map +1 -0
- package/dist/inputs/SgInputPhone.js +66 -0
- package/dist/inputs/SgInputPostalCode.d.ts +37 -0
- package/dist/inputs/SgInputPostalCode.d.ts.map +1 -0
- package/dist/inputs/SgInputPostalCode.js +193 -0
- package/dist/inputs/SgInputSelect.d.ts +16 -0
- package/dist/inputs/SgInputSelect.d.ts.map +1 -0
- package/dist/inputs/SgInputSelect.js +104 -0
- package/dist/inputs/SgInputText.d.ts +49 -0
- package/dist/inputs/SgInputText.d.ts.map +1 -0
- package/dist/inputs/SgInputText.js +336 -0
- package/dist/inputs/SgInputTextArea.d.ts +41 -0
- package/dist/inputs/SgInputTextArea.d.ts.map +1 -0
- package/dist/inputs/SgInputTextArea.js +216 -0
- package/dist/inputs/SgTextEditor.d.ts +27 -0
- package/dist/inputs/SgTextEditor.d.ts.map +1 -0
- package/dist/inputs/SgTextEditor.js +201 -0
- package/dist/integration/module.d.ts +39 -0
- package/dist/integration/module.d.ts.map +1 -0
- package/dist/integration/module.js +1 -0
- package/dist/layout/GroupBox.d.ts +10 -0
- package/dist/layout/GroupBox.d.ts.map +1 -0
- package/dist/layout/GroupBox.js +14 -0
- package/dist/layout/SgCard.d.ts +35 -0
- package/dist/layout/SgCard.d.ts.map +1 -0
- package/dist/layout/SgCard.js +106 -0
- package/dist/layout/SgDockLayout.d.ts +37 -0
- package/dist/layout/SgDockLayout.d.ts.map +1 -0
- package/dist/layout/SgDockLayout.js +101 -0
- package/dist/layout/SgDockZone.d.ts +12 -0
- package/dist/layout/SgDockZone.d.ts.map +1 -0
- package/dist/layout/SgDockZone.js +20 -0
- package/dist/layout/SgGrid.d.ts +18 -0
- package/dist/layout/SgGrid.d.ts.map +1 -0
- package/dist/layout/SgGrid.js +101 -0
- package/dist/layout/SgGroupBox.d.ts +10 -0
- package/dist/layout/SgGroupBox.d.ts.map +1 -0
- package/dist/layout/SgGroupBox.js +14 -0
- package/dist/layout/SgMainPanel.d.ts +11 -0
- package/dist/layout/SgMainPanel.d.ts.map +1 -0
- package/dist/layout/SgMainPanel.js +70 -0
- package/dist/layout/SgPanel.d.ts +22 -0
- package/dist/layout/SgPanel.d.ts.map +1 -0
- package/dist/layout/SgPanel.js +33 -0
- package/dist/layout/SgScreen.d.ts +11 -0
- package/dist/layout/SgScreen.d.ts.map +1 -0
- package/dist/layout/SgScreen.js +18 -0
- package/dist/layout/SgStack.d.ts +15 -0
- package/dist/layout/SgStack.d.ts.map +1 -0
- package/dist/layout/SgStack.js +32 -0
- package/dist/layout/SgToolBar.d.ts +46 -0
- package/dist/layout/SgToolBar.d.ts.map +1 -0
- package/dist/layout/SgToolBar.js +199 -0
- package/dist/layout/SgTreeView.d.ts +80 -0
- package/dist/layout/SgTreeView.d.ts.map +1 -0
- package/dist/layout/SgTreeView.js +338 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +19 -0
- package/dist/masks.d.ts +14 -0
- package/dist/masks.d.ts.map +1 -0
- package/dist/masks.js +91 -0
- package/dist/overlay/SgDialog.d.ts +39 -0
- package/dist/overlay/SgDialog.d.ts.map +1 -0
- package/dist/overlay/SgDialog.js +177 -0
- package/dist/overlay/SgPopup.d.ts +42 -0
- package/dist/overlay/SgPopup.d.ts.map +1 -0
- package/dist/overlay/SgPopup.js +218 -0
- package/dist/rhf.d.ts +6 -0
- package/dist/rhf.d.ts.map +1 -0
- package/dist/rhf.js +1 -0
- package/dist/validators.d.ts +27 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +218 -0
- package/dist/wizard/SGWizard.d.ts +28 -0
- package/dist/wizard/SGWizard.d.ts.map +1 -0
- package/dist/wizard/SGWizard.js +124 -0
- package/package.json +53 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { SgButton } from "./SgButton";
|
|
5
|
+
/* ── helpers ── */
|
|
6
|
+
function cn(...parts) {
|
|
7
|
+
return parts.filter(Boolean).join(" ");
|
|
8
|
+
}
|
|
9
|
+
/* ── constants ── */
|
|
10
|
+
const ITEM_GAP = { sm: 40, md: 48, lg: 56 };
|
|
11
|
+
const DEFAULT_RADIUS = { sm: 100, md: 120, lg: 150 };
|
|
12
|
+
const DIRECTION_START_ANGLE = {
|
|
13
|
+
up: 270,
|
|
14
|
+
down: 90,
|
|
15
|
+
left: 180,
|
|
16
|
+
right: 0
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_TOOLTIP = {
|
|
19
|
+
up: "left",
|
|
20
|
+
down: "right",
|
|
21
|
+
left: "top",
|
|
22
|
+
right: "bottom"
|
|
23
|
+
};
|
|
24
|
+
/* ── positioning ── */
|
|
25
|
+
function degToRad(deg) {
|
|
26
|
+
return (deg * Math.PI) / 180;
|
|
27
|
+
}
|
|
28
|
+
function computeItemPositions(items, type, direction, size, radius) {
|
|
29
|
+
const count = items.length;
|
|
30
|
+
if (count === 0)
|
|
31
|
+
return [];
|
|
32
|
+
if (type === "linear") {
|
|
33
|
+
const gap = ITEM_GAP[size];
|
|
34
|
+
return items.map((_, i) => {
|
|
35
|
+
const dist = gap * (i + 1);
|
|
36
|
+
switch (direction) {
|
|
37
|
+
case "up":
|
|
38
|
+
return { x: 0, y: -dist };
|
|
39
|
+
case "down":
|
|
40
|
+
return { x: 0, y: dist };
|
|
41
|
+
case "left":
|
|
42
|
+
return { x: -dist, y: 0 };
|
|
43
|
+
case "right":
|
|
44
|
+
return { x: dist, y: 0 };
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const r = radius ?? DEFAULT_RADIUS[size];
|
|
49
|
+
const centerAngle = DIRECTION_START_ANGLE[direction];
|
|
50
|
+
let totalArc;
|
|
51
|
+
switch (type) {
|
|
52
|
+
case "circle":
|
|
53
|
+
totalArc = 360;
|
|
54
|
+
break;
|
|
55
|
+
case "semi-circle":
|
|
56
|
+
totalArc = 180;
|
|
57
|
+
break;
|
|
58
|
+
case "quarter-circle":
|
|
59
|
+
totalArc = 90;
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
totalArc = 360;
|
|
63
|
+
}
|
|
64
|
+
const step = type === "circle" ? totalArc / count : totalArc / Math.max(count - 1, 1);
|
|
65
|
+
const startAngle = type === "circle" ? centerAngle : centerAngle - totalArc / 2;
|
|
66
|
+
return items.map((_, i) => {
|
|
67
|
+
const angle = startAngle + step * i;
|
|
68
|
+
const rad = degToRad(angle);
|
|
69
|
+
return {
|
|
70
|
+
x: Math.round(r * Math.cos(rad)),
|
|
71
|
+
y: Math.round(r * Math.sin(rad))
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/* ── default icons ── */
|
|
76
|
+
function PlusIcon() {
|
|
77
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M5 12h14" }), _jsx("path", { d: "M12 5v14" })] }));
|
|
78
|
+
}
|
|
79
|
+
/* ── tooltip ── */
|
|
80
|
+
const TOOLTIP_POS = {
|
|
81
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
|
|
82
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
|
|
83
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-2",
|
|
84
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-2"
|
|
85
|
+
};
|
|
86
|
+
/* ── component ── */
|
|
87
|
+
export function SgSpeedDial(props) {
|
|
88
|
+
const { items, direction = "up", type = "linear", severity = "primary", appearance = "solid", size = "md", icon, activeIcon, radius, disabled = false, className, style, transitionDelay = 30, mask = false, tooltipPosition, onOpen, onClose } = props;
|
|
89
|
+
const [open, setOpen] = React.useState(false);
|
|
90
|
+
const containerRef = React.useRef(null);
|
|
91
|
+
const toggle = React.useCallback(() => {
|
|
92
|
+
if (disabled)
|
|
93
|
+
return;
|
|
94
|
+
setOpen((prev) => {
|
|
95
|
+
const next = !prev;
|
|
96
|
+
if (next)
|
|
97
|
+
onOpen?.();
|
|
98
|
+
else
|
|
99
|
+
onClose?.();
|
|
100
|
+
return next;
|
|
101
|
+
});
|
|
102
|
+
}, [disabled, onOpen, onClose]);
|
|
103
|
+
const close = React.useCallback(() => {
|
|
104
|
+
if (open) {
|
|
105
|
+
setOpen(false);
|
|
106
|
+
onClose?.();
|
|
107
|
+
}
|
|
108
|
+
}, [open, onClose]);
|
|
109
|
+
// click-outside
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
if (!open)
|
|
112
|
+
return;
|
|
113
|
+
function handleClick(e) {
|
|
114
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
115
|
+
close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function handleEscape(e) {
|
|
119
|
+
if (e.key === "Escape")
|
|
120
|
+
close();
|
|
121
|
+
}
|
|
122
|
+
document.addEventListener("mousedown", handleClick);
|
|
123
|
+
document.addEventListener("keydown", handleEscape);
|
|
124
|
+
return () => {
|
|
125
|
+
document.removeEventListener("mousedown", handleClick);
|
|
126
|
+
document.removeEventListener("keydown", handleEscape);
|
|
127
|
+
};
|
|
128
|
+
}, [open, close]);
|
|
129
|
+
const positions = computeItemPositions(items, type, direction, size, radius);
|
|
130
|
+
const tipPos = tooltipPosition ?? DEFAULT_TOOLTIP[direction];
|
|
131
|
+
const triggerIcon = icon ?? _jsx(PlusIcon, {});
|
|
132
|
+
const triggerActiveIcon = activeIcon ?? triggerIcon;
|
|
133
|
+
return (_jsxs(_Fragment, { children: [mask ? (_jsx("div", { className: cn("fixed inset-0 bg-black/50 transition-opacity duration-300 z-40", open ? "opacity-100" : "opacity-0 pointer-events-none"), onClick: close })) : null, _jsxs("div", { ref: containerRef, className: cn("relative inline-flex items-center justify-center", mask && open ? "z-50" : "z-auto", className), style: style, children: [items.map((item, i) => {
|
|
134
|
+
const pos = positions[i] ?? { x: 0, y: 0 };
|
|
135
|
+
const itemSeverity = item.severity ?? "plain";
|
|
136
|
+
return (_jsx("div", { className: "absolute", style: {
|
|
137
|
+
transform: open
|
|
138
|
+
? `translate(${pos.x}px, ${pos.y}px) scale(1)`
|
|
139
|
+
: "translate(0, 0) scale(0)",
|
|
140
|
+
opacity: open ? 1 : 0,
|
|
141
|
+
transition: `transform 200ms ease, opacity 200ms ease`,
|
|
142
|
+
transitionDelay: open ? `${i * transitionDelay}ms` : `${(items.length - 1 - i) * transitionDelay}ms`
|
|
143
|
+
}, children: _jsxs("div", { className: "relative group", children: [_jsx(SgButton, { severity: itemSeverity, appearance: itemSeverity === "plain" ? "solid" : appearance, size: size, shape: "rounded", disabled: item.disabled, className: item.className, onClick: () => {
|
|
144
|
+
item.onClick?.();
|
|
145
|
+
close();
|
|
146
|
+
}, "aria-label": item.label, leftIcon: item.icon }), item.label ? (_jsx("span", { className: cn("absolute whitespace-nowrap rounded bg-foreground/90 px-2 py-1 text-xs text-background", "opacity-0 group-hover:opacity-100 transition-opacity duration-150 pointer-events-none", TOOLTIP_POS[tipPos]), children: item.label })) : null] }) }, i));
|
|
147
|
+
}), _jsx(SgButton, { severity: severity, appearance: appearance, size: size, shape: "rounded", disabled: disabled, onClick: toggle, "aria-label": open ? "Close" : "Open", "aria-expanded": open, leftIcon: _jsx("span", { className: "inline-flex transition-transform duration-300", style: { transform: open ? "rotate(45deg)" : "rotate(0deg)" }, children: open ? triggerActiveIcon : triggerIcon }) })] })] }));
|
|
148
|
+
}
|
|
149
|
+
SgSpeedDial.displayName = "SgSpeedDial";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SgButtonCustomColors } from "./SgButton";
|
|
3
|
+
type Severity = "primary" | "secondary" | "success" | "info" | "warning" | "help" | "danger";
|
|
4
|
+
type Appearance = "solid" | "outline" | "ghost";
|
|
5
|
+
type Size = "sm" | "md" | "lg";
|
|
6
|
+
type Shape = "default" | "rounded" | "square";
|
|
7
|
+
type Elevation = "none" | "sm" | "md";
|
|
8
|
+
export type SgSplitButtonItem = {
|
|
9
|
+
label: string;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
separator?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type SgSplitButtonProps = {
|
|
16
|
+
label?: string;
|
|
17
|
+
leftIcon?: React.ReactNode;
|
|
18
|
+
severity?: Severity;
|
|
19
|
+
appearance?: Appearance;
|
|
20
|
+
size?: Size;
|
|
21
|
+
shape?: Shape;
|
|
22
|
+
elevation?: Elevation;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
onClick?: () => void;
|
|
26
|
+
items: SgSplitButtonItem[];
|
|
27
|
+
customColors?: SgButtonCustomColors;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const SgSplitButton: React.ForwardRefExoticComponent<SgSplitButtonProps & React.RefAttributes<HTMLDivElement>>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=SgSplitButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgSplitButton.d.ts","sourceRoot":"","sources":["../../src/buttons/SgSplitButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,KAAK,QAAQ,GACT,SAAS,GACT,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,GACT,MAAM,GACN,QAAQ,CAAC;AAEb,KAAK,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhD,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/B,KAAK,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9C,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAmBF,eAAO,MAAM,aAAa,2FAkKzB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { SgButton, resolveButtonColors } from "./SgButton";
|
|
5
|
+
function ChevronDown({ className }) {
|
|
6
|
+
return (_jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
7
|
+
}
|
|
8
|
+
export const SgSplitButton = React.forwardRef(({ label, leftIcon, severity = "primary", appearance = "solid", size = "md", shape = "default", elevation = "none", disabled = false, loading = false, onClick, items, customColors, className }, ref) => {
|
|
9
|
+
const [open, setOpen] = React.useState(false);
|
|
10
|
+
const containerRef = React.useRef(null);
|
|
11
|
+
const menuRef = React.useRef(null);
|
|
12
|
+
React.useImperativeHandle(ref, () => containerRef.current);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (!open)
|
|
15
|
+
return;
|
|
16
|
+
function handleClickOutside(e) {
|
|
17
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
18
|
+
setOpen(false);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function handleEscape(e) {
|
|
22
|
+
if (e.key === "Escape")
|
|
23
|
+
setOpen(false);
|
|
24
|
+
}
|
|
25
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26
|
+
document.addEventListener("keydown", handleEscape);
|
|
27
|
+
return () => {
|
|
28
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
29
|
+
document.removeEventListener("keydown", handleEscape);
|
|
30
|
+
};
|
|
31
|
+
}, [open]);
|
|
32
|
+
const toggle = () => {
|
|
33
|
+
if (!disabled && !loading)
|
|
34
|
+
setOpen((prev) => !prev);
|
|
35
|
+
};
|
|
36
|
+
const handleItemClick = (item) => {
|
|
37
|
+
if (item.disabled)
|
|
38
|
+
return;
|
|
39
|
+
item.onClick?.();
|
|
40
|
+
setOpen(false);
|
|
41
|
+
};
|
|
42
|
+
const colors = resolveButtonColors(severity, customColors);
|
|
43
|
+
const toneKey = severity === "danger"
|
|
44
|
+
? "error"
|
|
45
|
+
: severity === "help"
|
|
46
|
+
? "tertiary"
|
|
47
|
+
: severity;
|
|
48
|
+
const tone100 = `var(--sg-${toneKey}-100, var(--sg-primary-100))`;
|
|
49
|
+
const tone200 = `var(--sg-${toneKey}-200, ${tone100})`;
|
|
50
|
+
const tone300 = `var(--sg-${toneKey}-300, ${tone200})`;
|
|
51
|
+
const menuBg = `rgb(${tone100})`;
|
|
52
|
+
const menuFg = colors.bg;
|
|
53
|
+
const menuHoverBg = `rgb(${tone200})`;
|
|
54
|
+
const menuBorder = `rgb(${tone200})`;
|
|
55
|
+
const dividerBorderClass = appearance === "solid"
|
|
56
|
+
? "border-l border-[rgb(var(--sg-border))]"
|
|
57
|
+
: appearance === "outline"
|
|
58
|
+
? "border-l border-[var(--sg-btn-border,currentColor)]"
|
|
59
|
+
: "border-l border-current/20";
|
|
60
|
+
const sizeClasses = {
|
|
61
|
+
sm: { menu: "min-w-[140px]", item: "px-3 py-1.5", text: "text-sm", iconSize: "size-3.5" },
|
|
62
|
+
md: { menu: "min-w-[160px]", item: "px-4 py-2", text: "text-sm", iconSize: "size-4" },
|
|
63
|
+
lg: { menu: "min-w-[180px]", item: "px-5 py-2.5", text: "text-base", iconSize: "size-5" }
|
|
64
|
+
};
|
|
65
|
+
const s = sizeClasses[size];
|
|
66
|
+
return (_jsxs("div", { ref: containerRef, className: `relative inline-flex ${className ?? ""}`, children: [_jsx(SgButton, { severity: severity, appearance: appearance, size: size, shape: shape, elevation: elevation, disabled: disabled, loading: loading, leftIcon: leftIcon, onClick: onClick, customColors: customColors, className: "rounded-r-none", children: label }), _jsx(SgButton, { severity: severity, appearance: appearance, size: size, shape: shape, elevation: elevation, disabled: disabled || loading, leftIcon: _jsx(ChevronDown, { className: "size-4" }), onClick: toggle, customColors: customColors, className: `rounded-l-none ${dividerBorderClass}`, "aria-haspopup": "true", "aria-expanded": open }), open && (_jsx("div", { ref: menuRef, role: "menu", className: `absolute right-0 top-full z-50 mt-1 ${s.menu} overflow-hidden rounded-lg shadow-lg`, style: {
|
|
67
|
+
backgroundColor: menuBg,
|
|
68
|
+
color: menuFg,
|
|
69
|
+
border: `1px solid ${menuBorder}`
|
|
70
|
+
}, children: items.map((item, i) => (_jsxs(React.Fragment, { children: [item.separator && i > 0 ? (_jsx("div", { className: "my-0.5", style: {
|
|
71
|
+
borderTop: `1px solid ${appearance === "solid" ? `rgb(${tone300})` : "rgb(var(--sg-text)/0.2)"}`
|
|
72
|
+
} })) : null, _jsxs("button", { role: "menuitem", type: "button", disabled: item.disabled, className: `flex w-full items-center gap-2 ${s.item} ${s.text} transition-colors disabled:opacity-50 disabled:cursor-not-allowed`, style: {
|
|
73
|
+
backgroundColor: "transparent"
|
|
74
|
+
}, onMouseEnter: (e) => {
|
|
75
|
+
if (!item.disabled)
|
|
76
|
+
(e.currentTarget.style.backgroundColor = menuHoverBg);
|
|
77
|
+
}, onMouseLeave: (e) => {
|
|
78
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
79
|
+
}, onClick: () => handleItemClick(item), children: [item.icon ? _jsx("span", { className: `shrink-0 ${s.iconSize}`, children: item.icon }) : null, _jsx("span", { children: item.label })] })] }, i))) }))] }));
|
|
80
|
+
});
|
|
81
|
+
SgSplitButton.displayName = "SgSplitButton";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SgClockTheme } from "./themes/types";
|
|
3
|
+
export type SgClockProps = {
|
|
4
|
+
variant?: "digital" | "analog" | "instrument";
|
|
5
|
+
size?: "sm" | "md" | "lg" | number;
|
|
6
|
+
timezone?: string;
|
|
7
|
+
locale?: string;
|
|
8
|
+
format?: "12h" | "24h";
|
|
9
|
+
showSeconds?: boolean;
|
|
10
|
+
digitalStyle?: "default" | "segment";
|
|
11
|
+
secondHandMode?: "step" | "smooth";
|
|
12
|
+
instrumentMode?: "hours" | "minutes" | "seconds";
|
|
13
|
+
instrumentWindow?: number;
|
|
14
|
+
instrumentRange?: {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
};
|
|
18
|
+
instrumentStep?: number;
|
|
19
|
+
instrumentStyle?: "outlined" | "soft";
|
|
20
|
+
instrumentHighlight?: boolean;
|
|
21
|
+
instrumentSmooth?: boolean;
|
|
22
|
+
themeId?: string;
|
|
23
|
+
theme?: SgClockTheme;
|
|
24
|
+
className?: string;
|
|
25
|
+
centerOverlay?: React.ReactNode;
|
|
26
|
+
};
|
|
27
|
+
export declare function SgClock(props: SgClockProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=SgClock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgClock.d.ts","sourceRoot":"","sources":["../../src/clock/SgClock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAenD,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC9C,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACjD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACtC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACjC,CAAC;AA+bF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAqE1C"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useSgTime } from "./SgTimeProvider";
|
|
5
|
+
import { useSgClockThemeResolver } from "./themes/provider";
|
|
6
|
+
import { ThemeLayer, resolveTheme } from "./themes/renderTheme";
|
|
7
|
+
import { useDarkFlag } from "./themes/useDarkFlag";
|
|
8
|
+
import { getTheme } from "./themes/registry";
|
|
9
|
+
function cn(...parts) {
|
|
10
|
+
return parts.filter(Boolean).join(" ");
|
|
11
|
+
}
|
|
12
|
+
function useSecondTick() {
|
|
13
|
+
const [, setTick] = React.useState(0);
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
const id = window.setInterval(() => setTick((v) => v + 1), 1000);
|
|
16
|
+
return () => window.clearInterval(id);
|
|
17
|
+
}, []);
|
|
18
|
+
}
|
|
19
|
+
function getHmsForTimezone(date, locale, timeZone) {
|
|
20
|
+
const parts = new Intl.DateTimeFormat(locale, {
|
|
21
|
+
timeZone,
|
|
22
|
+
hour: "2-digit",
|
|
23
|
+
minute: "2-digit",
|
|
24
|
+
second: "2-digit",
|
|
25
|
+
hour12: false
|
|
26
|
+
}).formatToParts(date);
|
|
27
|
+
const get = (type) => {
|
|
28
|
+
const p = parts.find((x) => x.type === type)?.value ?? "0";
|
|
29
|
+
const n = Number.parseInt(p, 10);
|
|
30
|
+
return Number.isFinite(n) ? n : 0;
|
|
31
|
+
};
|
|
32
|
+
return { h: get("hour"), m: get("minute"), s: get("second") };
|
|
33
|
+
}
|
|
34
|
+
function sizeToClass(size) {
|
|
35
|
+
if (typeof size === "number")
|
|
36
|
+
return "";
|
|
37
|
+
if (size === "sm")
|
|
38
|
+
return "text-sm";
|
|
39
|
+
if (size === "lg")
|
|
40
|
+
return "text-2xl";
|
|
41
|
+
return "text-base";
|
|
42
|
+
}
|
|
43
|
+
function digitalSizeToNumber(size) {
|
|
44
|
+
if (typeof size === "number")
|
|
45
|
+
return size;
|
|
46
|
+
if (size === "sm")
|
|
47
|
+
return 12;
|
|
48
|
+
if (size === "lg")
|
|
49
|
+
return 28;
|
|
50
|
+
return 16;
|
|
51
|
+
}
|
|
52
|
+
const SEGMENTS = {
|
|
53
|
+
"0": [
|
|
54
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
55
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
56
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
57
|
+
{ x: 1, y: 9, w: 4, h: 1 },
|
|
58
|
+
{ x: 0, y: 5, w: 1, h: 4 },
|
|
59
|
+
{ x: 0, y: 1, w: 1, h: 4 }
|
|
60
|
+
],
|
|
61
|
+
"1": [
|
|
62
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
63
|
+
{ x: 5, y: 5, w: 1, h: 4 }
|
|
64
|
+
],
|
|
65
|
+
"2": [
|
|
66
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
67
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
68
|
+
{ x: 1, y: 4.5, w: 4, h: 1 },
|
|
69
|
+
{ x: 0, y: 5, w: 1, h: 4 },
|
|
70
|
+
{ x: 1, y: 9, w: 4, h: 1 }
|
|
71
|
+
],
|
|
72
|
+
"3": [
|
|
73
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
74
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
75
|
+
{ x: 1, y: 4.5, w: 4, h: 1 },
|
|
76
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
77
|
+
{ x: 1, y: 9, w: 4, h: 1 }
|
|
78
|
+
],
|
|
79
|
+
"4": [
|
|
80
|
+
{ x: 0, y: 1, w: 1, h: 4 },
|
|
81
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
82
|
+
{ x: 1, y: 4.5, w: 4, h: 1 },
|
|
83
|
+
{ x: 5, y: 5, w: 1, h: 4 }
|
|
84
|
+
],
|
|
85
|
+
"5": [
|
|
86
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
87
|
+
{ x: 0, y: 1, w: 1, h: 4 },
|
|
88
|
+
{ x: 1, y: 4.5, w: 4, h: 1 },
|
|
89
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
90
|
+
{ x: 1, y: 9, w: 4, h: 1 }
|
|
91
|
+
],
|
|
92
|
+
"6": [
|
|
93
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
94
|
+
{ x: 0, y: 1, w: 1, h: 4 },
|
|
95
|
+
{ x: 1, y: 4.5, w: 4, h: 1 },
|
|
96
|
+
{ x: 0, y: 5, w: 1, h: 4 },
|
|
97
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
98
|
+
{ x: 1, y: 9, w: 4, h: 1 }
|
|
99
|
+
],
|
|
100
|
+
"7": [
|
|
101
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
102
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
103
|
+
{ x: 5, y: 5, w: 1, h: 4 }
|
|
104
|
+
],
|
|
105
|
+
"8": [
|
|
106
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
107
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
108
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
109
|
+
{ x: 1, y: 9, w: 4, h: 1 },
|
|
110
|
+
{ x: 0, y: 5, w: 1, h: 4 },
|
|
111
|
+
{ x: 0, y: 1, w: 1, h: 4 },
|
|
112
|
+
{ x: 1, y: 4.5, w: 4, h: 1 }
|
|
113
|
+
],
|
|
114
|
+
"9": [
|
|
115
|
+
{ x: 1, y: 0, w: 4, h: 1 },
|
|
116
|
+
{ x: 5, y: 1, w: 1, h: 4 },
|
|
117
|
+
{ x: 5, y: 5, w: 1, h: 4 },
|
|
118
|
+
{ x: 1, y: 9, w: 4, h: 1 },
|
|
119
|
+
{ x: 0, y: 1, w: 1, h: 4 },
|
|
120
|
+
{ x: 1, y: 4.5, w: 4, h: 1 }
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
function round(n) {
|
|
124
|
+
return Math.round(n * 1000) / 1000;
|
|
125
|
+
}
|
|
126
|
+
function renderSegmentDigit(digit, x, y, scale, keyPrefix) {
|
|
127
|
+
const segs = SEGMENTS[digit] ?? [];
|
|
128
|
+
return segs.map((s, idx) => (_jsx("rect", { x: round(x + s.x * scale), y: round(y + s.y * scale), width: round(s.w * scale), height: round(s.h * scale), className: "fill-neutral-800 dark:fill-neutral-200", rx: round(0.3 * scale) }, `${keyPrefix}-${idx}`)));
|
|
129
|
+
}
|
|
130
|
+
function renderSegmentChar(ch, x, y, scale, keyPrefix) {
|
|
131
|
+
if (ch === ":") {
|
|
132
|
+
return (_jsxs("g", { children: [_jsx("rect", { x: round(x + 2.2 * scale), y: round(y + 2 * scale), width: round(1 * scale), height: round(1 * scale), className: "fill-neutral-800 dark:fill-neutral-200" }), _jsx("rect", { x: round(x + 2.2 * scale), y: round(y + 7 * scale), width: round(1 * scale), height: round(1 * scale), className: "fill-neutral-800 dark:fill-neutral-200" })] }, keyPrefix));
|
|
133
|
+
}
|
|
134
|
+
if (!SEGMENTS[ch])
|
|
135
|
+
return null;
|
|
136
|
+
return _jsx("g", { children: renderSegmentDigit(ch, x, y, scale, keyPrefix) }, keyPrefix);
|
|
137
|
+
}
|
|
138
|
+
function renderSegmentText(text, sizePx) {
|
|
139
|
+
const baseScale = sizePx / 16;
|
|
140
|
+
const digitW = 6 * baseScale;
|
|
141
|
+
const digitH = 10 * baseScale;
|
|
142
|
+
const gap = 1.4 * baseScale;
|
|
143
|
+
const colonW = 4 * baseScale;
|
|
144
|
+
let width = 0;
|
|
145
|
+
for (const ch of text) {
|
|
146
|
+
width += ch === ":" ? colonW : digitW;
|
|
147
|
+
width += gap;
|
|
148
|
+
}
|
|
149
|
+
width -= gap;
|
|
150
|
+
const height = digitH;
|
|
151
|
+
let cursor = 0;
|
|
152
|
+
const nodes = [];
|
|
153
|
+
text.split("").forEach((ch, idx) => {
|
|
154
|
+
const w = ch === ":" ? colonW : digitW;
|
|
155
|
+
nodes.push(renderSegmentChar(ch, cursor, 0, baseScale, `seg-${idx}`));
|
|
156
|
+
cursor += w + gap;
|
|
157
|
+
});
|
|
158
|
+
return { width, height, nodes };
|
|
159
|
+
}
|
|
160
|
+
function wrapRange(value, start, end) {
|
|
161
|
+
const size = end - start + 1;
|
|
162
|
+
const v = ((value - start) % size + size) % size;
|
|
163
|
+
return start + v;
|
|
164
|
+
}
|
|
165
|
+
function getTimeValue(date, locale, timeZone, mode, smooth) {
|
|
166
|
+
const { h, m, s } = getHmsForTimezone(date, locale, timeZone);
|
|
167
|
+
const ms = date.getMilliseconds();
|
|
168
|
+
if (mode === "hours") {
|
|
169
|
+
if (!smooth)
|
|
170
|
+
return { base: h, frac: 0 };
|
|
171
|
+
const value = h + m / 60 + s / 3600;
|
|
172
|
+
return { base: Math.floor(value), frac: value - Math.floor(value) };
|
|
173
|
+
}
|
|
174
|
+
if (mode === "minutes") {
|
|
175
|
+
if (!smooth)
|
|
176
|
+
return { base: m, frac: 0 };
|
|
177
|
+
const value = m + s / 60;
|
|
178
|
+
return { base: Math.floor(value), frac: value - Math.floor(value) };
|
|
179
|
+
}
|
|
180
|
+
if (!smooth)
|
|
181
|
+
return { base: s, frac: 0 };
|
|
182
|
+
const value = s + ms / 1000;
|
|
183
|
+
return { base: Math.floor(value), frac: value - Math.floor(value) };
|
|
184
|
+
}
|
|
185
|
+
function InstrumentClock({ size = "md", locale = "pt-BR", timezone, instrumentWindow = 12, instrumentRange, instrumentStep = 1, instrumentStyle = "outlined", instrumentHighlight = true, instrumentSmooth = true, className }) {
|
|
186
|
+
const { tick, nowMs } = useSgTime();
|
|
187
|
+
void tick;
|
|
188
|
+
useSecondTick();
|
|
189
|
+
const date = new Date(nowMs());
|
|
190
|
+
const windowSize = instrumentWindow % 2 === 0 ? instrumentWindow + 1 : instrumentWindow;
|
|
191
|
+
const centerIndex = Math.floor(windowSize / 2);
|
|
192
|
+
const cellWidth = typeof size === "number" ? size : size === "sm" ? 36 : size === "lg" ? 64 : 48;
|
|
193
|
+
const height = Math.round(cellWidth * 0.9);
|
|
194
|
+
const pad = instrumentStyle === "soft" ? "bg-muted/40" : "bg-background";
|
|
195
|
+
const border = instrumentStyle === "outlined" ? "border border-border" : "";
|
|
196
|
+
const lastPosRef = React.useRef({
|
|
197
|
+
hours: null,
|
|
198
|
+
minutes: null,
|
|
199
|
+
seconds: null
|
|
200
|
+
});
|
|
201
|
+
const renderLine = (mode, range) => {
|
|
202
|
+
const { base, frac } = getTimeValue(date, locale, timezone, mode, instrumentSmooth);
|
|
203
|
+
const values = Array.from({ length: windowSize }).map((_, idx) => {
|
|
204
|
+
const offset = (idx - centerIndex) * instrumentStep;
|
|
205
|
+
const value = wrapRange(base + offset, range.start, range.end);
|
|
206
|
+
return value;
|
|
207
|
+
});
|
|
208
|
+
const pos = base + frac;
|
|
209
|
+
const lastPos = lastPosRef.current[mode] ?? null;
|
|
210
|
+
if (lastPos === null) {
|
|
211
|
+
lastPosRef.current[mode] = pos;
|
|
212
|
+
}
|
|
213
|
+
else if (instrumentSmooth) {
|
|
214
|
+
const rangeSize = range.end - range.start + 1;
|
|
215
|
+
const delta = pos - lastPos;
|
|
216
|
+
if (delta < -rangeSize / 2) {
|
|
217
|
+
lastPosRef.current[mode] = lastPos - rangeSize;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const translateX = ((lastPosRef.current[mode] ?? pos) - pos) * cellWidth;
|
|
221
|
+
lastPosRef.current[mode] = pos;
|
|
222
|
+
return (_jsxs("div", { className: cn("relative overflow-hidden rounded-lg", pad, border), style: { height }, children: [_jsx("div", { className: "flex items-center", style: {
|
|
223
|
+
transform: `translateX(${translateX}px)`,
|
|
224
|
+
transition: instrumentSmooth ? "transform 60ms linear" : "none"
|
|
225
|
+
}, children: values.map((v, idx) => {
|
|
226
|
+
const isCenter = idx === centerIndex;
|
|
227
|
+
return (_jsx("div", { className: cn("flex items-center justify-center font-mono tabular-nums text-muted-foreground", isCenter && instrumentHighlight ? "text-foreground font-semibold" : ""), style: { width: cellWidth, height }, children: String(v).padStart(2, "0") }, `${mode}-${v}-${idx}`));
|
|
228
|
+
}) }), _jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: _jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute -top-2 left-1/2 h-2 w-0.5 -translate-x-1/2 bg-foreground" }), _jsx("div", { className: "absolute -bottom-2 left-1/2 h-2 w-0.5 -translate-x-1/2 bg-foreground" }), _jsx("div", { className: "h-full w-0.5 bg-foreground", style: { height } })] }) })] }));
|
|
229
|
+
};
|
|
230
|
+
return (_jsxs("div", { className: cn("relative w-full max-w-full space-y-3", className), children: [renderLine("hours", instrumentRange ?? { start: 0, end: 23 }), renderLine("minutes", instrumentRange ?? { start: 0, end: 59 }), renderLine("seconds", instrumentRange ?? { start: 0, end: 59 })] }));
|
|
231
|
+
}
|
|
232
|
+
function AnalogClock({ size = 280, timezone, locale = "pt-BR", showSeconds = true, secondHandMode = "step", themeId = "classic", theme, className, centerOverlay }) {
|
|
233
|
+
const { tick, nowMs } = useSgTime();
|
|
234
|
+
void tick;
|
|
235
|
+
useSecondTick();
|
|
236
|
+
const resolver = useSgClockThemeResolver();
|
|
237
|
+
const dark = useDarkFlag();
|
|
238
|
+
const date = new Date(nowMs());
|
|
239
|
+
const { h, m, s } = getHmsForTimezone(date, locale, timezone);
|
|
240
|
+
const ms = date.getMilliseconds();
|
|
241
|
+
const sec = secondHandMode === "smooth" ? s + ms / 1000 : s;
|
|
242
|
+
const secDeg = (sec / 60) * 360;
|
|
243
|
+
const minDeg = ((m + sec / 60) / 60) * 360;
|
|
244
|
+
const hourDeg = (((h % 12) + m / 60 + sec / 3600) / 12) * 360;
|
|
245
|
+
const themeObj = theme ??
|
|
246
|
+
(resolver ? resolveTheme(resolver, themeId, "classic") : getTheme(themeId) ?? getTheme("classic"));
|
|
247
|
+
return (_jsx("div", { className: className, children: _jsxs("svg", { width: size, height: size, viewBox: "0 0 100 100", className: "block", "aria-label": "Analog clock", children: [_jsx("g", { id: "theme", children: themeObj ? _jsx(ThemeLayer, { theme: themeObj, args: { size, dark } }) : null }), _jsxs("g", { id: "hands", children: [_jsx("g", { transform: `rotate(${hourDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "50", x2: "50", y2: "28", className: "stroke-neutral-800 dark:stroke-neutral-200", strokeWidth: "2.6", strokeLinecap: "round" }) }), _jsx("g", { transform: `rotate(${minDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "50", x2: "50", y2: "18", className: "stroke-neutral-800 dark:stroke-neutral-200", strokeWidth: "1.7", strokeLinecap: "round" }) }), showSeconds && (_jsx("g", { transform: `rotate(${secDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "54", x2: "50", y2: "14", className: "stroke-rose-500", strokeWidth: "0.9", strokeLinecap: "round" }) }))] }), _jsx("circle", { cx: "50", cy: "50", r: "2.2", className: "fill-neutral-800 dark:fill-neutral-200" }), showSeconds ? _jsx("circle", { cx: "50", cy: "50", r: "1.1", className: "fill-rose-500" }) : null, centerOverlay ? (_jsx("foreignObject", { x: "35", y: "35", width: "30", height: "30", children: _jsx("div", { className: "flex h-full w-full items-center justify-center", children: centerOverlay }) })) : null] }) }));
|
|
248
|
+
}
|
|
249
|
+
function DigitalClock({ timezone, locale = "pt-BR", format = "24h", showSeconds = true, size = "md", digitalStyle = "default", className }) {
|
|
250
|
+
const { tick, nowMs } = useSgTime();
|
|
251
|
+
void tick;
|
|
252
|
+
useSecondTick();
|
|
253
|
+
const d = new Date(nowMs());
|
|
254
|
+
const text = new Intl.DateTimeFormat(locale, {
|
|
255
|
+
timeZone: timezone,
|
|
256
|
+
hour: "2-digit",
|
|
257
|
+
minute: "2-digit",
|
|
258
|
+
second: showSeconds ? "2-digit" : undefined,
|
|
259
|
+
hour12: format === "12h"
|
|
260
|
+
}).format(d);
|
|
261
|
+
const classSize = sizeToClass(size);
|
|
262
|
+
const fontSize = typeof size === "number" ? { fontSize: `${size}px`, lineHeight: 1 } : undefined;
|
|
263
|
+
const sizePx = digitalSizeToNumber(size);
|
|
264
|
+
if (digitalStyle === "segment") {
|
|
265
|
+
const seg = renderSegmentText(text, sizePx);
|
|
266
|
+
return (_jsx("svg", { width: seg.width, height: seg.height, viewBox: `0 0 ${seg.width} ${seg.height}`, className: cn("block", className), "aria-label": "Digital clock", children: seg.nodes }));
|
|
267
|
+
}
|
|
268
|
+
return (_jsx("div", { className: cn("font-mono tabular-nums", classSize, className), style: fontSize, children: text }));
|
|
269
|
+
}
|
|
270
|
+
export function SgClock(props) {
|
|
271
|
+
const { variant = "digital", size = "md", timezone, locale = "pt-BR", format = "24h", showSeconds = true, digitalStyle = "default", secondHandMode = "step", instrumentMode = "minutes", instrumentWindow = 12, instrumentRange, instrumentStep = 1, instrumentStyle = "outlined", instrumentHighlight = true, instrumentSmooth = true, themeId = "classic", theme, className, centerOverlay } = props;
|
|
272
|
+
if (variant === "analog") {
|
|
273
|
+
const analogSize = typeof size === "number" ? size : size === "sm" ? 140 : size === "lg" ? 320 : 240;
|
|
274
|
+
return (_jsx(AnalogClock, { size: analogSize, themeId: themeId, theme: theme, timezone: timezone, locale: locale, showSeconds: showSeconds, secondHandMode: secondHandMode, className: className, centerOverlay: centerOverlay }));
|
|
275
|
+
}
|
|
276
|
+
if (variant === "instrument") {
|
|
277
|
+
return (_jsx(InstrumentClock, { size: size, locale: locale, timezone: timezone, instrumentMode: instrumentMode, instrumentWindow: instrumentWindow, instrumentRange: instrumentRange, instrumentStep: instrumentStep, instrumentStyle: instrumentStyle, instrumentHighlight: instrumentHighlight, instrumentSmooth: instrumentSmooth, className: className }));
|
|
278
|
+
}
|
|
279
|
+
return (_jsx(DigitalClock, { timezone: timezone, locale: locale, format: format, showSeconds: showSeconds, size: size, digitalStyle: digitalStyle, className: className }));
|
|
280
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SgTimeContextValue = {
|
|
3
|
+
serverStartMs: number;
|
|
4
|
+
perfStartMs: number;
|
|
5
|
+
tick: number;
|
|
6
|
+
nowMs: () => number;
|
|
7
|
+
};
|
|
8
|
+
export declare function useSgTime(): SgTimeContextValue;
|
|
9
|
+
export declare function SgTimeProvider({ initialServerTime, children }: {
|
|
10
|
+
initialServerTime: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=SgTimeProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SgTimeProvider.d.ts","sourceRoot":"","sources":["../../src/clock/SgTimeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,MAAM,CAAC;CACrB,CAAC;AAIF,wBAAgB,SAAS,uBAIxB;AAED,wBAAgB,cAAc,CAAC,EAC7B,iBAAiB,EACjB,QAAQ,EACT,EAAE;IACD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,2CAyCA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
const SgTimeContext = React.createContext(null);
|
|
5
|
+
export function useSgTime() {
|
|
6
|
+
const ctx = React.useContext(SgTimeContext);
|
|
7
|
+
if (!ctx)
|
|
8
|
+
throw new Error("useSgTime must be used within <SgTimeProvider />");
|
|
9
|
+
return ctx;
|
|
10
|
+
}
|
|
11
|
+
export function SgTimeProvider({ initialServerTime, children }) {
|
|
12
|
+
const serverStartMsRef = React.useRef(Date.parse(initialServerTime));
|
|
13
|
+
const perfStartMsRef = React.useRef(0);
|
|
14
|
+
const [tick, setTick] = React.useState(0);
|
|
15
|
+
const [hydrated, setHydrated] = React.useState(false);
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
perfStartMsRef.current = performance.now();
|
|
18
|
+
setHydrated(true);
|
|
19
|
+
const alignDelay = 1000 - (Date.now() % 1000);
|
|
20
|
+
let intervalId = null;
|
|
21
|
+
const timeoutId = window.setTimeout(() => {
|
|
22
|
+
setTick((x) => x + 1);
|
|
23
|
+
intervalId = window.setInterval(() => setTick((x) => x + 1), 1000);
|
|
24
|
+
}, alignDelay);
|
|
25
|
+
return () => {
|
|
26
|
+
window.clearTimeout(timeoutId);
|
|
27
|
+
if (intervalId)
|
|
28
|
+
window.clearInterval(intervalId);
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
const nowMs = React.useCallback(() => {
|
|
32
|
+
if (!hydrated)
|
|
33
|
+
return serverStartMsRef.current;
|
|
34
|
+
const delta = performance.now() - perfStartMsRef.current;
|
|
35
|
+
return serverStartMsRef.current + delta;
|
|
36
|
+
}, [hydrated]);
|
|
37
|
+
const value = React.useMemo(() => ({
|
|
38
|
+
serverStartMs: serverStartMsRef.current,
|
|
39
|
+
perfStartMs: perfStartMsRef.current,
|
|
40
|
+
tick,
|
|
41
|
+
nowMs
|
|
42
|
+
}), [tick, nowMs]);
|
|
43
|
+
return _jsx(SgTimeContext.Provider, { value: value, children: children });
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SgClockTheme } from "./types";
|
|
2
|
+
export type SgClockThemePickerProps = {
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (id: string) => void;
|
|
5
|
+
label?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
filter?: (theme: SgClockTheme) => boolean;
|
|
9
|
+
previewSize?: number;
|
|
10
|
+
searchable?: boolean;
|
|
11
|
+
fallbackThemeId?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function SgClockThemePicker({ value, onChange, label, placeholder, className, filter, previewSize, searchable, fallbackThemeId }: SgClockThemePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=SgClockThemePicker.d.ts.map
|