@seedgrid/fe-components 2026.11.4 → 2026.11.6
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/ai/seedgrid-components.manifest.json +1475 -11
- package/dist/buttons/SgButton.d.ts +41 -3
- package/dist/buttons/SgButton.d.ts.map +1 -1
- package/dist/buttons/SgButton.js +51 -5
- package/dist/buttons/SgButton.meta.d.ts.map +1 -1
- package/dist/buttons/SgButton.meta.js +27 -5
- package/dist/digits/fade-digit/SgFadeDigit.d.ts.map +1 -1
- package/dist/digits/fade-digit/SgFadeDigit.js +4 -1
- package/dist/gadgets/clock/SgClock.d.ts +25 -1
- package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
- package/dist/gadgets/clock/SgClock.js +18 -3
- package/dist/gadgets/clock/SgClock.meta.d.ts.map +1 -1
- package/dist/gadgets/clock/SgClock.meta.js +2 -1
- package/dist/generated/compiledStyles.d.ts +1 -1
- package/dist/generated/compiledStyles.d.ts.map +1 -1
- package/dist/generated/compiledStyles.js +1 -1
- package/dist/i18n/SgComponentsI18nProvider.meta.d.ts.map +1 -1
- package/dist/i18n/SgComponentsI18nProvider.meta.js +6 -3
- package/dist/i18n/useComponentsI18n.meta.d.ts.map +1 -1
- package/dist/i18n/useComponentsI18n.meta.js +8 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs/SgMultiSelect.d.ts.map +1 -1
- package/dist/inputs/SgMultiSelect.js +4 -2
- package/dist/inputs/SgMultiSelectChips.d.ts.map +1 -1
- package/dist/inputs/SgMultiSelectChips.js +4 -2
- package/dist/layout/SgPageControl.d.ts +1 -0
- package/dist/layout/SgPageControl.d.ts.map +1 -1
- package/dist/layout/SgPageControl.js +3 -1
- package/dist/layout/SgToolBar.d.ts +6 -0
- package/dist/layout/SgToolBar.d.ts.map +1 -1
- package/dist/layout/SgToolBar.js +107 -5
- package/dist/layout/SgToolBar.meta.d.ts.map +1 -1
- package/dist/layout/SgToolBar.meta.js +8 -0
- package/dist/layout/toolbar-logic.d.ts +11 -0
- package/dist/layout/toolbar-logic.d.ts.map +1 -1
- package/dist/layout/toolbar-logic.js +25 -0
- package/dist/sandbox.cjs +58 -58
- package/dist/style.css +2 -2
- package/dist/wizard/SgWizard.js +1 -1
- package/package.json +3 -3
|
@@ -14,7 +14,24 @@ export type SgButtonCustomColors = {
|
|
|
14
14
|
activeBg?: string;
|
|
15
15
|
ring?: string;
|
|
16
16
|
};
|
|
17
|
-
export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color" | "disabled"> & {
|
|
17
|
+
export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color" | "disabled" | "onClick"> & {
|
|
18
|
+
/**
|
|
19
|
+
* Clique do botão. Pode ser síncrono ou assíncrono (`(event) => unknown`).
|
|
20
|
+
*
|
|
21
|
+
* - Síncrono (abrir modal, alternar painel): funciona como sempre, nada muda.
|
|
22
|
+
* - Se DEVOLVER uma Promise (`async () => { await salvar(); }` ou `() => salvar()`), o botão entra em loading
|
|
23
|
+
* sozinho (spinner, desabilitado, aria-busy), ignora cliques até terminar (inclusive cliques seguidos antes do
|
|
24
|
+
* re-render) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na
|
|
25
|
+
* idempotência da ação na tela (não substitui a chave de idempotência no servidor).
|
|
26
|
+
* - Cuidado: precisa DEVOLVER a Promise. `onClick={() => { salvar(); }}` (chaves sem return) NÃO protege.
|
|
27
|
+
* - Erro: o botão não engole o erro (sai do loading e relança como rejeição não tratada). Trate com try/catch
|
|
28
|
+
* dentro do onClick e avise o usuário (ex.: `sgWhistle.error`).
|
|
29
|
+
* - Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar); ações instantâneas ficam síncronas.
|
|
30
|
+
* - A prop `loading` manual continua valendo e vence (loading efetivo = `loading` || Promise pendente).
|
|
31
|
+
*
|
|
32
|
+
* O retorno é `unknown` só para não quebrar handlers existentes de corpo-expressão (`() => lista.push(x)`).
|
|
33
|
+
*/
|
|
34
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => unknown;
|
|
18
35
|
/** Quando false, desabilita o botão. Padrão: true. */
|
|
19
36
|
enabled?: boolean;
|
|
20
37
|
severity?: Severity;
|
|
@@ -26,11 +43,31 @@ export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
|
26
43
|
iconOnly?: boolean;
|
|
27
44
|
leftIcon?: React.ReactNode;
|
|
28
45
|
rightIcon?: React.ReactNode;
|
|
46
|
+
/** Exibe spinner e bloqueia o clique. Vence o loading automático do onClick: loading efetivo = `loading` || Promise pendente. */
|
|
29
47
|
loading?: boolean;
|
|
30
48
|
customColors?: SgButtonCustomColors;
|
|
31
49
|
};
|
|
32
|
-
export declare function resolveButtonColors(severity: Severity, custom?: SgButtonCustomColors): Required<SgButtonCustomColors
|
|
33
|
-
|
|
50
|
+
export declare function resolveButtonColors(severity: Severity, custom?: SgButtonCustomColors): Required<SgButtonCustomColors> & {
|
|
51
|
+
activeFg: string;
|
|
52
|
+
};
|
|
53
|
+
export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "disabled" | "color" | "onClick"> & {
|
|
54
|
+
/**
|
|
55
|
+
* Clique do botão. Pode ser síncrono ou assíncrono (`(event) => unknown`).
|
|
56
|
+
*
|
|
57
|
+
* - Síncrono (abrir modal, alternar painel): funciona como sempre, nada muda.
|
|
58
|
+
* - Se DEVOLVER uma Promise (`async () => { await salvar(); }` ou `() => salvar()`), o botão entra em loading
|
|
59
|
+
* sozinho (spinner, desabilitado, aria-busy), ignora cliques até terminar (inclusive cliques seguidos antes do
|
|
60
|
+
* re-render) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na
|
|
61
|
+
* idempotência da ação na tela (não substitui a chave de idempotência no servidor).
|
|
62
|
+
* - Cuidado: precisa DEVOLVER a Promise. `onClick={() => { salvar(); }}` (chaves sem return) NÃO protege.
|
|
63
|
+
* - Erro: o botão não engole o erro (sai do loading e relança como rejeição não tratada). Trate com try/catch
|
|
64
|
+
* dentro do onClick e avise o usuário (ex.: `sgWhistle.error`).
|
|
65
|
+
* - Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar); ações instantâneas ficam síncronas.
|
|
66
|
+
* - A prop `loading` manual continua valendo e vence (loading efetivo = `loading` || Promise pendente).
|
|
67
|
+
*
|
|
68
|
+
* O retorno é `unknown` só para não quebrar handlers existentes de corpo-expressão (`() => lista.push(x)`).
|
|
69
|
+
*/
|
|
70
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => unknown;
|
|
34
71
|
/** Quando false, desabilita o botão. Padrão: true. */
|
|
35
72
|
enabled?: boolean;
|
|
36
73
|
severity?: Severity;
|
|
@@ -42,6 +79,7 @@ export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.Button
|
|
|
42
79
|
iconOnly?: boolean;
|
|
43
80
|
leftIcon?: React.ReactNode;
|
|
44
81
|
rightIcon?: React.ReactNode;
|
|
82
|
+
/** Exibe spinner e bloqueia o clique. Vence o loading automático do onClick: loading efetivo = `loading` || Promise pendente. */
|
|
45
83
|
loading?: boolean;
|
|
46
84
|
customColors?: SgButtonCustomColors;
|
|
47
85
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgButton.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,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,GAAG,QAAQ,CAAC;AAEzD,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;
|
|
1
|
+
{"version":3,"file":"SgButton.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,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,GAAG,QAAQ,CAAC;AAEzD,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC,GAAG;IAClH;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC;IAClE,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,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,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,iIAAiI;IACjI,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAoFF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,oBAAoB,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcvD;AAqDD,eAAO,MAAM,QAAQ;IAzLnB;;;;;;;;;;;;;;;OAeG;cACO,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,OAAO;IACjE,sDAAsD;cAC5C,OAAO;eACN,QAAQ;iBACN,UAAU;WAChB,IAAI;YACH,KAAK;gBACD,SAAS;YACb,KAAK,CAAC,SAAS;eACZ,OAAO;eACP,KAAK,CAAC,SAAS;gBACd,KAAK,CAAC,SAAS;IAC3B,iIAAiI;cACvH,OAAO;mBACF,oBAAoB;2CAsQpC,CAAC"}
|
package/dist/buttons/SgButton.js
CHANGED
|
@@ -10,6 +10,8 @@ const PRESET = {
|
|
|
10
10
|
fg: "var(--sg-btn-primary-fg, hsl(var(--primary-foreground)))",
|
|
11
11
|
border: "var(--sg-btn-primary-border, hsl(var(--primary)))",
|
|
12
12
|
hoverBg: "var(--sg-btn-primary-hover-bg, hsl(var(--primary)))",
|
|
13
|
+
hoverFg: "var(--sg-btn-primary-hover-fg, var(--sg-btn-primary-fg, hsl(var(--primary-foreground))))",
|
|
14
|
+
activeFg: "var(--sg-btn-primary-active-fg, var(--sg-btn-primary-fg, hsl(var(--primary-foreground))))",
|
|
13
15
|
ring: "var(--sg-btn-primary-ring, hsl(var(--primary)/0.35))"
|
|
14
16
|
},
|
|
15
17
|
secondary: {
|
|
@@ -17,6 +19,8 @@ const PRESET = {
|
|
|
17
19
|
fg: "var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground)))",
|
|
18
20
|
border: "var(--sg-btn-secondary-border, hsl(var(--secondary)))",
|
|
19
21
|
hoverBg: "var(--sg-btn-secondary-hover-bg, hsl(var(--secondary)))",
|
|
22
|
+
hoverFg: "var(--sg-btn-secondary-hover-fg, var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground))))",
|
|
23
|
+
activeFg: "var(--sg-btn-secondary-active-fg, var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground))))",
|
|
20
24
|
ring: "var(--sg-btn-secondary-ring, hsl(var(--secondary)/0.35))"
|
|
21
25
|
},
|
|
22
26
|
success: {
|
|
@@ -24,6 +28,8 @@ const PRESET = {
|
|
|
24
28
|
fg: "var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%))))",
|
|
25
29
|
border: "var(--sg-btn-success-border, hsl(var(--tertiary, var(--accent))))",
|
|
26
30
|
hoverBg: "var(--sg-btn-success-hover-bg, hsl(var(--tertiary, var(--accent))))",
|
|
31
|
+
hoverFg: "var(--sg-btn-success-hover-fg, var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%)))))",
|
|
32
|
+
activeFg: "var(--sg-btn-success-active-fg, var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%)))))",
|
|
27
33
|
ring: "var(--sg-btn-success-ring, hsl(var(--tertiary, var(--accent))/0.35))"
|
|
28
34
|
},
|
|
29
35
|
info: {
|
|
@@ -31,6 +37,8 @@ const PRESET = {
|
|
|
31
37
|
fg: "var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground))))",
|
|
32
38
|
border: "var(--sg-btn-info-border, hsl(var(--secondary, var(--primary))))",
|
|
33
39
|
hoverBg: "var(--sg-btn-info-hover-bg, hsl(var(--secondary, var(--primary))))",
|
|
40
|
+
hoverFg: "var(--sg-btn-info-hover-fg, var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
|
|
41
|
+
activeFg: "var(--sg-btn-info-active-fg, var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
|
|
34
42
|
ring: "var(--sg-btn-info-ring, hsl(var(--secondary, var(--primary))/0.35))"
|
|
35
43
|
},
|
|
36
44
|
warning: {
|
|
@@ -38,6 +46,8 @@ const PRESET = {
|
|
|
38
46
|
fg: "var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%))))",
|
|
39
47
|
border: "var(--sg-btn-warning-border, hsl(var(--secondary, var(--accent))))",
|
|
40
48
|
hoverBg: "var(--sg-btn-warning-hover-bg, hsl(var(--secondary, var(--accent))))",
|
|
49
|
+
hoverFg: "var(--sg-btn-warning-hover-fg, var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%)))))",
|
|
50
|
+
activeFg: "var(--sg-btn-warning-active-fg, var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%)))))",
|
|
41
51
|
ring: "var(--sg-btn-warning-ring, hsl(var(--secondary, var(--accent))/0.35))"
|
|
42
52
|
},
|
|
43
53
|
help: {
|
|
@@ -45,6 +55,8 @@ const PRESET = {
|
|
|
45
55
|
fg: "var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground))))",
|
|
46
56
|
border: "var(--sg-btn-help-border, hsl(var(--secondary, var(--primary))))",
|
|
47
57
|
hoverBg: "var(--sg-btn-help-hover-bg, hsl(var(--secondary, var(--primary))))",
|
|
58
|
+
hoverFg: "var(--sg-btn-help-hover-fg, var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
|
|
59
|
+
activeFg: "var(--sg-btn-help-active-fg, var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
|
|
48
60
|
ring: "var(--sg-btn-help-ring, hsl(var(--secondary, var(--primary))/0.35))"
|
|
49
61
|
},
|
|
50
62
|
danger: {
|
|
@@ -52,6 +64,8 @@ const PRESET = {
|
|
|
52
64
|
fg: "var(--sg-btn-danger-fg, hsl(var(--destructive-foreground)))",
|
|
53
65
|
border: "var(--sg-btn-danger-border, hsl(var(--destructive)))",
|
|
54
66
|
hoverBg: "var(--sg-btn-danger-hover-bg, hsl(var(--destructive)))",
|
|
67
|
+
hoverFg: "var(--sg-btn-danger-hover-fg, var(--sg-btn-danger-fg, hsl(var(--destructive-foreground))))",
|
|
68
|
+
activeFg: "var(--sg-btn-danger-active-fg, var(--sg-btn-danger-fg, hsl(var(--destructive-foreground))))",
|
|
55
69
|
ring: "var(--sg-btn-danger-ring, hsl(var(--destructive)/0.35))"
|
|
56
70
|
},
|
|
57
71
|
};
|
|
@@ -67,9 +81,11 @@ export function resolveButtonColors(severity, custom) {
|
|
|
67
81
|
fg: custom?.fg ?? base.fg,
|
|
68
82
|
border: custom?.border ?? base.border,
|
|
69
83
|
hoverBg: custom?.hoverBg ?? base.hoverBg,
|
|
70
|
-
|
|
84
|
+
// Sem cor customizada, cada estado usa o texto escolhido por contraste sobre o proprio fundo (tokens *-hover-fg/*-active-fg).
|
|
85
|
+
hoverFg: custom?.hoverFg ?? custom?.fg ?? base.hoverFg,
|
|
71
86
|
hoverBorder: custom?.hoverBorder ?? (custom?.border ?? base.border),
|
|
72
87
|
activeBg: custom?.activeBg ?? (custom?.hoverBg ?? base.hoverBg),
|
|
88
|
+
activeFg: custom?.hoverFg ?? custom?.fg ?? base.activeFg,
|
|
73
89
|
ring: custom?.ring ?? base.ring
|
|
74
90
|
};
|
|
75
91
|
}
|
|
@@ -84,6 +100,7 @@ function buildVars(severity, custom) {
|
|
|
84
100
|
["--sg-btn-hover-fg"]: merged.hoverFg,
|
|
85
101
|
["--sg-btn-hover-border"]: merged.hoverBorder,
|
|
86
102
|
["--sg-btn-active-bg"]: merged.activeBg,
|
|
103
|
+
["--sg-btn-active-fg"]: merged.activeFg,
|
|
87
104
|
["--sg-btn-ring"]: merged.ring,
|
|
88
105
|
["--sg-btn-tint"]: `var(--sg-${toneKey}-600)`
|
|
89
106
|
};
|
|
@@ -91,7 +108,7 @@ function buildVars(severity, custom) {
|
|
|
91
108
|
function appearanceClass(appearance) {
|
|
92
109
|
switch (appearance) {
|
|
93
110
|
case "solid":
|
|
94
|
-
return cn("bg-[var(--sg-btn-bg)] text-[var(--sg-btn-fg)] border border-[var(--sg-btn-border)]", "hover:bg-[var(--sg-btn-hover-bg)] hover:text-[var(--sg-btn-hover-fg)] hover:border-[var(--sg-btn-hover-border)]", "active:bg-[var(--sg-btn-active-bg)]");
|
|
111
|
+
return cn("bg-[var(--sg-btn-bg)] text-[var(--sg-btn-fg)] border border-[var(--sg-btn-border)]", "hover:bg-[var(--sg-btn-hover-bg)] hover:text-[var(--sg-btn-hover-fg)] hover:border-[var(--sg-btn-hover-border)]", "active:bg-[var(--sg-btn-active-bg)] active:text-[var(--sg-btn-active-fg)]");
|
|
95
112
|
case "outline":
|
|
96
113
|
return cn("bg-transparent text-[var(--sg-btn-bg)] border border-[var(--sg-btn-border)]", "hover:bg-[rgb(var(--sg-btn-tint)/0.12)]", "active:bg-[rgb(var(--sg-btn-tint)/0.18)]");
|
|
97
114
|
case "ghost":
|
|
@@ -108,16 +125,45 @@ function elevationClass(elevation) {
|
|
|
108
125
|
return "";
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
|
-
export const SgButton = React.forwardRef(({ severity = "primary", appearance = "solid", size, shape, elevation = "none", label, iconOnly, leftIcon, rightIcon, loading = false, enabled = true, className, children, style, customColors, type = "button", ...rest }, ref) => {
|
|
128
|
+
export const SgButton = React.forwardRef(({ severity = "primary", appearance = "solid", size, shape, elevation = "none", label, iconOnly, leftIcon, rightIcon, loading = false, enabled = true, className, children, style, customColors, type = "button", onClick, ...rest }, ref) => {
|
|
129
|
+
const [pending, setPending] = React.useState(false);
|
|
130
|
+
const pendingRef = React.useRef(false);
|
|
131
|
+
const mountedRef = React.useRef(true);
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
mountedRef.current = true;
|
|
134
|
+
return () => {
|
|
135
|
+
mountedRef.current = false;
|
|
136
|
+
};
|
|
137
|
+
}, []);
|
|
138
|
+
const isLoading = loading || pending;
|
|
139
|
+
const handleClick = React.useCallback((event) => {
|
|
140
|
+
if (!onClick || !enabled || loading || pendingRef.current)
|
|
141
|
+
return;
|
|
142
|
+
const result = onClick(event);
|
|
143
|
+
if (!result || typeof result.then !== "function")
|
|
144
|
+
return;
|
|
145
|
+
pendingRef.current = true;
|
|
146
|
+
setPending(true);
|
|
147
|
+
const done = () => {
|
|
148
|
+
pendingRef.current = false;
|
|
149
|
+
if (mountedRef.current)
|
|
150
|
+
setPending(false);
|
|
151
|
+
};
|
|
152
|
+
// O erro da Promise nao e engolido: segue como rejeicao sem tratamento, como antes.
|
|
153
|
+
void result.then(done, (error) => {
|
|
154
|
+
done();
|
|
155
|
+
throw error;
|
|
156
|
+
});
|
|
157
|
+
}, [onClick, enabled, loading]);
|
|
112
158
|
const resolvedChildren = children ?? label;
|
|
113
|
-
const isDisabled = Boolean(!enabled ||
|
|
159
|
+
const isDisabled = Boolean(!enabled || isLoading);
|
|
114
160
|
const inferredIconOnly = !resolvedChildren && (leftIcon || rightIcon);
|
|
115
161
|
const isIconOnly = iconOnly ?? inferredIconOnly;
|
|
116
162
|
const resolvedSize = size ?? (isIconOnly ? "sm" : "md");
|
|
117
163
|
const resolvedShape = shape ?? (isIconOnly ? "circle" : "default");
|
|
118
164
|
const s = SIZE[resolvedSize];
|
|
119
165
|
const contentClass = cn("inline-flex min-w-0 items-center justify-center", !isIconOnly ? "w-full" : undefined, s.gap);
|
|
120
|
-
return (_jsx("button", { ref: ref, type: type, disabled: isDisabled, style: { ...buildVars(severity, customColors), ...style }, className: cn("inline-flex min-w-0 max-w-full items-center justify-center select-none whitespace-normal break-words text-center", "font-medium", "transition-[background-color,color,border-color,box-shadow,transform] duration-150", "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[var(--sg-btn-ring)]", "disabled:opacity-60 disabled:cursor-not-allowed", isIconOnly ? cn("aspect-square", s.h) : cn(s.minH, s.py, s.px), s.text, resolvedShape === "rounded" ? "rounded-full" : resolvedShape === "circle" ? "rounded-full" : resolvedShape === "square" ? "rounded-none" : s.radius, appearanceClass(appearance), elevationClass(elevation), !isDisabled ? "active:translate-y-[0.5px]" : false, className), ...rest, children:
|
|
166
|
+
return (_jsx("button", { ref: ref, type: type, disabled: isDisabled, "aria-busy": isLoading ? true : undefined, style: { ...buildVars(severity, customColors), ...style }, className: cn("inline-flex min-w-0 max-w-full items-center justify-center select-none whitespace-normal break-words text-center", "font-medium", "transition-[background-color,color,border-color,box-shadow,transform] duration-150", "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[var(--sg-btn-ring)]", "disabled:opacity-60 disabled:cursor-not-allowed", isIconOnly ? cn("aspect-square", s.h) : cn(s.minH, s.py, s.px), s.text, resolvedShape === "rounded" ? "rounded-full" : resolvedShape === "circle" ? "rounded-full" : resolvedShape === "square" ? "rounded-none" : s.radius, appearanceClass(appearance), elevationClass(elevation), !isDisabled ? "active:translate-y-[0.5px]" : false, className), ...rest, onClick: onClick ? handleClick : undefined, children: isLoading ? (_jsxs("span", { className: contentClass, children: [_jsx(Spinner, { className: s.icon }), resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null] })) : (_jsxs("span", { className: contentClass, children: [leftIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: leftIcon }) : null, resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null, rightIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: rightIcon }) : null] })) }));
|
|
121
167
|
});
|
|
122
168
|
SgButton.displayName = "SgButton";
|
|
123
169
|
function Spinner({ className }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgButton.meta.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"SgButton.meta.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAoIpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WA6BrB,CAAC"}
|
|
@@ -47,7 +47,7 @@ export const sgMeta = {
|
|
|
47
47
|
name: "loading",
|
|
48
48
|
type: "boolean",
|
|
49
49
|
default: false,
|
|
50
|
-
description: "Exibe spinner e bloqueia
|
|
50
|
+
description: "Exibe spinner e bloqueia o clique. Continua valendo e vence o loading automático do onClick: o loading efetivo é loading ou operação pendente (Promise/Future devolvida pelo onClick).",
|
|
51
51
|
semanticRole: "behavior",
|
|
52
52
|
bindable: true
|
|
53
53
|
},
|
|
@@ -75,8 +75,8 @@ export const sgMeta = {
|
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
name: "onClick",
|
|
78
|
-
type: "
|
|
79
|
-
description: "
|
|
78
|
+
type: "(event: MouseEvent<HTMLButtonElement>) => unknown",
|
|
79
|
+
description: "Clique do botão; pode ser síncrono ou assíncrono. Síncrono (abrir modal, alternar): nada muda. Se DEVOLVER uma Promise/Future, como async () => { await salvar(); } ou () => salvar(), o botão entra em loading sozinho (spinner, desabilitado, aria-busy na web), ignora cliques até terminar (inclusive cliques seguidos antes de a tela atualizar) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na idempotência da ação na tela (não substitui a chave de idempotência no servidor). Cuidado: a função precisa devolver a operação; com chaves e sem return (ou sem async), como () => { salvar(); } ou () { salvar(); }, não protege; já async () {...} sempre devolve. O erro não é engolido: o botão sai do loading e relança como rejeição não tratada; trate com try/catch dentro do onClick e avise o usuário (sgWhistle.error). Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar).",
|
|
80
80
|
semanticRole: "event",
|
|
81
81
|
bindable: false
|
|
82
82
|
}
|
|
@@ -89,6 +89,24 @@ export const sgMeta = {
|
|
|
89
89
|
file: "apps/showcase/src/app/components/sg-button/samples/basico.tsx.sample",
|
|
90
90
|
kind: "sample"
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
id: "auto-loading",
|
|
94
|
+
title: "Loading automático com Future",
|
|
95
|
+
file: "apps/showcase/src/app/components/sg-button/samples/loading-automatico-com-future.tsx.sample",
|
|
96
|
+
kind: "sample"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "sync-vs-async",
|
|
100
|
+
title: "onClick síncrono x assíncrono",
|
|
101
|
+
file: "apps/showcase/src/app/components/sg-button/samples/onclick-sincrono-x-assincrono.tsx.sample",
|
|
102
|
+
kind: "sample"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "async-error",
|
|
106
|
+
title: "Tratando erro no onClick assíncrono",
|
|
107
|
+
file: "apps/showcase/src/app/components/sg-button/samples/tratando-erro-no-onclick-assincrono.tsx.sample",
|
|
108
|
+
kind: "sample"
|
|
109
|
+
},
|
|
92
110
|
{
|
|
93
111
|
id: "playground",
|
|
94
112
|
title: "Playground",
|
|
@@ -116,7 +134,8 @@ export const aiHints = {
|
|
|
116
134
|
preferredUseCases: [
|
|
117
135
|
"Acoes primarias e secundarias em formularios e telas CRUD.",
|
|
118
136
|
"Submit, confirmacao, salvar, cancelar e outras acoes explicitas do usuario.",
|
|
119
|
-
"Chamadas para acao com feedback visual de loading."
|
|
137
|
+
"Chamadas para acao com feedback visual de loading.",
|
|
138
|
+
"Salvar, enviar ou pagar: use onClick assíncrono (async () => { await salvar(); } ou () => salvar()) para o botão entrar em loading sozinho e ignorar duplo clique; acoes instantaneas (abrir modal, alternar) ficam sincronas."
|
|
120
139
|
],
|
|
121
140
|
avoidUseCases: [
|
|
122
141
|
"Agrupar conteudo ou estruturar layout; nesses casos prefira SgPanel, SgCard ou SgStack.",
|
|
@@ -127,7 +146,10 @@ export const aiHints = {
|
|
|
127
146
|
relatedEntityFields: ["submit", "save", "cancel", "confirm", "delete"],
|
|
128
147
|
compositionHints: [
|
|
129
148
|
"Combinar com SgInputText e outros campos em formularios.",
|
|
130
|
-
"Usar dentro de SgPanel ou SgDialog para acoes contextuais."
|
|
149
|
+
"Usar dentro de SgPanel ou SgDialog para acoes contextuais.",
|
|
150
|
+
"O onClick precisa DEVOLVER a Promise/Future para o loading automatico funcionar: onClick={() => { salvar(); }} (chaves sem return) nao protege; use () => salvar() ou async () => { await salvar(); }. No Flutter, async () {...} sempre devolve Future; () { salvar(); } nao.",
|
|
151
|
+
"O botao nao engole o erro do onClick assincrono: sai do loading e relanca como rejeicao nao tratada. Trate com try/catch dentro do onClick e avise o usuario com sgWhistle.error (Flutter: sgWhistle.error(SgWhistleOptions(...))).",
|
|
152
|
+
"O loading automatico protege a acao na tela (duplo clique/reenvio), mas nao substitui a chave de idempotencia no servidor. A prop loading manual vence: loading efetivo = loading ou operacao pendente."
|
|
131
153
|
],
|
|
132
154
|
rankingSignals: {
|
|
133
155
|
freeText: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgFadeDigit.d.ts","sourceRoot":"","sources":["../../../src/digits/fade-digit/SgFadeDigit.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAiB,EACjB,eAA2B,EAC3B,IAAI,EACJ,QAAa,EACb,SAAS,GACV,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"SgFadeDigit.d.ts","sourceRoot":"","sources":["../../../src/digits/fade-digit/SgFadeDigit.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAiB,EACjB,eAA2B,EAC3B,IAAI,EACJ,QAAa,EACb,SAAS,GACV,EAAE,gBAAgB,2CA6GlB"}
|
|
@@ -21,8 +21,11 @@ export function SgFadeDigit({ value, color = "#edebeb", backgroundColor = "#3332
|
|
|
21
21
|
const [opacity, setOpacity] = React.useState(1);
|
|
22
22
|
React.useEffect(() => {
|
|
23
23
|
// Nothing to do — already showing the right value
|
|
24
|
-
|
|
24
|
+
// (and restore opacity: a fade-out may have started for a value that came back to the current one)
|
|
25
|
+
if (value === displayValue) {
|
|
26
|
+
setOpacity(1);
|
|
25
27
|
return;
|
|
28
|
+
}
|
|
26
29
|
// Phase 1: fade out
|
|
27
30
|
setOpacity(0);
|
|
28
31
|
// Phase 2: after fade-out completes, swap text and fade in
|
|
@@ -10,6 +10,8 @@ export type SgClockProps = {
|
|
|
10
10
|
format?: "12h" | "24h";
|
|
11
11
|
showSeconds?: boolean;
|
|
12
12
|
digitalStyle?: SgClockDigitalStyle;
|
|
13
|
+
/** Desenha o relogio digital quando `digitalStyle` e `"custom"`. Sem ele, cai no estilo `default`. */
|
|
14
|
+
digitBuilder?: SgClockDigitBuilder;
|
|
13
15
|
secondHandMode?: "step" | "smooth";
|
|
14
16
|
themeId?: string;
|
|
15
17
|
theme?: SgClockTheme;
|
|
@@ -19,6 +21,28 @@ export type SgClockProps = {
|
|
|
19
21
|
cardTitle?: React.ReactNode;
|
|
20
22
|
cardProps?: Omit<SgCardProps, "children" | "title">;
|
|
21
23
|
};
|
|
22
|
-
export type SgClockDigitalStyle = "default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard";
|
|
24
|
+
export type SgClockDigitalStyle = "default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard" | "custom";
|
|
25
|
+
/** Partes do relogio digital entregues ao `digitBuilder` (as mesmas do construtor do Flutter). */
|
|
26
|
+
export type SgClockDigitalParts = {
|
|
27
|
+
/** Sempre com 2 digitos; em 12h vai de 01 a 12. */
|
|
28
|
+
hour: string;
|
|
29
|
+
minute: string;
|
|
30
|
+
/** Vazio quando `showSeconds` e falso. */
|
|
31
|
+
second: string;
|
|
32
|
+
/** So em 12h (`AM`/`PM` ou o que o Intl do locale devolve). */
|
|
33
|
+
dayPeriod: string | undefined;
|
|
34
|
+
is12h: boolean;
|
|
35
|
+
showSeconds: boolean;
|
|
36
|
+
/** `size` original do SgClock (`"sm"`, `"md"`, `"lg"` ou numero). */
|
|
37
|
+
size: "sm" | "md" | "lg" | number;
|
|
38
|
+
/** `HH:MM` ou `HH:MM:SS`. */
|
|
39
|
+
text: string;
|
|
40
|
+
/** `text` + periodo em maiusculas. */
|
|
41
|
+
textWithPeriod: string;
|
|
42
|
+
/** Numero = ele mesmo, sm 12, lg 28, md 16. */
|
|
43
|
+
sizePx: number;
|
|
44
|
+
};
|
|
45
|
+
/** Recebe as partes do relogio digital e devolve o desenho completo (digitos e separadores). */
|
|
46
|
+
export type SgClockDigitBuilder = (parts: SgClockDigitalParts) => React.ReactNode;
|
|
23
47
|
export declare function SgClock(props: SgClockProps): import("react/jsx-runtime").JSX.Element;
|
|
24
48
|
//# sourceMappingURL=SgClock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgClock.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAUnD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2C/D,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,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,mBAAmB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,cAAc,GACd,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"SgClock.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAUnD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2C/D,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,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,mBAAmB,CAAC;IACnC,sGAAsG;IACtG,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,cAAc,GACd,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,GACT,QAAQ,CAAC;AAEb,kGAAkG;AAClG,MAAM,MAAM,mBAAmB,GAAG;IAChC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAClC,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,gGAAgG;AAChG,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;AA4hBlF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAwG1C"}
|
|
@@ -110,7 +110,7 @@ function AnalogClock({ size = 280, initialServerTime, timezone, locale = "pt-BR"
|
|
|
110
110
|
(resolver ? resolveTheme(resolver, themeId, "classic") : getTheme(themeId) ?? getTheme("classic"));
|
|
111
111
|
return (_jsx("div", { className: className, style: style, children: _jsxs("svg", { width: size, height: size, viewBox: "0 0 100 100", className: cn("block", dark ? "dark" : undefined), "aria-label": t(i18n, "components.gadgets.clock.analogAria"), 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] }) }));
|
|
112
112
|
}
|
|
113
|
-
function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, size = "md", digitalStyle = "default", className, style }) {
|
|
113
|
+
function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, size = "md", digitalStyle = "default", digitBuilder, className, style }) {
|
|
114
114
|
const i18n = useComponentsI18n();
|
|
115
115
|
const { nowMs, hasProvider, providerTick } = useClockNowMs(initialServerTime);
|
|
116
116
|
void providerTick;
|
|
@@ -137,6 +137,21 @@ function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format =
|
|
|
137
137
|
const fontSize = typeof size === "number" ? { fontSize: `${size}px`, lineHeight: 1 } : undefined;
|
|
138
138
|
const sizePx = digitalSizeToNumber(size);
|
|
139
139
|
const withPeriod = format === "12h" && dayPeriod ? `${text} ${dayPeriod.toUpperCase()}` : text;
|
|
140
|
+
if (digitalStyle === "custom" && digitBuilder) {
|
|
141
|
+
const is12h = format === "12h";
|
|
142
|
+
return (_jsx("div", { className: className, style: style, "aria-label": t(i18n, "components.gadgets.clock.digitalAria"), children: digitBuilder({
|
|
143
|
+
hour,
|
|
144
|
+
minute,
|
|
145
|
+
second: showSeconds ? second : "",
|
|
146
|
+
dayPeriod: is12h && dayPeriod ? dayPeriod : undefined,
|
|
147
|
+
is12h,
|
|
148
|
+
showSeconds,
|
|
149
|
+
size,
|
|
150
|
+
text,
|
|
151
|
+
textWithPeriod: withPeriod,
|
|
152
|
+
sizePx
|
|
153
|
+
}) }));
|
|
154
|
+
}
|
|
140
155
|
if (digitalStyle === "flip") {
|
|
141
156
|
const hNum = Number.parseInt(hour, 10) || 0;
|
|
142
157
|
const mNum = Number.parseInt(minute, 10) || 0;
|
|
@@ -275,7 +290,7 @@ function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format =
|
|
|
275
290
|
}
|
|
276
291
|
export function SgClock(props) {
|
|
277
292
|
const i18n = useComponentsI18n();
|
|
278
|
-
const { clockStyle = "digital", size = "md", initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, digitalStyle = "default", secondHandMode = "step", themeId = "classic", theme, className, style, centerOverlay, cardTitle, cardProps } = props;
|
|
293
|
+
const { clockStyle = "digital", size = "md", initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, digitalStyle = "default", digitBuilder, secondHandMode = "step", themeId = "classic", theme, className, style, centerOverlay, cardTitle, cardProps } = props;
|
|
279
294
|
const resolvedCardTitle = cardTitle ?? t(i18n, "components.gadgets.clock.title");
|
|
280
295
|
const resolvedDragPersistKey = React.useMemo(() => {
|
|
281
296
|
const explicitPersistKey = cardProps?.dragPersistKey;
|
|
@@ -299,6 +314,6 @@ export function SgClock(props) {
|
|
|
299
314
|
const clockNode = clockStyle === "analog" ? ((() => {
|
|
300
315
|
const analogSize = typeof size === "number" ? size : size === "sm" ? 140 : size === "lg" ? 320 : 240;
|
|
301
316
|
return (_jsx(AnalogClock, { size: analogSize, initialServerTime: initialServerTime, themeId: themeId, theme: theme, timezone: timezone, locale: locale, showSeconds: showSeconds, secondHandMode: secondHandMode, className: className, style: style, centerOverlay: centerOverlay }));
|
|
302
|
-
})()) : (_jsx(DigitalClock, { timezone: timezone, initialServerTime: initialServerTime, locale: locale, format: format, showSeconds: showSeconds, size: size, digitalStyle: digitalStyle, className: className, style: style }));
|
|
317
|
+
})()) : (_jsx(DigitalClock, { timezone: timezone, initialServerTime: initialServerTime, locale: locale, format: format, showSeconds: showSeconds, size: size, digitalStyle: digitalStyle, digitBuilder: digitBuilder, className: className, style: style }));
|
|
303
318
|
return (_jsx(SgCard, { title: resolvedCardTitle, collapsible: true, defaultOpen: true, collapseToggleAlign: "right", collapseIconSize: 20, draggable: true, bgColor: "rgb(var(--sg-surface,var(--sg-bg)))", bgColorTitle: "rgb(var(--sg-surface-2,var(--sg-surface,var(--sg-bg))))", cardStyle: "outlined", ...cardProps, className: mergedCardClassName, bodyClassName: mergedCardBodyClassName, style: mergedCardStyle, dragPersistKey: resolvedDragPersistKey, children: clockNode }));
|
|
304
319
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgClock.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"SgClock.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,MAAM,EAAE,QA+BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
|
|
@@ -13,7 +13,8 @@ export const sgMeta = {
|
|
|
13
13
|
fieldSemantics: ["clock", "timeDisplay", "timezoneClock", "digitalClock", "analogClock"],
|
|
14
14
|
props: [
|
|
15
15
|
{ name: "clockStyle", type: '"digital" | "analog"', default: "digital", description: "Modo principal de exibicao do relogio.", semanticRole: "appearance", bindable: true },
|
|
16
|
-
{ name: "digitalStyle", type: '"default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard"', default: "default", description: "Estilo visual do relogio digital.", semanticRole: "appearance", bindable: true },
|
|
16
|
+
{ name: "digitalStyle", type: '"default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard" | "custom"', default: "default", description: "Estilo visual do relogio digital.", semanticRole: "appearance", bindable: true },
|
|
17
|
+
{ name: "digitBuilder", type: "(parts: SgClockDigitalParts) => ReactNode", description: "Desenha o relogio digital quando digitalStyle e custom; sem ele, usa o estilo default.", semanticRole: "appearance", bindable: false },
|
|
17
18
|
{ name: "timezone", type: "string", description: "Timezone usada no calculo da hora exibida.", semanticRole: "behavior", bindable: true },
|
|
18
19
|
{ name: "format", type: '"12h" | "24h"', default: "24h", description: "Formato horario do relogio.", semanticRole: "behavior", bindable: true },
|
|
19
20
|
{ name: "showSeconds", type: "boolean", default: true, description: "Exibe ou oculta os segundos.", semanticRole: "appearance", bindable: true },
|