@seedgrid/fe-components 2026.6.8 → 2026.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +7114 -330
  2. package/dist/commons/SgToaster.d.ts +2 -0
  3. package/dist/commons/SgToaster.d.ts.map +1 -1
  4. package/dist/commons/SgToaster.js +2 -2
  5. package/dist/commons/SgToaster.meta.d.ts.map +1 -1
  6. package/dist/commons/SgToaster.meta.js +2 -1
  7. package/dist/commons/SgWhistleHost.d.ts +16 -15
  8. package/dist/commons/SgWhistleHost.d.ts.map +1 -1
  9. package/dist/commons/SgWhistleHost.js +38 -147
  10. package/dist/commons/SgWhistleHost.meta.d.ts.map +1 -1
  11. package/dist/commons/SgWhistleHost.meta.js +17 -15
  12. package/dist/commons/SgWhistler.d.ts +20 -0
  13. package/dist/commons/SgWhistler.d.ts.map +1 -0
  14. package/dist/commons/SgWhistler.js +168 -0
  15. package/dist/commons/SgWhistler.meta.d.ts +4 -0
  16. package/dist/commons/SgWhistler.meta.d.ts.map +1 -0
  17. package/dist/commons/SgWhistler.meta.js +47 -0
  18. package/dist/commons/sgWhistleHostRegistry.d.ts +7 -0
  19. package/dist/commons/sgWhistleHostRegistry.d.ts.map +1 -0
  20. package/dist/commons/sgWhistleHostRegistry.js +42 -0
  21. package/dist/gadgets/clock/themes/SgClockThemePicker.d.ts.map +1 -1
  22. package/dist/gadgets/clock/themes/SgClockThemePicker.js +1 -1
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/inputs/SgCombobox.d.ts.map +1 -1
  27. package/dist/inputs/SgCombobox.js +6 -2
  28. package/dist/inputs/SgInputPassword.d.ts +1 -0
  29. package/dist/inputs/SgInputPassword.d.ts.map +1 -1
  30. package/dist/inputs/SgPeriodSelector.d.ts +103 -0
  31. package/dist/inputs/SgPeriodSelector.d.ts.map +1 -0
  32. package/dist/inputs/SgPeriodSelector.js +368 -0
  33. package/dist/inputs/SgPeriodSelector.meta.d.ts +4 -0
  34. package/dist/inputs/SgPeriodSelector.meta.d.ts.map +1 -0
  35. package/dist/inputs/SgPeriodSelector.meta.js +55 -0
  36. package/dist/layout/SgMenu.d.ts.map +1 -1
  37. package/dist/layout/SgMenu.js +1 -1
  38. package/dist/rhf.d.ts +20 -5
  39. package/dist/rhf.d.ts.map +1 -1
  40. package/dist/sandbox.cjs +45 -45
  41. package/package.json +3 -3
@@ -14,6 +14,8 @@ export type SgToasterProps = Omit<React.HTMLAttributes<HTMLDivElement>, "childre
14
14
  closeButton?: boolean;
15
15
  richColors?: boolean;
16
16
  transparency?: number;
17
+ /** Arredonda os cantos de cada toast (rounded-md). Desligue para cantos retos. */
18
+ rounded?: boolean;
17
19
  customColors?: SgToasterCustomColors;
18
20
  };
19
21
  /** @internal — used by SgToastHost to skip the host-presence check */
@@ -1 +1 @@
1
- {"version":3,"file":"SgToaster.d.ts","sourceRoot":"","sources":["../../src/commons/SgToaster.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQlF,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,eAAe,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CACzC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CACnD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACpF,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC,CAAC;AAgGF,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,wBAA6B,CAAC;AAE/D,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,kDA8I9C"}
1
+ {"version":3,"file":"SgToaster.d.ts","sourceRoot":"","sources":["../../src/commons/SgToaster.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQlF,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,eAAe,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CACzC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CACnD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACpF,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC,CAAC;AAgGF,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,wBAA6B,CAAC;AAE/D,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,kDAgJ9C"}
@@ -97,7 +97,7 @@ function resolveToastColors(type, richColors, customColors) {
97
97
  export const SgToasterHostContext = React.createContext(false);
98
98
  export function SgToaster(props) {
99
99
  const renderedByHost = React.useContext(SgToasterHostContext);
100
- const { position = "top-right", duration = 4000, visibleToasts = 6, closeButton = true, richColors = true, transparency = 0, customColors, className, style, ...rest } = props;
100
+ const { position = "top-right", duration = 4000, visibleToasts = 6, closeButton = true, richColors = true, transparency = 0, rounded = true, customColors, className, style, ...rest } = props;
101
101
  const i18n = useComponentsI18n();
102
102
  const [toasts, setToasts] = React.useState([]);
103
103
  const timersRef = React.useRef({});
@@ -155,7 +155,7 @@ export function SgToaster(props) {
155
155
  return (_jsx("div", { className: cn("pointer-events-none fixed z-[1100] flex max-h-screen w-full flex-col gap-2 p-4 sm:w-auto", POSITION_CLASS[position], className), style: style, ...rest, children: visible.map((toast) => {
156
156
  const typeColors = resolveToastColors(toast.type, richColors, customColors);
157
157
  const canClose = toast.closeButton ?? closeButton;
158
- return (_jsxs("div", { className: cn("pointer-events-auto flex min-w-[260px] max-w-[420px] items-start gap-3 rounded-md border px-3 py-2 shadow-lg", toast.className), style: {
158
+ return (_jsxs("div", { className: cn("pointer-events-auto flex min-w-[260px] max-w-[420px] items-start gap-3 border px-3 py-2 shadow-lg", rounded ? "rounded-md" : "rounded-none", toast.className), style: {
159
159
  borderColor: typeColors.border,
160
160
  backgroundColor: typeColors.bg,
161
161
  color: typeColors.fg,
@@ -1 +1 @@
1
- {"version":3,"file":"SgToaster.meta.d.ts","sourceRoot":"","sources":["../../src/commons/SgToaster.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA4BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
1
+ {"version":3,"file":"SgToaster.meta.d.ts","sourceRoot":"","sources":["../../src/commons/SgToaster.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA6BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
@@ -16,7 +16,8 @@ export const sgMeta = {
16
16
  { name: "duration", type: "number", default: 4000, description: "Duracao padrao dos toasts em milissegundos.", semanticRole: "behavior", bindable: true },
17
17
  { name: "visibleToasts", type: "number", default: 6, description: "Quantidade maxima de toasts visiveis simultaneamente.", semanticRole: "behavior", bindable: true },
18
18
  { name: "closeButton", type: "boolean", default: true, description: "Exibe botao de fechar em cada toast.", semanticRole: "appearance", bindable: true },
19
- { name: "richColors", type: "boolean", default: true, description: "Ativa paleta visual rica por tipo de toast.", semanticRole: "appearance", bindable: true }
19
+ { name: "richColors", type: "boolean", default: true, description: "Ativa paleta visual rica por tipo de toast.", semanticRole: "appearance", bindable: true },
20
+ { name: "rounded", type: "boolean", default: true, description: "Arredonda os cantos de cada toast (desligue para cantos retos).", semanticRole: "appearance", bindable: true }
20
21
  ],
21
22
  states: ["idle", "showing-toasts"],
22
23
  examples: [
@@ -1,16 +1,17 @@
1
- import React from "react";
2
- import { type SgWhistleSeverity } from "./SgWhistle";
3
- type SgWhistleColors = {
4
- bg: string;
5
- fg: string;
6
- border: string;
7
- };
8
- export type SgWhistleHostProps = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
9
- max?: number;
10
- newestOnTop?: boolean;
11
- gap?: number;
12
- customColors?: Partial<Record<SgWhistleSeverity, SgWhistleColors>>;
13
- };
14
- export declare function SgWhistleHost(props: SgWhistleHostProps): import("react/jsx-runtime").JSX.Element;
15
- export {};
1
+ import { type SgWhistlerProps } from "./SgWhistler";
2
+ export type SgWhistleHostProps = SgWhistlerProps;
3
+ /**
4
+ * SgWhistleHost — marca o local onde os whistles vao aparecer.
5
+ *
6
+ * Monte em qualquer ponto da arvore. Se existirem varios hosts ao mesmo tempo
7
+ * (ex.: um no topo e outro embaixo do layout), o mais profundo na arvore React
8
+ * (ultimo a montar) tem prioridade. Quando ele desmonta, o host montado mais
9
+ * recentemente antes dele volta a ficar ativo automaticamente.
10
+ *
11
+ * Quando qualquer SgWhistleHost esta presente, o SgWhistler "solto" cede a vez
12
+ * e para de renderizar, para os dois coexistirem sem duplicar.
13
+ *
14
+ * Aceita os mesmos props do SgWhistler (max, newestOnTop, gap, roundBorder, etc.).
15
+ */
16
+ export declare function SgWhistleHost(props: SgWhistleHostProps): import("react/jsx-runtime").JSX.Element | null;
16
17
  //# sourceMappingURL=SgWhistleHost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SgWhistleHost.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistleHost.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAMrB,KAAK,eAAe,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACxF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;CACpE,CAAC;AA2GF,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CA+HtD"}
1
+ {"version":3,"file":"SgWhistleHost.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistleHost.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAavF,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,kDAiCtD"}
@@ -1,154 +1,45 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import React from "react";
4
- import { t, useComponentsI18n } from "../i18n";
5
- import { dismissSgWhistle, subscribeSgWhistles } from "./SgWhistle";
6
- function cn(...parts) {
7
- return parts.filter(Boolean).join(" ");
8
- }
9
- const DEFAULT_COLORS = {
10
- default: {
11
- bg: "hsl(var(--background))",
12
- fg: "hsl(var(--foreground))",
13
- border: "hsl(var(--border))"
14
- },
15
- success: {
16
- bg: "#f0fdf4",
17
- fg: "#14532d",
18
- border: "#86efac"
19
- },
20
- info: {
21
- bg: "#eff6ff",
22
- fg: "#1e3a8a",
23
- border: "#93c5fd"
24
- },
25
- warning: {
26
- bg: "#fffbeb",
27
- fg: "#78350f",
28
- border: "#fcd34d"
29
- },
30
- error: {
31
- bg: "#fef2f2",
32
- fg: "#7f1d1d",
33
- border: "#fca5a5"
34
- },
35
- loading: {
36
- bg: "#eff6ff",
37
- fg: "#1d4ed8",
38
- border: "#60a5fa"
39
- }
40
- };
41
- function clampOpacity(value) {
42
- if (value === undefined || Number.isNaN(value)) {
43
- return 1;
44
- }
45
- return Math.max(0, Math.min(1, value));
46
- }
47
- function resolveColors(severity, customColors) {
48
- const base = DEFAULT_COLORS[severity];
49
- const custom = customColors?.[severity];
50
- return {
51
- bg: custom?.bg ?? base.bg,
52
- fg: custom?.fg ?? base.fg,
53
- border: custom?.border ?? base.border
54
- };
55
- }
56
- function buildBorderStyle(borderStyle, colors) {
57
- if (borderStyle === "none") {
58
- return {
59
- borderWidth: 0
60
- };
61
- }
62
- if (borderStyle === "left-accent") {
63
- return {
64
- borderWidth: 1,
65
- borderColor: colors.border,
66
- borderLeftWidth: 4
67
- };
68
- }
69
- if (borderStyle === "full-accent") {
70
- return {
71
- borderWidth: 1,
72
- borderColor: colors.border,
73
- boxShadow: `inset 0 0 0 1px ${colors.border}`
74
- };
75
- }
76
- if (borderStyle === "soft") {
77
- return {
78
- borderWidth: 1,
79
- borderColor: colors.border,
80
- boxShadow: "0 1px 2px rgba(15, 23, 42, 0.06)"
81
- };
82
- }
83
- return {
84
- borderWidth: 1,
85
- borderColor: colors.border
86
- };
87
- }
88
- function renderWhistleContent(whistle) {
89
- if (whistle.renderer !== undefined) {
90
- return whistle.renderer;
91
- }
92
- return (_jsxs("div", { className: "min-w-0 flex-1", children: [whistle.title ? _jsx("div", { className: "text-sm font-semibold", children: whistle.title }) : null, whistle.message ? _jsx("div", { className: cn(whistle.title ? "mt-0.5" : undefined, "text-sm"), children: whistle.message }) : null] }));
93
- }
4
+ import { SgWhistler, SgWhistlerHostContext } from "./SgWhistler";
5
+ import { registerWhistleHost, unregisterWhistleHost, getActiveWhistleHostId, subscribeWhistleHostRegistry, nextWhistleHostId } from "./sgWhistleHostRegistry";
6
+ /**
7
+ * SgWhistleHost — marca o local onde os whistles vao aparecer.
8
+ *
9
+ * Monte em qualquer ponto da arvore. Se existirem varios hosts ao mesmo tempo
10
+ * (ex.: um no topo e outro embaixo do layout), o mais profundo na arvore React
11
+ * (ultimo a montar) tem prioridade. Quando ele desmonta, o host montado mais
12
+ * recentemente antes dele volta a ficar ativo automaticamente.
13
+ *
14
+ * Quando qualquer SgWhistleHost esta presente, o SgWhistler "solto" cede a vez
15
+ * e para de renderizar, para os dois coexistirem sem duplicar.
16
+ *
17
+ * Aceita os mesmos props do SgWhistler (max, newestOnTop, gap, roundBorder, etc.).
18
+ */
94
19
  export function SgWhistleHost(props) {
95
- const { max = 4, newestOnTop = false, gap = 12, customColors, className, style, ...rest } = props;
96
- const i18n = useComponentsI18n();
97
- const [whistles, setWhistles] = React.useState([]);
98
- const timersRef = React.useRef({});
99
- React.useEffect(() => subscribeSgWhistles(setWhistles), []);
100
- const visible = React.useMemo(() => {
101
- if (max <= 0) {
102
- return [];
103
- }
104
- const items = whistles.slice(-max);
105
- return newestOnTop ? [...items].reverse() : items;
106
- }, [whistles, max, newestOnTop]);
107
- React.useEffect(() => {
108
- const activeIds = new Set();
109
- visible.forEach((whistle) => {
110
- activeIds.add(whistle.id);
111
- if (timersRef.current[whistle.id]) {
112
- return;
113
- }
114
- if (whistle.severity === "loading") {
115
- return;
116
- }
117
- const timeout = whistle.duration ?? 5000;
118
- if (timeout <= 0) {
119
- return;
120
- }
121
- timersRef.current[whistle.id] = setTimeout(() => {
122
- dismissSgWhistle(whistle.id);
123
- delete timersRef.current[whistle.id];
124
- }, timeout);
125
- });
126
- Object.entries(timersRef.current).forEach(([id, timeoutRef]) => {
127
- if (activeIds.has(id)) {
128
- return;
129
- }
130
- clearTimeout(timeoutRef);
131
- delete timersRef.current[id];
132
- });
133
- }, [visible]);
20
+ // ID estavel entre re-renders, gerado uma vez por instancia.
21
+ const idRef = React.useRef(null);
22
+ if (idRef.current === null) {
23
+ idRef.current = nextWhistleHostId();
24
+ }
25
+ const id = idRef.current;
26
+ // Comeca inativo para evitar mismatch de hidratacao no SSR.
27
+ const [isActive, setIsActive] = React.useState(false);
28
+ // Registra no mount, desregistra no unmount.
134
29
  React.useEffect(() => {
30
+ registerWhistleHost(id);
31
+ setIsActive(getActiveWhistleHostId() === id);
135
32
  return () => {
136
- Object.values(timersRef.current).forEach((timeoutRef) => clearTimeout(timeoutRef));
137
- timersRef.current = {};
33
+ unregisterWhistleHost(id);
138
34
  };
139
- }, []);
140
- return (_jsx("div", { className: cn("flex flex-col", className), style: { gap, ...style }, ...rest, children: visible.map((whistle) => {
141
- const colors = resolveColors(whistle.severity, customColors);
142
- const dismissible = whistle.dismissible ?? true;
143
- return (_jsxs("div", { className: cn("flex items-start gap-3 rounded-lg px-3 py-2", whistle.borderStyle === "none" ? undefined : "border", whistle.className), style: {
144
- backgroundColor: colors.bg,
145
- color: colors.fg,
146
- opacity: clampOpacity(whistle.opacity),
147
- ...buildBorderStyle(whistle.borderStyle ?? "solid", colors),
148
- ...whistle.style
149
- }, role: "status", "aria-live": "polite", children: [whistle.icon ? _jsx("div", { className: "shrink-0 pt-0.5", children: whistle.icon }) : null, renderWhistleContent(whistle), whistle.action ? (_jsx("button", { type: "button", className: "shrink-0 rounded border border-current/25 px-2 py-1 text-xs font-medium", onClick: () => {
150
- whistle.action?.onClick();
151
- dismissSgWhistle(whistle.id);
152
- }, children: whistle.action.label })) : null, dismissible ? (_jsx("button", { type: "button", className: "shrink-0 rounded px-1 py-0.5 text-sm leading-none opacity-80", "aria-label": t(i18n, "components.toaster.close"), onClick: () => dismissSgWhistle(whistle.id), children: "x" })) : null] }, whistle.id));
153
- }) }));
35
+ }, [id]);
36
+ // Reage a outros hosts registrando / desregistrando.
37
+ React.useEffect(() => {
38
+ return subscribeWhistleHostRegistry(() => {
39
+ setIsActive(getActiveWhistleHostId() === id);
40
+ });
41
+ }, [id]);
42
+ if (!isActive)
43
+ return null;
44
+ return (_jsx(SgWhistlerHostContext.Provider, { value: true, children: _jsx(SgWhistler, { ...props }) }));
154
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SgWhistleHost.meta.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistleHost.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA0BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
1
+ {"version":3,"file":"SgWhistleHost.meta.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistleHost.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA2BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAoBrB,CAAC"}
@@ -7,39 +7,41 @@ export const sgMeta = {
7
7
  displayName: "SgWhistleHost",
8
8
  category: "feedback",
9
9
  subcategory: "whistle-host",
10
- description: "Host de avisos persistentes ou semi-persistentes em pilha, com cores por severidade, acao opcional e controle de maximo visivel.",
11
- tags: ["feedback", "alerts", "host", "inline-notifications"],
12
- capabilities: ["alert-stacking", "severity-colors", "dismissible-items", "timed-dismiss"],
10
+ description: "Marca o local onde os whistles aparecem. Suporta varios hosts simultaneos (ex.: topo e rodape) via registry: apenas o ultimo montado fica ativo, e o SgWhistler solto cede a vez quando qualquer host esta presente.",
11
+ tags: ["feedback", "alerts", "host", "inline-notifications", "registry"],
12
+ capabilities: ["alert-stacking", "severity-colors", "dismissible-items", "timed-dismiss", "multi-host-registry"],
13
13
  fieldSemantics: ["alertHost", "persistentFeedback", "stackedAlerts"],
14
14
  props: [
15
- { name: "max", type: "number", default: 4, description: "Quantidade maxima de whistle items visiveis.", semanticRole: "behavior", bindable: true },
15
+ { name: "max", type: "number", default: 4, description: "Quantidade maxima de whistle items visiveis neste host.", semanticRole: "behavior", bindable: true },
16
16
  { name: "newestOnTop", type: "boolean", default: false, description: "Inverte a ordem visual para mostrar os mais novos primeiro.", semanticRole: "behavior", bindable: true },
17
- { name: "gap", type: "number", default: 12, description: "Espacamento entre os avisos renderizados.", semanticRole: "appearance", bindable: true }
17
+ { name: "gap", type: "number", default: 12, description: "Espacamento entre os avisos renderizados.", semanticRole: "appearance", bindable: true },
18
+ { name: "rounded", type: "boolean", default: true, description: "Arredonda os cantos de cada whistle (desligue para cantos retos).", semanticRole: "appearance", bindable: true }
18
19
  ],
19
- states: ["idle", "showing-alerts"],
20
+ states: ["idle", "showing-alerts", "inactive"],
20
21
  examples: [
21
- { id: "basic", title: "Basico", file: "apps/showcase/src/app/components/sg-whistle-host/samples/basico.tsx.sample", kind: "sample" },
22
+ { id: "basic", title: "Basico", file: "apps/showcase/src/app/components/sg-whistle-host/samples/base-setup.tsx.sample", kind: "sample" },
22
23
  { id: "playground", title: "Playground", file: "apps/showcase/src/app/components/sg-whistle-host/sg-whistle-host.tsx.playground", kind: "playground" }
23
24
  ],
24
25
  showcase: { route: "/components/sg-whistle-host", hasPlayground: true, hasPropsTable: true },
25
- sdui: { rendererType: "feedback.whistle-host", acceptsDataBinding: false, defaultProps: { max: 4, newestOnTop: false } }
26
+ sdui: { rendererType: "feedback.whistle-host", acceptsDataBinding: false, defaultProps: { max: 4, newestOnTop: false, rounded: true } }
26
27
  };
27
28
  export const aiHints = {
28
29
  version: "0.1",
29
30
  preferredUseCases: [
30
- "Exibir avisos empilhados mais persistentes do que um toast.",
31
- "Comunicar erros, warnings e mensagens operacionais inline em uma area fixa.",
32
- "Agrupar alertas com acao e dismiss local."
31
+ "Definir um ou mais pontos fixos onde os avisos empilhados devem aparecer.",
32
+ "Layouts com hosts em zonas diferentes (ex.: um no topo e outro embaixo), com prioridade automatica ao ultimo montado.",
33
+ "Comunicar erros, warnings e mensagens operacionais inline em area fixa."
33
34
  ],
34
35
  avoidUseCases: [
35
- "Feedback ultraleve e efemero; nesses casos prefira toaster.",
36
- "Modais de confirmacao.",
37
- "Indicadores compactos como badge."
36
+ "Um unico ponto de exibicao sem necessidade de registry; ai o SgWhistler basta.",
37
+ "Feedback ultraleve e efemero; prefira toaster.",
38
+ "Modais de confirmacao."
38
39
  ],
39
- synonyms: ["alert host", "message stack", "persistent notifications"],
40
+ synonyms: ["alert host", "message stack host", "persistent notifications host"],
40
41
  relatedEntityFields: ["alerts", "warnings", "inlineMessages", "systemMessages"],
41
42
  compositionHints: [
42
43
  "Usar com a API `sgWhistle` para publicar avisos.",
44
+ "Espelha o par SgToaster/SgToastHost: SgWhistler e o renderizador, SgWhistleHost e o host com registry.",
43
45
  "Combinar com SgCard ou SgPanel em dashboards operacionais."
44
46
  ],
45
47
  rankingSignals: { freeText: 0, structuredChoice: 0, date: 0, number: 0, denseLayout: 0.7 }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { type SgWhistleSeverity } from "./SgWhistle";
3
+ type SgWhistleColors = {
4
+ bg: string;
5
+ fg: string;
6
+ border: string;
7
+ };
8
+ export type SgWhistlerProps = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
9
+ max?: number;
10
+ newestOnTop?: boolean;
11
+ gap?: number;
12
+ /** Arredonda os cantos de cada whistle (rounded-lg). Desligue para cantos retos. */
13
+ rounded?: boolean;
14
+ customColors?: Partial<Record<SgWhistleSeverity, SgWhistleColors>>;
15
+ };
16
+ /** @internal — usado por SgWhistleHost para pular a checagem de presenca de host. */
17
+ export declare const SgWhistlerHostContext: React.Context<boolean>;
18
+ export declare function SgWhistler(props: SgWhistlerProps): import("react/jsx-runtime").JSX.Element | null;
19
+ export {};
20
+ //# sourceMappingURL=SgWhistler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SgWhistler.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistler.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAOrB,KAAK,eAAe,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;CACpE,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,wBAA6B,CAAC;AA2GhE,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,kDA6IhD"}
@@ -0,0 +1,168 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { t, useComponentsI18n } from "../i18n";
5
+ import { dismissSgWhistle, subscribeSgWhistles } from "./SgWhistle";
6
+ import { hasAnyWhistleHost, subscribeWhistleHostRegistry } from "./sgWhistleHostRegistry";
7
+ function cn(...parts) {
8
+ return parts.filter(Boolean).join(" ");
9
+ }
10
+ /** @internal — usado por SgWhistleHost para pular a checagem de presenca de host. */
11
+ export const SgWhistlerHostContext = React.createContext(false);
12
+ const DEFAULT_COLORS = {
13
+ default: {
14
+ bg: "hsl(var(--background))",
15
+ fg: "hsl(var(--foreground))",
16
+ border: "hsl(var(--border))"
17
+ },
18
+ success: {
19
+ bg: "#f0fdf4",
20
+ fg: "#14532d",
21
+ border: "#86efac"
22
+ },
23
+ info: {
24
+ bg: "#eff6ff",
25
+ fg: "#1e3a8a",
26
+ border: "#93c5fd"
27
+ },
28
+ warning: {
29
+ bg: "#fffbeb",
30
+ fg: "#78350f",
31
+ border: "#fcd34d"
32
+ },
33
+ error: {
34
+ bg: "#fef2f2",
35
+ fg: "#7f1d1d",
36
+ border: "#fca5a5"
37
+ },
38
+ loading: {
39
+ bg: "#eff6ff",
40
+ fg: "#1d4ed8",
41
+ border: "#60a5fa"
42
+ }
43
+ };
44
+ function clampOpacity(value) {
45
+ if (value === undefined || Number.isNaN(value)) {
46
+ return 1;
47
+ }
48
+ return Math.max(0, Math.min(1, value));
49
+ }
50
+ function resolveColors(severity, customColors) {
51
+ const base = DEFAULT_COLORS[severity];
52
+ const custom = customColors?.[severity];
53
+ return {
54
+ bg: custom?.bg ?? base.bg,
55
+ fg: custom?.fg ?? base.fg,
56
+ border: custom?.border ?? base.border
57
+ };
58
+ }
59
+ function buildBorderStyle(borderStyle, colors) {
60
+ if (borderStyle === "none") {
61
+ return {
62
+ borderWidth: 0
63
+ };
64
+ }
65
+ if (borderStyle === "left-accent") {
66
+ return {
67
+ borderWidth: 1,
68
+ borderColor: colors.border,
69
+ borderLeftWidth: 4
70
+ };
71
+ }
72
+ if (borderStyle === "full-accent") {
73
+ return {
74
+ borderWidth: 1,
75
+ borderColor: colors.border,
76
+ boxShadow: `inset 0 0 0 1px ${colors.border}`
77
+ };
78
+ }
79
+ if (borderStyle === "soft") {
80
+ return {
81
+ borderWidth: 1,
82
+ borderColor: colors.border,
83
+ boxShadow: "0 1px 2px rgba(15, 23, 42, 0.06)"
84
+ };
85
+ }
86
+ return {
87
+ borderWidth: 1,
88
+ borderColor: colors.border
89
+ };
90
+ }
91
+ function renderWhistleContent(whistle) {
92
+ if (whistle.renderer !== undefined) {
93
+ return whistle.renderer;
94
+ }
95
+ return (_jsxs("div", { className: "min-w-0 flex-1", children: [whistle.title ? _jsx("div", { className: "text-sm font-semibold", children: whistle.title }) : null, whistle.message ? _jsx("div", { className: cn(whistle.title ? "mt-0.5" : undefined, "text-sm"), children: whistle.message }) : null] }));
96
+ }
97
+ export function SgWhistler(props) {
98
+ const renderedByHost = React.useContext(SgWhistlerHostContext);
99
+ const { max = 4, newestOnTop = false, gap = 12, rounded = true, customColors, className, style, ...rest } = props;
100
+ const i18n = useComponentsI18n();
101
+ const [whistles, setWhistles] = React.useState([]);
102
+ const timersRef = React.useRef({});
103
+ // Se houver algum SgWhistleHost montado, o SgWhistler "solto" cede a vez (evita duplicar).
104
+ const [hostPresent, setHostPresent] = React.useState(() => !renderedByHost && hasAnyWhistleHost());
105
+ React.useEffect(() => subscribeSgWhistles(setWhistles), []);
106
+ React.useEffect(() => {
107
+ if (renderedByHost)
108
+ return;
109
+ setHostPresent(hasAnyWhistleHost());
110
+ return subscribeWhistleHostRegistry(() => setHostPresent(hasAnyWhistleHost()));
111
+ }, [renderedByHost]);
112
+ const visible = React.useMemo(() => {
113
+ if (max <= 0) {
114
+ return [];
115
+ }
116
+ const items = whistles.slice(-max);
117
+ return newestOnTop ? [...items].reverse() : items;
118
+ }, [whistles, max, newestOnTop]);
119
+ React.useEffect(() => {
120
+ const activeIds = new Set();
121
+ visible.forEach((whistle) => {
122
+ activeIds.add(whistle.id);
123
+ if (timersRef.current[whistle.id]) {
124
+ return;
125
+ }
126
+ if (whistle.severity === "loading") {
127
+ return;
128
+ }
129
+ const timeout = whistle.duration ?? 5000;
130
+ if (timeout <= 0) {
131
+ return;
132
+ }
133
+ timersRef.current[whistle.id] = setTimeout(() => {
134
+ dismissSgWhistle(whistle.id);
135
+ delete timersRef.current[whistle.id];
136
+ }, timeout);
137
+ });
138
+ Object.entries(timersRef.current).forEach(([id, timeoutRef]) => {
139
+ if (activeIds.has(id)) {
140
+ return;
141
+ }
142
+ clearTimeout(timeoutRef);
143
+ delete timersRef.current[id];
144
+ });
145
+ }, [visible]);
146
+ React.useEffect(() => {
147
+ return () => {
148
+ Object.values(timersRef.current).forEach((timeoutRef) => clearTimeout(timeoutRef));
149
+ timersRef.current = {};
150
+ };
151
+ }, []);
152
+ if (hostPresent)
153
+ return null;
154
+ return (_jsx("div", { className: cn("flex flex-col", className), style: { gap, ...style }, ...rest, children: visible.map((whistle) => {
155
+ const colors = resolveColors(whistle.severity, customColors);
156
+ const dismissible = whistle.dismissible ?? true;
157
+ return (_jsxs("div", { className: cn("flex items-start gap-3 px-3 py-2", rounded ? "rounded-lg" : "rounded-none", whistle.borderStyle === "none" ? undefined : "border", whistle.className), style: {
158
+ backgroundColor: colors.bg,
159
+ color: colors.fg,
160
+ opacity: clampOpacity(whistle.opacity),
161
+ ...buildBorderStyle(whistle.borderStyle ?? "solid", colors),
162
+ ...whistle.style
163
+ }, role: "status", "aria-live": "polite", children: [whistle.icon ? _jsx("div", { className: "shrink-0 pt-0.5", children: whistle.icon }) : null, renderWhistleContent(whistle), whistle.action ? (_jsx("button", { type: "button", className: "shrink-0 rounded border border-current/25 px-2 py-1 text-xs font-medium", onClick: () => {
164
+ whistle.action?.onClick();
165
+ dismissSgWhistle(whistle.id);
166
+ }, children: whistle.action.label })) : null, dismissible ? (_jsx("button", { type: "button", className: "shrink-0 rounded px-1 py-0.5 text-sm leading-none opacity-80", "aria-label": t(i18n, "components.toaster.close"), onClick: () => dismissSgWhistle(whistle.id), children: "x" })) : null] }, whistle.id));
167
+ }) }));
168
+ }
@@ -0,0 +1,4 @@
1
+ import type { SgAiHintsV0, SgMetaV0 } from "../ai-meta/types";
2
+ export declare const sgMeta: SgMetaV0;
3
+ export declare const aiHints: SgAiHintsV0;
4
+ //# sourceMappingURL=SgWhistler.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SgWhistler.meta.d.ts","sourceRoot":"","sources":["../../src/commons/SgWhistler.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QA2BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
@@ -0,0 +1,47 @@
1
+ export const sgMeta = {
2
+ version: "0.1",
3
+ componentId: "feedback.whistler",
4
+ package: "@seedgrid/fe-components",
5
+ exportName: "SgWhistler",
6
+ slug: "sg-whistler",
7
+ displayName: "SgWhistler",
8
+ category: "feedback",
9
+ subcategory: "whistler",
10
+ description: "Renderizador de avisos persistentes ou semi-persistentes em pilha, com cores por severidade, acao opcional e controle de maximo visivel. Cede a vez quando um SgWhistleHost esta presente.",
11
+ tags: ["feedback", "alerts", "renderer", "inline-notifications"],
12
+ capabilities: ["alert-stacking", "severity-colors", "dismissible-items", "timed-dismiss"],
13
+ fieldSemantics: ["alertRenderer", "persistentFeedback", "stackedAlerts"],
14
+ props: [
15
+ { name: "max", type: "number", default: 4, description: "Quantidade maxima de whistle items visiveis.", semanticRole: "behavior", bindable: true },
16
+ { name: "newestOnTop", type: "boolean", default: false, description: "Inverte a ordem visual para mostrar os mais novos primeiro.", semanticRole: "behavior", bindable: true },
17
+ { name: "gap", type: "number", default: 12, description: "Espacamento entre os avisos renderizados.", semanticRole: "appearance", bindable: true },
18
+ { name: "rounded", type: "boolean", default: true, description: "Arredonda os cantos de cada whistle (desligue para cantos retos).", semanticRole: "appearance", bindable: true }
19
+ ],
20
+ states: ["idle", "showing-alerts"],
21
+ examples: [
22
+ { id: "basic", title: "Basico", file: "apps/showcase/src/app/components/sg-whistler/samples/base-setup.tsx.sample", kind: "sample" },
23
+ { id: "playground", title: "Playground", file: "apps/showcase/src/app/components/sg-whistler/sg-whistler.tsx.playground", kind: "playground" }
24
+ ],
25
+ showcase: { route: "/components/sg-whistler", hasPlayground: true, hasPropsTable: true },
26
+ sdui: { rendererType: "feedback.whistler", acceptsDataBinding: false, defaultProps: { max: 4, newestOnTop: false, rounded: true } }
27
+ };
28
+ export const aiHints = {
29
+ version: "0.1",
30
+ preferredUseCases: [
31
+ "Exibir avisos empilhados mais persistentes do que um toast em uma area fixa.",
32
+ "Comunicar erros, warnings e mensagens operacionais inline.",
33
+ "Agrupar alertas com acao e dismiss local, sem precisar de multiplos pontos de montagem."
34
+ ],
35
+ avoidUseCases: [
36
+ "Precisar de varios pontos de montagem coexistindo; nesses casos use SgWhistleHost.",
37
+ "Feedback ultraleve e efemero; prefira toaster.",
38
+ "Modais de confirmacao."
39
+ ],
40
+ synonyms: ["alert renderer", "message stack", "persistent notifications"],
41
+ relatedEntityFields: ["alerts", "warnings", "inlineMessages", "systemMessages"],
42
+ compositionHints: [
43
+ "Usar com a API `sgWhistle` para publicar avisos.",
44
+ "Para varios locais de exibicao no layout, prefira SgWhistleHost (host com registry)."
45
+ ],
46
+ rankingSignals: { freeText: 0, structuredChoice: 0, date: 0, number: 0, denseLayout: 0.7 }
47
+ };
@@ -0,0 +1,7 @@
1
+ export declare function registerWhistleHost(id: string): void;
2
+ export declare function unregisterWhistleHost(id: string): void;
3
+ export declare function getActiveWhistleHostId(): string | null;
4
+ export declare function hasAnyWhistleHost(): boolean;
5
+ export declare function subscribeWhistleHostRegistry(fn: () => void): () => void;
6
+ export declare function nextWhistleHostId(): string;
7
+ //# sourceMappingURL=sgWhistleHostRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sgWhistleHostRegistry.d.ts","sourceRoot":"","sources":["../../src/commons/sgWhistleHostRegistry.ts"],"names":[],"mappings":"AAgBA,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,QAK7C;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,QAM/C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAKvE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C"}