@seedgrid/fe-components 2026.11.3 → 2026.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +101 -11
  2. package/dist/buttons/SgButton.d.ts +41 -3
  3. package/dist/buttons/SgButton.d.ts.map +1 -1
  4. package/dist/buttons/SgButton.js +51 -5
  5. package/dist/buttons/SgButton.meta.d.ts.map +1 -1
  6. package/dist/buttons/SgButton.meta.js +27 -5
  7. package/dist/gadgets/calendar/SgCalendar.d.ts +6 -0
  8. package/dist/gadgets/calendar/SgCalendar.d.ts.map +1 -1
  9. package/dist/gadgets/calendar/SgCalendar.js +9 -5
  10. package/dist/gadgets/calendar/SgCalendar.meta.d.ts.map +1 -1
  11. package/dist/gadgets/calendar/SgCalendar.meta.js +1 -0
  12. package/dist/gadgets/clock/SgClock.d.ts +25 -1
  13. package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
  14. package/dist/gadgets/clock/SgClock.js +18 -3
  15. package/dist/gadgets/clock/SgClock.meta.d.ts.map +1 -1
  16. package/dist/gadgets/clock/SgClock.meta.js +2 -1
  17. package/dist/generated/compiledStyles.d.ts +1 -1
  18. package/dist/generated/compiledStyles.d.ts.map +1 -1
  19. package/dist/generated/compiledStyles.js +1 -1
  20. package/dist/i18n/SgComponentsI18nProvider.meta.d.ts.map +1 -1
  21. package/dist/i18n/SgComponentsI18nProvider.meta.js +6 -3
  22. package/dist/i18n/useComponentsI18n.meta.d.ts.map +1 -1
  23. package/dist/i18n/useComponentsI18n.meta.js +8 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/inputs/SgDatatable.d.ts +7 -0
  28. package/dist/inputs/SgDatatable.d.ts.map +1 -1
  29. package/dist/inputs/SgDatatable.js +23 -11
  30. package/dist/inputs/SgDatatable.meta.d.ts.map +1 -1
  31. package/dist/inputs/SgDatatable.meta.js +1 -0
  32. package/dist/layout/SgToolBar.d.ts +6 -0
  33. package/dist/layout/SgToolBar.d.ts.map +1 -1
  34. package/dist/layout/SgToolBar.js +107 -5
  35. package/dist/layout/SgToolBar.meta.d.ts.map +1 -1
  36. package/dist/layout/SgToolBar.meta.js +8 -0
  37. package/dist/layout/toolbar-logic.d.ts +11 -0
  38. package/dist/layout/toolbar-logic.d.ts.map +1 -1
  39. package/dist/layout/toolbar-logic.js +25 -0
  40. package/dist/sandbox.cjs +55 -55
  41. package/dist/style.css +2 -2
  42. package/dist/wizard/SgWizard.d.ts +7 -0
  43. package/dist/wizard/SgWizard.d.ts.map +1 -1
  44. package/dist/wizard/SgWizard.js +11 -2
  45. package/dist/wizard/SgWizard.meta.d.ts.map +1 -1
  46. package/dist/wizard/SgWizard.meta.js +1 -0
  47. package/package.json +3 -3
@@ -3141,6 +3141,15 @@
3141
3141
  "semanticRole": "appearance",
3142
3142
  "bindable": true
3143
3143
  },
3144
+ {
3145
+ "name": "enabled",
3146
+ "type": "boolean",
3147
+ "required": false,
3148
+ "default": true,
3149
+ "description": "Quando false, bloqueia as acoes que mudam estado: ordenacao, selecao de linhas, filtros (global e por coluna), limpar filtros e gerenciador de colunas. Paginacao, rolagem e carga de mais linhas continuam funcionando. Aparencia de desabilitado e aria-disabled.",
3150
+ "semanticRole": "behavior",
3151
+ "bindable": true
3152
+ },
3144
3153
  {
3145
3154
  "name": "mobileMode",
3146
3155
  "type": "\"scroll\" | \"cards\"",
@@ -10484,7 +10493,7 @@
10484
10493
  "type": "boolean",
10485
10494
  "required": false,
10486
10495
  "default": false,
10487
- "description": "Exibe spinner e bloqueia interacao enquanto a acao esta em progresso.",
10496
+ "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).",
10488
10497
  "semanticRole": "behavior",
10489
10498
  "bindable": true
10490
10499
  },
@@ -10515,9 +10524,9 @@
10515
10524
  },
10516
10525
  {
10517
10526
  "name": "onClick",
10518
- "type": "MouseEventHandler<HTMLButtonElement>",
10527
+ "type": "(event: MouseEvent<HTMLButtonElement>) => unknown",
10519
10528
  "required": false,
10520
- "description": "Callback disparado quando a acao e executada.",
10529
+ "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).",
10521
10530
  "semanticRole": "event",
10522
10531
  "bindable": false
10523
10532
  },
@@ -10554,6 +10563,24 @@
10554
10563
  "file": "apps/showcase/src/app/components/sg-button/samples/basico.tsx.sample",
10555
10564
  "kind": "sample"
10556
10565
  },
10566
+ {
10567
+ "id": "auto-loading",
10568
+ "title": "Loading automático com Future",
10569
+ "file": "apps/showcase/src/app/components/sg-button/samples/loading-automatico-com-future.tsx.sample",
10570
+ "kind": "sample"
10571
+ },
10572
+ {
10573
+ "id": "sync-vs-async",
10574
+ "title": "onClick síncrono x assíncrono",
10575
+ "file": "apps/showcase/src/app/components/sg-button/samples/onclick-sincrono-x-assincrono.tsx.sample",
10576
+ "kind": "sample"
10577
+ },
10578
+ {
10579
+ "id": "async-error",
10580
+ "title": "Tratando erro no onClick assíncrono",
10581
+ "file": "apps/showcase/src/app/components/sg-button/samples/tratando-erro-no-onclick-assincrono.tsx.sample",
10582
+ "kind": "sample"
10583
+ },
10557
10584
  {
10558
10585
  "id": "playground",
10559
10586
  "title": "Playground",
@@ -10581,7 +10608,8 @@
10581
10608
  "preferredUseCases": [
10582
10609
  "Acoes primarias e secundarias em formularios e telas CRUD.",
10583
10610
  "Submit, confirmacao, salvar, cancelar e outras acoes explicitas do usuario.",
10584
- "Chamadas para acao com feedback visual de loading."
10611
+ "Chamadas para acao com feedback visual de loading.",
10612
+ "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."
10585
10613
  ],
10586
10614
  "avoidUseCases": [
10587
10615
  "Agrupar conteudo ou estruturar layout; nesses casos prefira SgPanel, SgCard ou SgStack.",
@@ -10603,7 +10631,10 @@
10603
10631
  ],
10604
10632
  "compositionHints": [
10605
10633
  "Combinar com SgInputText e outros campos em formularios.",
10606
- "Usar dentro de SgPanel ou SgDialog para acoes contextuais."
10634
+ "Usar dentro de SgPanel ou SgDialog para acoes contextuais.",
10635
+ "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.",
10636
+ "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(...))).",
10637
+ "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."
10607
10638
  ],
10608
10639
  "rankingSignals": {
10609
10640
  "freeText": 0,
@@ -18099,6 +18130,15 @@
18099
18130
  "semanticRole": "behavior",
18100
18131
  "bindable": true
18101
18132
  },
18133
+ {
18134
+ "name": "overflow",
18135
+ "type": "boolean",
18136
+ "required": false,
18137
+ "default": false,
18138
+ "description": "Move para um menu '...' os botoes que nao cabem na largura disponivel; abaixo de 600px abre como painel inferior.",
18139
+ "semanticRole": "behavior",
18140
+ "bindable": true
18141
+ },
18102
18142
  {
18103
18143
  "name": "children",
18104
18144
  "type": "ReactNode",
@@ -18542,6 +18582,15 @@
18542
18582
  "semanticRole": "behavior",
18543
18583
  "bindable": true
18544
18584
  },
18585
+ {
18586
+ "name": "enabled",
18587
+ "type": "boolean",
18588
+ "required": false,
18589
+ "default": true,
18590
+ "description": "Quando false, o wizard inteiro fica desabilitado: Anterior, Proximo, Concluir e os passos do stepper deixam de responder; a etapa atual segue visivel. Diferente de nextDisabled, que so bloqueia o avanco.",
18591
+ "semanticRole": "behavior",
18592
+ "bindable": true
18593
+ },
18545
18594
  {
18546
18595
  "name": "onStepChange",
18547
18596
  "type": "(index: number) => void",
@@ -18901,6 +18950,15 @@
18901
18950
  "semanticRole": "validation",
18902
18951
  "bindable": true
18903
18952
  },
18953
+ {
18954
+ "name": "enabled",
18955
+ "type": "boolean",
18956
+ "required": false,
18957
+ "default": true,
18958
+ "description": "Quando false, os dias, os botoes de mes anterior/proximo e o atalho hoje ficam desabilitados (nao focaveis); a data e o mes atuais continuam legiveis.",
18959
+ "semanticRole": "behavior",
18960
+ "bindable": true
18961
+ },
18904
18962
  {
18905
18963
  "name": "onValueChange",
18906
18964
  "type": "(date: Date) => void",
@@ -19087,13 +19145,21 @@
19087
19145
  },
19088
19146
  {
19089
19147
  "name": "digitalStyle",
19090
- "type": "\"default\" | \"segment\" | \"sevenSegment\" | \"roller3d\" | \"flip\" | \"fade\" | \"matrix\" | \"neon\" | \"discard\"",
19148
+ "type": "\"default\" | \"segment\" | \"sevenSegment\" | \"roller3d\" | \"flip\" | \"fade\" | \"matrix\" | \"neon\" | \"discard\" | \"custom\"",
19091
19149
  "required": false,
19092
19150
  "default": "default",
19093
19151
  "description": "Estilo visual do relogio digital.",
19094
19152
  "semanticRole": "appearance",
19095
19153
  "bindable": true
19096
19154
  },
19155
+ {
19156
+ "name": "digitBuilder",
19157
+ "type": "(parts: SgClockDigitalParts) => ReactNode",
19158
+ "required": false,
19159
+ "description": "Desenha o relogio digital quando digitalStyle e custom; sem ele, usa o estilo default.",
19160
+ "semanticRole": "appearance",
19161
+ "bindable": false
19162
+ },
19097
19163
  {
19098
19164
  "name": "timezone",
19099
19165
  "type": "string",
@@ -23718,20 +23784,38 @@
23718
23784
  "examples": [
23719
23785
  {
23720
23786
  "id": "basic",
23721
- "title": "Uso basico",
23787
+ "title": "Uso básico",
23722
23788
  "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/uso-basico.tsx.sample",
23723
23789
  "kind": "sample"
23724
23790
  },
23725
23791
  {
23726
23792
  "id": "dynamic",
23727
- "title": "Troca dinamica de locale",
23793
+ "title": "Troca dinâmica de locale",
23728
23794
  "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/troca-dinamica-de-locale.tsx.sample",
23729
23795
  "kind": "sample"
23796
+ },
23797
+ {
23798
+ "id": "global",
23799
+ "title": "API imperativa (setComponentsI18n)",
23800
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/api-imperativa-setcomponentsi18n.tsx.sample",
23801
+ "kind": "sample"
23802
+ },
23803
+ {
23804
+ "id": "custom",
23805
+ "title": "Sobrescrita de mensagens",
23806
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/sobrescrita-de-mensagens.tsx.sample",
23807
+ "kind": "sample"
23808
+ },
23809
+ {
23810
+ "id": "playground",
23811
+ "title": "Playground",
23812
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/sg-components-i18n-provider.tsx.playground",
23813
+ "kind": "playground"
23730
23814
  }
23731
23815
  ],
23732
23816
  "showcase": {
23733
23817
  "route": "/components/providers/sg-components-i18n-provider",
23734
- "hasPlayground": false,
23818
+ "hasPlayground": true,
23735
23819
  "hasPropsTable": true
23736
23820
  },
23737
23821
  "sdui": {
@@ -23843,14 +23927,20 @@
23843
23927
  },
23844
23928
  {
23845
23929
  "id": "locale-aware",
23846
- "title": "Componentes conscientes do locale",
23930
+ "title": "Construindo componentes cientes de locale",
23847
23931
  "file": "apps/showcase/src/app/components/hooks/use-components-i18n/samples/componentes-conscientes-do-locale.tsx.sample",
23848
23932
  "kind": "sample"
23933
+ },
23934
+ {
23935
+ "id": "playground",
23936
+ "title": "Playground",
23937
+ "file": "apps/showcase/src/app/components/hooks/use-components-i18n/use-components-i18n.tsx.playground",
23938
+ "kind": "playground"
23849
23939
  }
23850
23940
  ],
23851
23941
  "showcase": {
23852
23942
  "route": "/components/hooks/use-components-i18n",
23853
- "hasPlayground": false,
23943
+ "hasPlayground": true,
23854
23944
  "hasPropsTable": true
23855
23945
  }
23856
23946
  },
@@ -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
- export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "disabled" | "color"> & {
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;IACtG,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,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAsEF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,oBAAoB,CAAC,CAYhC;AAoDD,eAAO,MAAM,QAAQ;IAtJnB,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;cACjB,OAAO;mBACF,oBAAoB;2CAmNpC,CAAC"}
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"}
@@ -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
- hoverFg: custom?.hoverFg ?? (custom?.fg ?? base.fg),
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 || loading);
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: loading ? (_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] })) }));
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,QAiHpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAyBrB,CAAC"}
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 interacao enquanto a acao esta em progresso.",
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: "MouseEventHandler<HTMLButtonElement>",
79
- description: "Callback disparado quando a acao e executada.",
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,
@@ -18,6 +18,12 @@ export type SgCalendarProps = Omit<React.HTMLAttributes<HTMLDivElement>, "childr
18
18
  minDate?: Date | string;
19
19
  maxDate?: Date | string;
20
20
  isDateDisabled?: (date: Date) => boolean;
21
+ /**
22
+ * Habilita o calendario (padrao `true`). Com `false` os dias, os botoes de mes anterior/proximo
23
+ * e o atalho "hoje" ficam desabilitados (nao focaveis, sem clique); a data e o mes atuais seguem
24
+ * legiveis.
25
+ */
26
+ enabled?: boolean;
21
27
  showTodayShortcut?: boolean;
22
28
  cardTitle?: React.ReactNode;
23
29
  cardProps?: Omit<SgCardProps, "children" | "title">;
@@ -1 +1 @@
1
- {"version":3,"file":"SgCalendar.d.ts","sourceRoot":"","sources":["../../../src/gadgets/calendar/SgCalendar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAuG/D,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG;IAClG,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IACzC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAgU1D;yBAhUe,UAAU"}
1
+ {"version":3,"file":"SgCalendar.d.ts","sourceRoot":"","sources":["../../../src/gadgets/calendar/SgCalendar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAuG/D,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG;IAClG,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IACzC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAwU1D;yBAxUe,UAAU"}
@@ -92,7 +92,7 @@ function capitalizeFirst(value, locale) {
92
92
  }
93
93
  export function SgCalendar(props) {
94
94
  const i18n = useComponentsI18n();
95
- const { value, defaultValue, onValueChange, viewDate, defaultViewDate, onViewDateChange, locale = "pt-BR", weekStartsOn = 0, weekdayFormat = "narrow", numberOfMonths = 1, monthsPerLine = 3, monthMinWidth, showAdjacentMonths = true, minDate, maxDate, isDateDisabled, showTodayShortcut = true, className, style, cardTitle, cardProps, ...rest } = props;
95
+ const { value, defaultValue, onValueChange, viewDate, defaultViewDate, onViewDateChange, locale = "pt-BR", weekStartsOn = 0, weekdayFormat = "narrow", numberOfMonths = 1, monthsPerLine = 3, monthMinWidth, showAdjacentMonths = true, minDate, maxDate, isDateDisabled, enabled = true, showTodayShortcut = true, className, style, cardTitle, cardProps, ...rest } = props;
96
96
  const resolvedCardTitle = cardTitle ?? t(i18n, "components.gadgets.calendar.title");
97
97
  const today = React.useMemo(() => normalizeDate(new Date()), []);
98
98
  const valueControlled = value !== undefined;
@@ -171,6 +171,8 @@ export function SgCalendar(props) {
171
171
  return false;
172
172
  }, [isDateDisabled, parsedMaxDate, parsedMinDate]);
173
173
  const handleSelectDate = React.useCallback((date) => {
174
+ if (!enabled)
175
+ return;
174
176
  if (isCellDisabled(date))
175
177
  return;
176
178
  if (!valueControlled) {
@@ -180,8 +182,10 @@ export function SgCalendar(props) {
180
182
  if (!isSameMonth(date, monthViewDate)) {
181
183
  setMonthViewDate(date);
182
184
  }
183
- }, [isCellDisabled, monthViewDate, onValueChange, setMonthViewDate, valueControlled]);
185
+ }, [enabled, isCellDisabled, monthViewDate, onValueChange, setMonthViewDate, valueControlled]);
184
186
  const handleGoToday = React.useCallback(() => {
187
+ if (!enabled)
188
+ return;
185
189
  const now = normalizeDate(new Date());
186
190
  if (!isCellDisabled(now)) {
187
191
  if (!valueControlled) {
@@ -190,7 +194,7 @@ export function SgCalendar(props) {
190
194
  onValueChange?.(new Date(now));
191
195
  }
192
196
  setMonthViewDate(now);
193
- }, [isCellDisabled, onValueChange, setMonthViewDate, valueControlled]);
197
+ }, [enabled, isCellDisabled, onValueChange, setMonthViewDate, valueControlled]);
194
198
  const monthDates = React.useMemo(() => Array.from({ length: safeNumberOfMonths }, (_, index) => addMonths(monthViewDate, index)), [monthViewDate, safeNumberOfMonths]);
195
199
  const selectedLabel = capitalizeFirst(formatSelectedDate.format(selectedDate), locale);
196
200
  const visibleColumns = Math.min(safeMonthsPerLine, safeNumberOfMonths);
@@ -206,7 +210,7 @@ export function SgCalendar(props) {
206
210
  maxWidth: "100%",
207
211
  ...(cardProps?.style ?? {})
208
212
  };
209
- 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, children: _jsxs("div", { className: cn("inline-flex max-w-full select-none flex-col gap-3", className), style: style, ...rest, children: [_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-background px-4 py-3", children: [_jsx("div", { className: "min-h-6 text-sm font-medium text-foreground", children: selectedLabel }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => setMonthViewDate(addMonths(monthViewDate, -1)), className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": uiLabels.previousMonth, title: uiLabels.previousMonth, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "m12.5 5.8-4.2 4.2 4.2 4.2", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx("button", { type: "button", onClick: () => setMonthViewDate(addMonths(monthViewDate, 1)), className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": uiLabels.nextMonth, title: uiLabels.nextMonth, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "m7.5 5.8 4.2 4.2-4.2 4.2", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), showTodayShortcut ? (_jsx("button", { type: "button", onClick: handleGoToday, className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-border/70 bg-muted/40 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": uiLabels.goToday, title: uiLabels.goToday, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "M5.6 7.6 10 12l4.4-4.4", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })) : null] })] }), _jsx("div", { className: "max-h-full max-w-full overflow-auto", children: _jsx("div", { className: "grid gap-4", style: monthGridStyle, children: monthDates.map((monthDate) => {
213
+ 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, children: _jsxs("div", { className: cn("inline-flex max-w-full select-none flex-col gap-3", !enabled && "opacity-60", className), style: style, "aria-disabled": enabled ? undefined : true, ...rest, children: [_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-background px-4 py-3", children: [_jsx("div", { className: "min-h-6 text-sm font-medium text-foreground", children: selectedLabel }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => enabled && setMonthViewDate(addMonths(monthViewDate, -1)), disabled: !enabled, className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:hover:bg-transparent", "aria-label": uiLabels.previousMonth, title: uiLabels.previousMonth, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "m12.5 5.8-4.2 4.2 4.2 4.2", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx("button", { type: "button", onClick: () => enabled && setMonthViewDate(addMonths(monthViewDate, 1)), disabled: !enabled, className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": uiLabels.nextMonth, title: uiLabels.nextMonth, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "m7.5 5.8 4.2 4.2-4.2 4.2", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), showTodayShortcut ? (_jsx("button", { type: "button", onClick: handleGoToday, disabled: !enabled, className: "inline-flex h-8 w-8 items-center justify-center rounded-md border border-border/70 bg-muted/40 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": uiLabels.goToday, title: uiLabels.goToday, children: _jsx("svg", { viewBox: "0 0 20 20", className: "h-4 w-4", "aria-hidden": "true", children: _jsx("path", { d: "M5.6 7.6 10 12l4.4-4.4", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })) : null] })] }), _jsx("div", { className: "max-h-full max-w-full overflow-auto", children: _jsx("div", { className: "grid gap-4", style: monthGridStyle, children: monthDates.map((monthDate) => {
210
214
  const firstDayOfMonth = startOfMonth(monthDate);
211
215
  const firstDayOffset = (firstDayOfMonth.getDay() - safeWeekStartsOn + 7) % 7;
212
216
  const gridStart = addDays(firstDayOfMonth, -firstDayOffset);
@@ -215,7 +219,7 @@ export function SgCalendar(props) {
215
219
  return (_jsxs("div", { className: "rounded-xl border border-border/70 bg-background", children: [_jsx("div", { className: "border-b border-border/70 px-4 py-3", children: _jsx("div", { className: "text-[1.1rem] font-semibold text-foreground", children: monthLabel }) }), _jsxs("div", { className: "px-4 pb-4 pt-3", children: [_jsx("div", { className: "mb-2 grid grid-cols-7 text-center text-[0.79rem] font-medium uppercase tracking-wide text-muted-foreground", children: weekdayLabels.map((label, index) => (_jsx("span", { className: "py-1", children: label }, `${label}-${index}`))) }), _jsx("div", { className: "grid grid-cols-7 gap-y-1", children: days.map((day) => {
216
220
  const inCurrentMonth = isSameMonth(day, monthDate);
217
221
  const hiddenAdjacentDay = !showAdjacentMonths && !inCurrentMonth;
218
- const disabled = isCellDisabled(day);
222
+ const disabled = !enabled || isCellDisabled(day);
219
223
  const selected = isSameDay(day, selectedDate) && inCurrentMonth;
220
224
  const isToday = isSameDay(day, today) && inCurrentMonth;
221
225
  if (hiddenAdjacentDay) {
@@ -1 +1 @@
1
- {"version":3,"file":"SgCalendar.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/calendar/SgCalendar.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"}
1
+ {"version":3,"file":"SgCalendar.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/calendar/SgCalendar.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,MAAM,EAAE,QAgCpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
@@ -19,6 +19,7 @@ export const sgMeta = {
19
19
  { name: "numberOfMonths", type: "number", default: 1, description: "Quantidade de meses visiveis ao mesmo tempo.", semanticRole: "appearance", bindable: true },
20
20
  { name: "minDate", type: "Date | string", description: "Limite minimo de selecao.", semanticRole: "validation", bindable: true },
21
21
  { name: "maxDate", type: "Date | string", description: "Limite maximo de selecao.", semanticRole: "validation", bindable: true },
22
+ { name: "enabled", type: "boolean", default: true, description: "Quando false, os dias, os botoes de mes anterior/proximo e o atalho hoje ficam desabilitados (nao focaveis); a data e o mes atuais continuam legiveis.", semanticRole: "behavior", bindable: true },
22
23
  { name: "onValueChange", type: "(date: Date) => void", description: "Callback disparado ao selecionar uma data.", semanticRole: "event", bindable: false }
23
24
  ],
24
25
  states: ["default", "multi-month", "disabled-dates", "controlled"],