@rendra-ui/web 2.2.2 → 2.4.0

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 (50) hide show
  1. package/README.md +94 -68
  2. package/bin/rendra.mjs +19 -7
  3. package/dist/base.css +1 -1
  4. package/dist/{button-BgSglLWN.js → button-C6rjowQi.js} +4 -4
  5. package/dist/{button-BgSglLWN.js.map → button-C6rjowQi.js.map} +1 -1
  6. package/dist/chart.js +5 -5
  7. package/dist/cli/auditar.js +66 -16
  8. package/dist/cli/auditar.js.map +1 -1
  9. package/dist/cli/codigos.js +2 -2
  10. package/dist/cli/executar-auditar.js +634 -0
  11. package/dist/cli/executar-auditar.js.map +1 -0
  12. package/dist/cli/help.js +7 -2
  13. package/dist/cli/help.js.map +1 -1
  14. package/dist/cli/trocar.js +2 -2
  15. package/dist/cli/typescript-loader.js +1 -1
  16. package/dist/{cn-Bo1YMupg.js → cn-DXDpTGsu.js} +2 -2
  17. package/dist/{cn-Bo1YMupg.js.map → cn-DXDpTGsu.js.map} +1 -1
  18. package/dist/{components-Dl15AjD-.js → components-hp7iWioq.js} +21 -4
  19. package/dist/components-hp7iWioq.js.map +1 -0
  20. package/dist/components.css +107 -1
  21. package/dist/document-viewer.js +4 -4
  22. package/dist/{empty-state-Be_tReTU.js → empty-state-CdaDLcsB.js} +50 -30
  23. package/dist/empty-state-CdaDLcsB.js.map +1 -0
  24. package/dist/index.js +135 -67
  25. package/dist/index.js.map +1 -1
  26. package/dist/{rendra-provider-DPulIoQB.js → rendra-provider-7jUgUHzR.js} +2 -2
  27. package/dist/{rendra-provider-DPulIoQB.js.map → rendra-provider-7jUgUHzR.js.map} +1 -1
  28. package/dist/rich-text-editor.js +3 -3
  29. package/dist/router-bridge.js +2 -2
  30. package/dist/tokens.css +11 -1
  31. package/dist/{tooltip-dC9gqkuv.js → tooltip-bdmbX6vC.js} +3 -3
  32. package/dist/{tooltip-dC9gqkuv.js.map → tooltip-bdmbX6vC.js.map} +1 -1
  33. package/dist/types/components/app-shell/app-shell.d.ts +22 -2
  34. package/dist/types/components/app-shell/shell-context.d.ts +7 -1
  35. package/dist/types/components/app-shell/types.d.ts +23 -0
  36. package/dist/types/components/layout/page-header.d.ts +17 -1
  37. package/dist/types/components/ui/brand-feedback-icon.d.ts +20 -3
  38. package/dist/types/components/ui/button-group.d.ts +8 -1
  39. package/dist/types/components/ui/empty-state.d.ts +4 -0
  40. package/dist/types/components/ui/error-page.d.ts +6 -1
  41. package/dist/types/components/ui/modal.d.ts +4 -0
  42. package/dist/types/components/ui/select.d.ts +4 -1
  43. package/dist/types/components/ui/wizard.d.ts +14 -1
  44. package/dist/types/lib/initials.d.ts +2 -0
  45. package/dist/{use-breakpoint-BUHd0Cbz.js → use-breakpoint-Dyp-0E_c.js} +2 -2
  46. package/dist/{use-breakpoint-BUHd0Cbz.js.map → use-breakpoint-Dyp-0E_c.js.map} +1 -1
  47. package/dist/widget-grid.js +2 -2
  48. package/package.json +7 -1
  49. package/dist/components-Dl15AjD-.js.map +0 -1
  50. package/dist/empty-state-Be_tReTU.js.map +0 -1
@@ -1,5 +1,6 @@
1
- /*! Rendra Design System v2.2.2 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-Bo1YMupg.js";
1
+ /*! Rendra Design System v2.4.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-DXDpTGsu.js";
3
+ import { CircleCheck, CircleX, Info, TriangleAlert } from "lucide-react";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  import { createContext, useContext } from "react";
5
6
  import { cva } from "class-variance-authority";
@@ -14,27 +15,39 @@ function useBrand() {
14
15
  }
15
16
  //#endregion
16
17
  //#region src/components/ui/brand-feedback-icon.tsx
18
+ var sizes = {
19
+ sm: "size-icon-sm",
20
+ md: "size-icon-md",
21
+ lg: "size-icon-lg",
22
+ xl: "size-12",
23
+ "2xl": "size-16"
24
+ };
17
25
  var iconVariants = cva("relative inline-flex shrink-0", {
18
- variants: {
19
- type: {
20
- success: "text-success",
21
- error: "text-destructive",
22
- warning: "text-warning",
23
- info: "text-info"
24
- },
25
- size: {
26
- sm: "size-icon-sm",
27
- md: "size-icon-md",
28
- lg: "size-icon-lg",
29
- xl: "size-12",
30
- "2xl": "size-16"
31
- }
32
- },
33
- defaultVariants: {
34
- type: "info",
35
- size: "md"
36
- }
26
+ variants: { size: sizes },
27
+ defaultVariants: { size: "md" }
37
28
  });
29
+ /** Cor do estado (tinge o símbolo e, em statusBadge, o ícone ao lado). */
30
+ var stateText = {
31
+ success: "text-success",
32
+ error: "text-destructive",
33
+ warning: "text-warning",
34
+ info: "text-info"
35
+ };
36
+ /** Ícone Lucide do selo ao lado do símbolo (statusBadge). */
37
+ var badgeIcons = {
38
+ success: CircleCheck,
39
+ error: CircleX,
40
+ warning: TriangleAlert,
41
+ info: Info
42
+ };
43
+ /** Tamanho do ícone ao lado, proporcional ao do símbolo. */
44
+ var badgeIconSize = {
45
+ sm: "size-3",
46
+ md: "size-icon-sm",
47
+ lg: "size-icon-md",
48
+ xl: "size-icon-lg",
49
+ "2xl": "size-icon-lg"
50
+ };
38
51
  var badgeTone = {
39
52
  success: "bg-success text-success-foreground",
40
53
  error: "bg-destructive text-destructive-foreground",
@@ -100,7 +113,7 @@ function StatusGlyph({ type, animated, ...props }) {
100
113
  * semântica, com um selo de status para que o significado não dependa só da cor.
101
114
  * Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).
102
115
  */
103
- function BrandFeedbackIcon({ type, size, label, animated = false, className }) {
116
+ function BrandFeedbackIcon({ type, size, label, animated = false, symbolTone = "state", statusBadge = false, animation = "pop", className }) {
104
117
  const { brand } = useBrand();
105
118
  const Custom = brand.feedbackIcons?.[type];
106
119
  const Symbol = brand.symbol;
@@ -108,19 +121,26 @@ function BrandFeedbackIcon({ type, size, label, animated = false, className }) {
108
121
  role: "img",
109
122
  "aria-label": label
110
123
  } : { "aria-hidden": true };
124
+ const encaixe = animation === "encaixe";
125
+ const rootTone = symbolTone === "state" ? stateText[type] : void 0;
126
+ const symbolTonal = symbolTone === "brand" && "text-primary-text";
127
+ const symbolAnim = animated && (encaixe ? "animate-encaixe" : "animate-pop");
128
+ const badgeAnim = animated && (encaixe ? "animate-encaixe" : "animate-pop anim-delay-1");
129
+ const BadgeIcon = badgeIcons[type];
130
+ const sideBadge = statusBadge && !Custom;
111
131
  return /* @__PURE__ */ jsx("span", {
112
132
  "data-slot": "feedback-icon",
113
133
  "data-rendra": "BFI-001",
114
- className: cn(iconVariants({
115
- type,
116
- size
117
- }), animated && type === "error" && "animate-shake", className),
134
+ className: cn(sideBadge ? "relative inline-flex shrink-0 items-center gap-1" : iconVariants({ size }), rootTone, animated && !encaixe && type === "error" && "animate-shake", className),
118
135
  ...a11y,
119
- children: Custom ? /* @__PURE__ */ jsx(Custom, { className: cn("size-full", animated && "animate-pop") }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Symbol, { className: cn("size-full mask-status-cutout", animated && "animate-pop") }), /* @__PURE__ */ jsx("span", {
120
- className: cn("absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full", badgeTone[type], animated && "animate-pop anim-delay-1"),
136
+ children: Custom ? /* @__PURE__ */ jsx(Custom, { className: cn("size-full", symbolTonal, symbolAnim) }) : sideBadge ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Symbol, { className: cn(sizes[size ?? "md"], symbolTonal, symbolAnim) }), /* @__PURE__ */ jsx(BadgeIcon, {
137
+ "aria-hidden": true,
138
+ className: cn("shrink-0", badgeIconSize[size ?? "md"], stateText[type], badgeAnim)
139
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Symbol, { className: cn("size-full mask-status-cutout", symbolTonal, symbolAnim) }), /* @__PURE__ */ jsx("span", {
140
+ className: cn("absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full", badgeTone[type], badgeAnim),
121
141
  children: /* @__PURE__ */ jsx(StatusGlyph, {
122
142
  type,
123
- animated,
143
+ animated: animated && !encaixe,
124
144
  className: "size-full"
125
145
  })
126
146
  })] })
@@ -165,4 +185,4 @@ function EmptyState({ title, description, type = "info", actions, size = "defaul
165
185
  //#endregion
166
186
  export { BrandContext as a, useBrand as i, BrandFeedbackIcon as n, feedbackLabels as r, EmptyState as t };
167
187
 
168
- //# sourceMappingURL=empty-state-Be_tReTU.js.map
188
+ //# sourceMappingURL=empty-state-CdaDLcsB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state-CdaDLcsB.js","names":[],"sources":["../src/brand/brand-context.ts","../src/brand/use-brand.ts","../src/components/ui/brand-feedback-icon.tsx","../src/components/ui/empty-state.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { Shape } from '@/lib/shape'\nimport type { BrandConfig, PaletteConfig } from './types'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nexport interface BrandContextValue {\n brand: BrandConfig\n brands: BrandConfig[]\n setBrandId: (id: string) => void\n mode: ColorMode\n resolvedMode: 'light' | 'dark'\n setMode: (mode: ColorMode) => void\n /** Formato do template ativo (fixo por template). */\n shape: Shape\n /** Paleta de cores em uso (por padrão, a do próprio modelo). */\n palette: PaletteConfig\n palettes: PaletteConfig[]\n /** Usa a paleta de outro template; null volta às cores do próprio modelo. */\n setPaletteId: (id: string | null) => void\n /** Logotipo adequado para a sidebar com a paleta atual. */\n sidebarLogoVariant: 'light' | 'dark'\n}\n\nexport const BrandContext = createContext<BrandContextValue | null>(null)\n","import { useContext } from 'react'\nimport { BrandContext } from './brand-context'\n\nexport function useBrand() {\n const ctx = useContext(BrandContext)\n if (!ctx) throw new Error('useBrand precisa estar dentro de <BrandProvider>.')\n return ctx\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport { CircleCheck, CircleX, Info, TriangleAlert, type LucideIcon } from 'lucide-react'\nimport type { SVGProps } from 'react'\nimport { useBrand } from '@/brand/use-brand'\nimport type { FeedbackType } from '@/brand/types'\nimport { cn } from '@/lib/cn'\n\nconst sizes = {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n xl: 'size-12',\n '2xl': 'size-16',\n} as const\n\nconst iconVariants = cva('relative inline-flex shrink-0', {\n variants: { size: sizes },\n defaultVariants: { size: 'md' },\n})\n\n/** Cor do estado (tinge o símbolo e, em statusBadge, o ícone ao lado). */\nconst stateText: Record<FeedbackType, string> = {\n success: 'text-success',\n error: 'text-destructive',\n warning: 'text-warning',\n info: 'text-info',\n}\n\n/** Ícone Lucide do selo ao lado do símbolo (statusBadge). */\nconst badgeIcons: Record<FeedbackType, LucideIcon> = {\n success: CircleCheck,\n error: CircleX,\n warning: TriangleAlert,\n info: Info,\n}\n\n/** Tamanho do ícone ao lado, proporcional ao do símbolo. */\nconst badgeIconSize: Record<keyof typeof sizes, string> = {\n sm: 'size-3',\n md: 'size-icon-sm',\n lg: 'size-icon-md',\n xl: 'size-icon-lg',\n '2xl': 'size-icon-lg',\n}\n\nconst badgeTone: Record<FeedbackType, string> = {\n success: 'bg-success text-success-foreground',\n error: 'bg-destructive text-destructive-foreground',\n warning: 'bg-warning text-warning-foreground',\n info: 'bg-info text-info-foreground',\n}\n\nexport const feedbackLabels: Record<FeedbackType, string> = {\n success: 'Sucesso',\n error: 'Erro',\n warning: 'Atenção',\n info: 'Informação',\n}\n\n/** Glifos universais de status desenhados em traço, herdando currentColor. */\nfunction StatusGlyph({\n type,\n animated,\n ...props\n}: { type: FeedbackType; animated?: boolean } & SVGProps<SVGSVGElement>) {\n // pathLength=1 normaliza o traço: o desenho anima de 1 a 0 em qualquer glifo.\n const draw = animated\n ? ({ pathLength: 1, strokeDasharray: 1, className: 'animate-draw' } as const)\n : {}\n const common = {\n viewBox: '0 0 12 12',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n 'aria-hidden': true,\n ...props,\n }\n switch (type) {\n case 'success':\n return (\n <svg {...common}>\n <path d=\"M3 6.2 5.1 8.2 9 4\" {...draw} />\n </svg>\n )\n case 'error':\n return (\n <svg {...common}>\n <path d=\"M4 4l4 4M8 4 4 8\" {...draw} />\n </svg>\n )\n case 'warning':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v3.3M6 8.8v0\" {...draw} />\n </svg>\n )\n case 'info':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v0M6 5.5v3.3\" {...draw} />\n </svg>\n )\n }\n}\n\nexport interface BrandFeedbackIconProps extends VariantProps<typeof iconVariants> {\n type: FeedbackType\n /** Texto para leitores de tela. Sem ele o ícone é decorativo (o texto ao lado informa). */\n label?: string\n /**\n * Anima a entrada: o símbolo entra com mola e o glifo se desenha (check se formando,\n * X se riscando). O erro treme ao final. Respeita reduzir movimento.\n * Troque a key do componente para repetir a animação.\n */\n animated?: boolean\n /**\n * Cor do símbolo. `state` (padrão): a cor do estado (verde, vermelho, amarelo, azul).\n * `brand`: a cor primária da marca (`text-primary-text`); o selo de estado mantém a cor do\n * estado, para o significado não depender só da cor.\n */\n symbolTone?: 'state' | 'brand'\n /**\n * `false` (padrão): selo de estado sobreposto ao símbolo. `true`: o ícone do estado\n * (Lucide, na cor do estado) fica ao lado do símbolo, no lugar do selo sobreposto. Com um\n * SVG próprio em `brand.feedbackIcons[type]`, o SVG vence e nenhum selo é desenhado.\n */\n statusBadge?: boolean\n /**\n * Só vale com `animated`. `pop` (padrão): entra com mola, o glifo se desenha e o erro\n * treme. `encaixe`: entra em 240 ms, curva de saída suave, sem quique, sem desenho e sem\n * tremor. Com \"reduzir movimento\" do sistema, nenhuma animação roda.\n */\n animation?: 'pop' | 'encaixe'\n className?: string\n}\n\n/**\n * Ícone de feedback da marca: o símbolo da marca em currentColor, tingido pela cor\n * semântica, com um selo de status para que o significado não dependa só da cor.\n * Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).\n */\nexport function BrandFeedbackIcon({\n type,\n size,\n label,\n animated = false,\n symbolTone = 'state',\n statusBadge = false,\n animation = 'pop',\n className,\n}: BrandFeedbackIconProps) {\n const { brand } = useBrand()\n const Custom = brand.feedbackIcons?.[type]\n const Symbol = brand.symbol\n const a11y = label\n ? ({ role: 'img', 'aria-label': label } as const)\n : ({ 'aria-hidden': true } as const)\n const encaixe = animation === 'encaixe'\n // Com `symbolTone=\"state\"` o texto do estado fica na raiz (como sempre); com `brand` sai da raiz.\n const rootTone = symbolTone === 'state' ? stateText[type] : undefined\n const symbolTonal = symbolTone === 'brand' && 'text-primary-text'\n const symbolAnim = animated && (encaixe ? 'animate-encaixe' : 'animate-pop')\n const badgeAnim = animated && (encaixe ? 'animate-encaixe' : 'animate-pop anim-delay-1')\n const BadgeIcon = badgeIcons[type]\n const sideBadge = statusBadge && !Custom\n\n return (\n <span\n data-slot=\"feedback-icon\"\n data-rendra=\"BFI-001\"\n className={cn(\n sideBadge ? 'relative inline-flex shrink-0 items-center gap-1' : iconVariants({ size }),\n rootTone,\n animated && !encaixe && type === 'error' && 'animate-shake',\n className,\n )}\n {...a11y}\n >\n {Custom ? (\n <Custom className={cn('size-full', symbolTonal, symbolAnim)} />\n ) : sideBadge ? (\n <>\n <Symbol className={cn(sizes[size ?? 'md'], symbolTonal, symbolAnim)} />\n <BadgeIcon\n aria-hidden\n className={cn('shrink-0', badgeIconSize[size ?? 'md'], stateText[type], badgeAnim)}\n />\n </>\n ) : (\n <>\n <Symbol className={cn('size-full mask-status-cutout', symbolTonal, symbolAnim)} />\n <span\n className={cn(\n 'absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full',\n badgeTone[type],\n badgeAnim,\n )}\n >\n <StatusGlyph type={type} animated={animated && !encaixe} className=\"size-full\" />\n </span>\n </>\n )}\n </span>\n )\n}\n","import type { ReactNode } from 'react'\nimport type { FeedbackType } from '@/brand/types'\nimport { BrandFeedbackIcon } from '@/components/ui/brand-feedback-icon'\nimport { cn } from '@/lib/cn'\n\nexport interface EmptyStateProps {\n title: string\n description?: ReactNode\n /** Tipo do ícone de feedback da marca. info para \"sem dados\", error para falha. */\n type?: FeedbackType\n /** Ações: no máximo uma principal e uma secundária. */\n actions?: ReactNode\n /** compact: dentro de tabela ou card pequeno. */\n size?: 'default' | 'compact'\n /**\n * Classes extras, que vencem as do componente. Para zerar o respiro vertical (por exemplo,\n * dentro de um card que já tem o seu) use `className=\"py-0 md:py-0\"`.\n */\n className?: string\n}\n\n/** Estado vazio (e de erro) com o ícone de feedback da marca. */\nexport function EmptyState({\n title,\n description,\n type = 'info',\n actions,\n size = 'default',\n className,\n}: EmptyStateProps) {\n return (\n <div\n data-rendra=\"VAZ-001\"\n className={cn(\n 'flex flex-col items-center justify-center gap-4 text-center',\n size === 'compact' ? 'px-4 py-8' : 'px-4 py-12 md:py-16',\n className,\n )}\n >\n <span className=\"relative flex items-center justify-center\">\n <span aria-hidden className=\"absolute size-16 rounded-full bg-gradient-soft\" />\n <BrandFeedbackIcon\n type={type}\n size={size === 'compact' ? 'xl' : '2xl'}\n className=\"relative\"\n />\n </span>\n <div className=\"flex w-full flex-col gap-1\">\n <p className=\"text-base font-semibold\">{title}</p>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap justify-center gap-3\">{actions}</div>}\n </div>\n )\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- /*! Rendra Design System v2.2.2 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-Bo1YMupg.js";
3
- import { a as catalogByComponent, c as findComponentsWithoutDefault, d as findPresetCollisions, f as getCatalogEntry, i as assertCatalogIntegrity, l as findDuplicateCodes, n as CATALOG_EXCLUDED_FILES, o as filesMissingCatalogEntry, p as resolveCatalogCode, r as COMPONENT_CODE_PATTERN, s as findComponentsWithMultipleDefaults, t as CATALOG, u as findInvalidFormatCodes } from "./components-Dl15AjD-.js";
4
- import { i as usePrefersReducedMotion, n as buttonVariants, r as Spinner, t as Button } from "./button-BgSglLWN.js";
5
- import { a as DropdownMenuContent, c as DropdownMenuLabel, d as DropdownMenuSeparator, f as DropdownMenuSub, h as DropdownMenuTrigger, i as DropdownMenuCheckboxItem, l as DropdownMenuRadioGroup, m as DropdownMenuSubTrigger, n as TooltipProvider, o as DropdownMenuGroup, p as DropdownMenuSubContent, r as DropdownMenu, s as DropdownMenuItem, t as Tooltip, u as DropdownMenuRadioItem } from "./tooltip-dC9gqkuv.js";
6
- import { a as BrandContext, i as useBrand, n as BrandFeedbackIcon, r as feedbackLabels, t as EmptyState } from "./empty-state-Be_tReTU.js";
7
- import { a as useRendraNavigate, i as useRendraLink, n as useCurrentPath, r as useRendraBreadcrumbs, t as RendraProvider } from "./rendra-provider-DPulIoQB.js";
8
- import { n as useBreakpoint, t as breakpoints } from "./use-breakpoint-BUHd0Cbz.js";
1
+ /*! Rendra Design System v2.4.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-DXDpTGsu.js";
3
+ import { a as catalogByComponent, c as findComponentsWithoutDefault, d as findPresetCollisions, f as getCatalogEntry, i as assertCatalogIntegrity, l as findDuplicateCodes, n as CATALOG_EXCLUDED_FILES, o as filesMissingCatalogEntry, p as resolveCatalogCode, r as COMPONENT_CODE_PATTERN, s as findComponentsWithMultipleDefaults, t as CATALOG, u as findInvalidFormatCodes } from "./components-hp7iWioq.js";
4
+ import { i as usePrefersReducedMotion, n as buttonVariants, r as Spinner, t as Button } from "./button-C6rjowQi.js";
5
+ import { a as DropdownMenuContent, c as DropdownMenuLabel, d as DropdownMenuSeparator, f as DropdownMenuSub, h as DropdownMenuTrigger, i as DropdownMenuCheckboxItem, l as DropdownMenuRadioGroup, m as DropdownMenuSubTrigger, n as TooltipProvider, o as DropdownMenuGroup, p as DropdownMenuSubContent, r as DropdownMenu, s as DropdownMenuItem, t as Tooltip, u as DropdownMenuRadioItem } from "./tooltip-bdmbX6vC.js";
6
+ import { a as BrandContext, i as useBrand, n as BrandFeedbackIcon, r as feedbackLabels, t as EmptyState } from "./empty-state-CdaDLcsB.js";
7
+ import { a as useRendraNavigate, i as useRendraLink, n as useCurrentPath, r as useRendraBreadcrumbs, t as RendraProvider } from "./rendra-provider-7jUgUHzR.js";
8
+ import { n as useBreakpoint, t as breakpoints } from "./use-breakpoint-Dyp-0E_c.js";
9
9
  import { ArrowDown, ArrowDownRight, ArrowLeft, ArrowRight, ArrowRightLeft, ArrowUp, ArrowUpRight, Bell, Bot, CalendarDays, Check, CheckCheck, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, Clock, Columns3, Download, EllipsisVertical, Eye, EyeOff, FileText, Film, Filter, Globe, GripVertical, Home, ImageIcon, ImagePlus, Info, LayoutTemplate, LogOut, Mail, MapPin, Menu, MessageCircle, MessagesSquare, Mic, Minus, Monitor, Moon, MoreHorizontal, Palette, PanelLeft, Paperclip, Pause, Pencil, Phone, Play, Plus, Reply, RotateCw, Search, Send, SkipForward, Smartphone, Smile, Star, Sun, Trash2, TriangleAlert, UploadCloud, User, VideoIcon, X, XCircle, Zap } from "lucide-react";
10
10
  import { Accordion as Accordion$1, Avatar as Avatar$1, Checkbox as Checkbox$1, Collapsible, ContextMenu, Dialog, Label as Label$1, NavigationMenu, Popover as Popover$1, Progress as Progress$1, RadioGroup as RadioGroup$1, Slider as Slider$1, Slot, Switch as Switch$1, Tabs as Tabs$1, VisuallyHidden } from "radix-ui";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -182,6 +182,13 @@ function Alert({ type, title, description, action, onDismiss, animated = false,
182
182
  });
183
183
  }
184
184
  //#endregion
185
+ //#region src/lib/initials.ts
186
+ /** Iniciais de um nome: primeira letra do primeiro e do último nome, em maiúsculas. */
187
+ function initials(name) {
188
+ const parts = name.trim().split(/\s+/);
189
+ return ((parts[0]?.charAt(0) ?? "") + (parts.length > 1 ? parts[parts.length - 1]?.charAt(0) ?? "" : "")).toUpperCase();
190
+ }
191
+ //#endregion
185
192
  //#region src/components/ui/avatar.tsx
186
193
  var avatarVariants = cva("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-avatar bg-primary-soft font-medium text-primary-soft-foreground select-none", {
187
194
  variants: { size: {
@@ -191,10 +198,6 @@ var avatarVariants = cva("relative inline-flex shrink-0 items-center justify-cen
191
198
  } },
192
199
  defaultVariants: { size: "md" }
193
200
  });
194
- function initials(name) {
195
- const parts = name.trim().split(/\s+/);
196
- return ((parts[0]?.charAt(0) ?? "") + (parts.length > 1 ? parts[parts.length - 1]?.charAt(0) ?? "" : "")).toUpperCase();
197
- }
198
201
  function Avatar({ name, src, size, className }) {
199
202
  return /* @__PURE__ */ jsxs(Avatar$1.Root, {
200
203
  "data-rendra": "AVT-001",
@@ -362,13 +365,17 @@ function BrandLogo({ on = "surface", symbolOnly, size = "md", className }) {
362
365
  }),
363
366
  !symbolOnly && /* @__PURE__ */ jsxs("span", {
364
367
  className: "truncate text-lg leading-none tracking-tight",
365
- children: [/* @__PURE__ */ jsx("span", {
366
- className: cn("font-semibold", strong),
367
- children: first
368
- }), rest.length > 0 && /* @__PURE__ */ jsxs("span", {
369
- className: cn("font-normal", soft),
370
- children: [" ", rest.join(" ")]
371
- })]
368
+ children: [
369
+ /* @__PURE__ */ jsx("span", {
370
+ className: cn("font-semibold", strong),
371
+ children: first
372
+ }),
373
+ rest.length > 0 && " ",
374
+ rest.length > 0 && /* @__PURE__ */ jsx("span", {
375
+ className: cn("font-normal", soft),
376
+ children: rest.join(" ")
377
+ })
378
+ ]
372
379
  }),
373
380
  symbolOnly && /* @__PURE__ */ jsx("span", {
374
381
  className: "sr-only",
@@ -468,11 +475,11 @@ function Breadcrumb({ items, variant = "responsive", className }) {
468
475
  * ButtonGroup único: agrupa botões de ação (children) ou funciona como controle
469
476
  * segmentado de escolha única (options + value).
470
477
  */
471
- function ButtonGroup({ children, options, value, onChange, size = "md", fullWidth, className, ...aria }) {
478
+ function ButtonGroup({ children, options, value, onChange, size = "md", fullWidth, orientation = "horizontal", className, ...aria }) {
472
479
  if (options) return /* @__PURE__ */ jsx("div", {
473
480
  role: "radiogroup",
474
481
  "aria-label": aria["aria-label"],
475
- "data-rendra": "BTNG-001",
482
+ "data-rendra": resolveCatalogCode("ButtonGroup", { orientation: "horizontal" }),
476
483
  className: cn("inline-flex max-w-full flex-wrap gap-1 rounded-control border bg-muted p-1", fullWidth && "flex w-full", className),
477
484
  children: options.map((o) => {
478
485
  const active = o.value === value;
@@ -487,18 +494,34 @@ function ButtonGroup({ children, options, value, onChange, size = "md", fullWidt
487
494
  }, o.value);
488
495
  })
489
496
  });
497
+ const vertical = orientation === "vertical";
498
+ const responsive = orientation === "responsive";
490
499
  return /* @__PURE__ */ jsx("div", {
491
500
  role: "group",
492
501
  "aria-label": aria["aria-label"],
493
- "data-rendra": "BTNG-001",
494
- className: cn("inline-flex max-w-full", "[&>*]:rounded-none [&>*]:shadow-none [&>*:first-child]:rounded-l-control [&>*:focus-visible]:z-10 [&>*:last-child]:rounded-r-control [&>*:not(:first-child)]:-ml-px", fullWidth && "flex w-full [&>*]:flex-1", className),
502
+ "data-rendra": resolveCatalogCode("ButtonGroup", { orientation }),
503
+ className: cn("inline-flex max-w-full", "[&>*]:rounded-none [&>*]:shadow-none [&>*:focus-visible]:z-10", orientation === "horizontal" && "[&>*:first-child]:rounded-l-control [&>*:last-child]:rounded-r-control [&>*:not(:first-child)]:-ml-px", vertical && "flex-col [&>*]:w-full [&>*:first-child]:rounded-t-control [&>*:last-child]:rounded-b-control [&>*:not(:first-child)]:-mt-px", responsive && "flex-col md:flex-row [&>*]:w-full md:[&>*]:w-auto [&>*:first-child]:rounded-t-control md:[&>*:first-child]:rounded-l-control md:[&>*:first-child]:rounded-tr-none [&>*:last-child]:rounded-b-control md:[&>*:last-child]:rounded-r-control md:[&>*:last-child]:rounded-bl-none [&>*:not(:first-child)]:-mt-px md:[&>*:not(:first-child)]:mt-0 md:[&>*:not(:first-child)]:-ml-px", fullWidth && (vertical ? "flex w-full" : "flex w-full [&>*]:flex-1"), className),
495
504
  children
496
505
  });
497
506
  }
498
507
  //#endregion
499
508
  //#region src/components/ui/overlay-shell.tsx
500
509
  function OverlayShell({ title, description, icon, children, footer, hideHeader, onRequestClose, bodyClassName }) {
510
+ const closeButton = /* @__PURE__ */ jsx("button", {
511
+ type: "button",
512
+ onClick: onRequestClose,
513
+ "aria-label": "Fechar",
514
+ className: cn("-mt-1 -mr-2 flex size-touch shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors hover:bg-accent hover:text-foreground md:size-control-md", hideHeader && "pointer-events-auto m-0 cursor-default opacity-0 focus-visible:cursor-pointer focus-visible:opacity-100"),
515
+ children: /* @__PURE__ */ jsx(X, {
516
+ className: "size-icon-md",
517
+ "aria-hidden": true
518
+ })
519
+ });
501
520
  return /* @__PURE__ */ jsxs(Fragment, { children: [
521
+ hideHeader && /* @__PURE__ */ jsx("div", {
522
+ className: "pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-end px-2 pt-safe md:px-4 md:pt-4",
523
+ children: closeButton
524
+ }),
502
525
  /* @__PURE__ */ jsxs("header", {
503
526
  className: cn("flex shrink-0 items-start gap-3 border-b px-4 pt-safe pb-4 md:px-6 md:pt-6", hideHeader && "sr-only"),
504
527
  children: [
@@ -520,16 +543,7 @@ function OverlayShell({ title, description, icon, children, footer, hideHeader,
520
543
  children: title
521
544
  })]
522
545
  }),
523
- /* @__PURE__ */ jsx("button", {
524
- type: "button",
525
- onClick: onRequestClose,
526
- "aria-label": "Fechar",
527
- className: "-mt-1 -mr-2 flex size-touch shrink-0 cursor-pointer items-center justify-center rounded-item text-muted-foreground transition-colors hover:bg-accent hover:text-foreground md:size-control-md",
528
- children: /* @__PURE__ */ jsx(X, {
529
- className: "size-icon-md",
530
- "aria-hidden": true
531
- })
532
- })
546
+ !hideHeader && closeButton
533
547
  ]
534
548
  }),
535
549
  /* @__PURE__ */ jsx("div", {
@@ -554,7 +568,7 @@ var feedback = {
554
568
  destructive: "error",
555
569
  info: "info"
556
570
  };
557
- function Modal({ open, onOpenChange, title, description, type = "confirm", size = "sm", children, confirmLabel = type === "destructive" ? "Excluir" : type === "info" ? "Entendi" : "Confirmar", cancelLabel = "Cancelar", onConfirm, formId, loading }) {
571
+ function Modal({ open, onOpenChange, title, description, type = "confirm", size = "sm", children, confirmLabel = type === "destructive" ? "Excluir" : type === "info" ? "Entendi" : "Confirmar", cancelLabel, onConfirm, formId, loading }) {
558
572
  const [busy, setBusy] = useState(false);
559
573
  const close = () => onOpenChange(false);
560
574
  const confirm = async () => {
@@ -590,8 +604,8 @@ function Modal({ open, onOpenChange, title, description, type = "confirm", size
590
604
  form: isForm ? formId : void 0,
591
605
  onClick: isForm && formId ? void 0 : confirm
592
606
  },
593
- cancel: type === "info" ? void 0 : {
594
- label: cancelLabel,
607
+ cancel: type === "info" && cancelLabel === void 0 ? void 0 : {
608
+ label: cancelLabel ?? "Cancelar",
595
609
  onClick: close
596
610
  }
597
611
  }),
@@ -1488,6 +1502,12 @@ var Input = forwardRef(function Input({ size, mask, icon, suffix, clearable, inv
1488
1502
  const [showPassword, setShowPassword] = useState(false);
1489
1503
  const [inner, setInner] = useState(String(value ?? defaultValue ?? ""));
1490
1504
  const current = value ?? inner;
1505
+ const [secretDraft, setSecretDraft] = useState("");
1506
+ const [lastIsEditing, setLastIsEditing] = useState(isEditing);
1507
+ if (isEditing !== lastIsEditing) {
1508
+ setLastIsEditing(isEditing);
1509
+ if (isEditing) setSecretDraft("");
1510
+ }
1491
1511
  const cb = useRef({
1492
1512
  onChange,
1493
1513
  onValueChange,
@@ -1536,6 +1556,7 @@ var Input = forwardRef(function Input({ size, mask, icon, suffix, clearable, inv
1536
1556
  const resetValue = () => {
1537
1557
  if (maskRef.current) maskRef.current.value = "";
1538
1558
  setInner("");
1559
+ setSecretDraft("");
1539
1560
  onChange?.("");
1540
1561
  onValueChange?.("", "");
1541
1562
  onCentsChange?.(null);
@@ -1639,8 +1660,9 @@ var Input = forwardRef(function Input({ size, mask, icon, suffix, clearable, inv
1639
1660
  "aria-busy": searching || void 0,
1640
1661
  className: cn(controlInput, inputClassName),
1641
1662
  ...isSecret ? {
1642
- defaultValue: "",
1663
+ value: secretDraft,
1643
1664
  onChange: (e) => {
1665
+ setSecretDraft(e.target.value);
1644
1666
  onChange?.(e.target.value);
1645
1667
  onValueChange?.(e.target.value, e.target.value);
1646
1668
  }
@@ -1688,7 +1710,7 @@ var Input = forwardRef(function Input({ size, mask, icon, suffix, clearable, inv
1688
1710
  className: "shrink-0 text-sm text-muted-foreground",
1689
1711
  children: suffix
1690
1712
  }),
1691
- clearable && current && !disabled && /* @__PURE__ */ jsx("button", {
1713
+ clearable && (isSecret ? secretDraft : current) && !disabled && /* @__PURE__ */ jsx("button", {
1692
1714
  type: "button",
1693
1715
  onClick: clear,
1694
1716
  "aria-label": "Limpar campo",
@@ -4009,10 +4031,11 @@ var content = {
4009
4031
  * Tela de erro única (404 e 500), com o ícone de feedback da marca animado.
4010
4032
  * Ações seguem a regra 30/70: secundária à esquerda, principal à direita.
4011
4033
  */
4012
- function ErrorPage({ code, title, description, fullScreen = false }) {
4034
+ function ErrorPage({ code, title, description, fullScreen = false, back = true }) {
4013
4035
  const Link = useRendraLink();
4014
4036
  const { goBack } = useRendraNavigate();
4015
4037
  const c = content[code];
4038
+ const principal = back ? "col-span-7" : "col-span-10";
4016
4039
  return /* @__PURE__ */ jsx("div", {
4017
4040
  "data-rendra": "ERRO-001",
4018
4041
  className: cn("flex w-full items-center justify-center px-4 py-12", fullScreen ? "min-h-dvh" : "min-h-full"),
@@ -4050,7 +4073,7 @@ function ErrorPage({ code, title, description, fullScreen = false }) {
4050
4073
  }),
4051
4074
  /* @__PURE__ */ jsxs("div", {
4052
4075
  className: "grid w-full grid-cols-10 gap-3",
4053
- children: [/* @__PURE__ */ jsx(Button, {
4076
+ children: [back && /* @__PURE__ */ jsx(Button, {
4054
4077
  variant: "outline",
4055
4078
  icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
4056
4079
  className: "col-span-3",
@@ -4062,12 +4085,12 @@ function ErrorPage({ code, title, description, fullScreen = false }) {
4062
4085
  })
4063
4086
  }), code === 500 ? /* @__PURE__ */ jsx(Button, {
4064
4087
  icon: /* @__PURE__ */ jsx(RotateCw, {}),
4065
- className: "col-span-7",
4088
+ className: principal,
4066
4089
  onClick: () => location.reload(),
4067
4090
  children: "Tentar de novo"
4068
4091
  }) : /* @__PURE__ */ jsx(Button, {
4069
4092
  asChild: true,
4070
- className: "col-span-7",
4093
+ className: principal,
4071
4094
  children: /* @__PURE__ */ jsxs(Link, {
4072
4095
  to: "/",
4073
4096
  children: [/* @__PURE__ */ jsx(Home, { "aria-hidden": true }), "Ir para o painel"]
@@ -4165,6 +4188,8 @@ function Slider({ value, defaultValue = [50], onChange, min = 0, max = 100, step
4165
4188
  }
4166
4189
  //#endregion
4167
4190
  //#region src/components/ui/select.tsx
4191
+ /** Acima deste número de opções, o "Selecionar todos" aparece sem a prop `selectAll`. */
4192
+ var SELECT_ALL_AUTO_MIN = 5;
4168
4193
  /**
4169
4194
  * Select único do sistema. Busca, múltiplo, selecionar todos, contador, criar opção,
4170
4195
  * busca remota e carregamento são props. No mobile abre como bottom sheet.
@@ -4236,6 +4261,7 @@ var Select = forwardRef(function Select(props, ref) {
4236
4261
  };
4237
4262
  const enabled = all.filter((o) => !o.disabled);
4238
4263
  const allSelected = enabled.length > 0 && enabled.every((o) => multiValue.includes(o.value));
4264
+ const showSelectAll = multiple && (props.selectAll ?? all.length > SELECT_ALL_AUTO_MIN);
4239
4265
  const toggleAll = () => {
4240
4266
  const next = allSelected ? [] : enabled.map((o) => o.value);
4241
4267
  if (isMobile) setDraft(next);
@@ -4376,11 +4402,11 @@ var Select = forwardRef(function Select(props, ref) {
4376
4402
  })]
4377
4403
  })]
4378
4404
  }),
4379
- multiple && props.selectAll && !q && enabled.length > 0 && /* @__PURE__ */ jsxs(Command.Item, {
4405
+ showSelectAll && !q && enabled.length > 0 && /* @__PURE__ */ jsxs(Command.Item, {
4380
4406
  value: "__todos__",
4381
4407
  onSelect: toggleAll,
4382
4408
  className: cn(itemClass, "font-medium"),
4383
- children: [checkbox(allSelected), "Selecionar todos"]
4409
+ children: [checkbox(allSelected), allSelected ? "Desmarcar todos" : "Selecionar todos"]
4384
4410
  }),
4385
4411
  Object.entries(groups).map(([group, opts]) => /* @__PURE__ */ jsx(Command.Group, {
4386
4412
  heading: group || void 0,
@@ -8318,7 +8344,7 @@ function Stepper({ steps, current, errors = [], orientation = "horizontal", onSt
8318
8344
  });
8319
8345
  }
8320
8346
  /** Cadastro em etapas, com validação por etapa e rodapé Voltar / Avançar. */
8321
- function Wizard({ steps, children, onValidateStep, onFinish, onCancel, finishLabel = "Concluir", orientation = "horizontal", stickyFooter = false }) {
8347
+ function Wizard({ steps, children, onValidateStep, onFinish, onCancel, cancelLabel = "Cancelar", backLabel = "Voltar", nextLabel = "Avançar", finishLabel = "Concluir", onSkip, skipLabel = "Pular", orientation = "horizontal", stickyFooter = false }) {
8322
8348
  const [current, setCurrent] = useState(0);
8323
8349
  const [errors, setErrors] = useState([]);
8324
8350
  const [busy, setBusy] = useState(false);
@@ -8337,6 +8363,11 @@ function Wizard({ steps, children, onValidateStep, onFinish, onCancel, finishLab
8337
8363
  setBusy(false);
8338
8364
  }
8339
8365
  };
8366
+ const skip = () => {
8367
+ setErrors((e) => e.filter((x) => x !== current));
8368
+ setCurrent((c) => c + 1);
8369
+ onSkip?.();
8370
+ };
8340
8371
  const body = /* @__PURE__ */ jsx("div", {
8341
8372
  className: "flex min-w-0 flex-col gap-6",
8342
8373
  children: children[current]
@@ -8356,15 +8387,19 @@ function Wizard({ steps, children, onValidateStep, onFinish, onCancel, finishLab
8356
8387
  children: [body, /* @__PURE__ */ jsx(ActionBar, {
8357
8388
  sticky: stickyFooter,
8358
8389
  cancel: current > 0 ? {
8359
- label: "Voltar",
8390
+ label: backLabel,
8360
8391
  icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
8361
8392
  onClick: () => setCurrent((c) => c - 1)
8362
8393
  } : onCancel ? {
8363
- label: "Cancelar",
8394
+ label: cancelLabel,
8364
8395
  onClick: onCancel
8365
8396
  } : void 0,
8397
+ secondary: onSkip && !last ? [{
8398
+ label: skipLabel,
8399
+ onClick: skip
8400
+ }] : void 0,
8366
8401
  primary: {
8367
- label: last ? finishLabel : "Avançar",
8402
+ label: last ? finishLabel : nextLabel,
8368
8403
  icon: last ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(ArrowRight, {}),
8369
8404
  onClick: next,
8370
8405
  loading: busy,
@@ -8537,7 +8572,7 @@ function Section({ title, description, actions, level = 2, gap = "4", className,
8537
8572
  * O título fica no header (com a trilha); aqui ele só é lido por leitores de tela,
8538
8573
  * para que cada página tenha um único título principal (h1).
8539
8574
  */
8540
- function PageHeader({ title, showTitle = false, description, help, breadcrumb, actions, children, className }) {
8575
+ function PageHeader({ title, showTitle = false, description, help, breadcrumb, actions, bodyTitle = false, titleSpace = "reserve", children, className }) {
8541
8576
  const shell = useContext(ShellContext);
8542
8577
  const setPageHelp = shell?.setPageHelp;
8543
8578
  useEffect(() => {
@@ -8546,18 +8581,24 @@ function PageHeader({ title, showTitle = false, description, help, breadcrumb, a
8546
8581
  return () => setPageHelp(null);
8547
8582
  }, [help, setPageHelp]);
8548
8583
  const inlineHelp = help && !shell;
8584
+ const visible = showTitle || bodyTitle || description || actions || breadcrumb || children || inlineHelp;
8585
+ const collapseTitle = titleSpace === "none" && !showTitle && !bodyTitle && !inlineHelp && !description;
8586
+ const titleClass = showTitle || inlineHelp ? "text-2xl md:text-3xl" : bodyTitle ? "text-base md:sr-only" : "sr-only";
8549
8587
  return /* @__PURE__ */ jsxs("header", {
8550
- className: cn("-mb-2 flex min-w-0 flex-col gap-4 md:-mb-4", !(showTitle || description || actions || breadcrumb || children || inlineHelp) && "sr-only", className),
8588
+ className: cn("-mb-2 flex min-w-0 flex-col gap-4 md:-mb-4", !visible && "sr-only", className),
8551
8589
  children: [
8552
8590
  breadcrumb && /* @__PURE__ */ jsx(Breadcrumb, { items: breadcrumb }),
8553
8591
  /* @__PURE__ */ jsxs("div", {
8554
8592
  className: "flex min-w-0 flex-col gap-4 md:flex-row md:items-center md:justify-between md:gap-6",
8555
- children: [/* @__PURE__ */ jsxs("div", {
8593
+ children: [collapseTitle ? /* @__PURE__ */ jsx("h1", {
8594
+ className: "sr-only",
8595
+ children: title
8596
+ }) : /* @__PURE__ */ jsxs("div", {
8556
8597
  className: "flex min-w-0 flex-col gap-1",
8557
8598
  children: [/* @__PURE__ */ jsxs("div", {
8558
8599
  className: "flex min-w-0 items-center gap-1",
8559
8600
  children: [/* @__PURE__ */ jsx("h1", {
8560
- className: showTitle || inlineHelp ? "text-2xl md:text-3xl" : "sr-only",
8601
+ className: titleClass,
8561
8602
  children: title
8562
8603
  }), inlineHelp && /* @__PURE__ */ jsx(InfoHint, {
8563
8604
  title: typeof title === "string" ? title : "Sobre esta tela",
@@ -9014,14 +9055,18 @@ function LayoutItems() {
9014
9055
  }
9015
9056
  function UserMenu() {
9016
9057
  const Link = useRendraLink();
9017
- const { user, userMenuItems, onLogout } = useShell();
9058
+ const { user, userMenuItems, onLogout, userMenu, workspaces } = useShell();
9018
9059
  const { brand, brands, setBrandId, palette, palettes, setPaletteId } = useBrand();
9060
+ const showTheme = userMenu?.theme !== false;
9061
+ const showTemplate = userMenu?.template !== false;
9062
+ const showLayout = userMenu?.layout !== false;
9063
+ const showPreferences = showTheme || showTemplate || showLayout;
9019
9064
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
9020
9065
  asChild: true,
9021
9066
  children: /* @__PURE__ */ jsx(Button, {
9022
9067
  variant: "ghost",
9023
9068
  iconOnly: true,
9024
- "aria-label": `Menu de ${user.name}`,
9069
+ "aria-label": `${initials(user.name)}, menu de ${user.name}`,
9025
9070
  children: /* @__PURE__ */ jsx(Avatar, {
9026
9071
  name: user.name,
9027
9072
  src: user.avatarUrl
@@ -9040,6 +9085,18 @@ function UserMenu() {
9040
9085
  children: user.email
9041
9086
  })]
9042
9087
  }),
9088
+ workspaces && workspaces.items.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
9089
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9090
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: workspaces.label ?? "Espaço" }),
9091
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
9092
+ value: workspaces.activeId,
9093
+ onValueChange: workspaces.onSelect,
9094
+ children: workspaces.items.map((w) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
9095
+ value: w.id,
9096
+ children: w.label
9097
+ }, w.id))
9098
+ })
9099
+ ] }),
9043
9100
  userMenuItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), userMenuItems.map((item) => {
9044
9101
  const Icon = item.icon;
9045
9102
  return item.to ? /* @__PURE__ */ jsx(DropdownMenuItem, {
@@ -9053,9 +9110,9 @@ function UserMenu() {
9053
9110
  children: [Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true }), item.label]
9054
9111
  }, item.label);
9055
9112
  })] }),
9056
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9057
- /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Sun, { "aria-hidden": true }), "Tema"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(ModeItems, {}) })] }),
9058
- /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Palette, { "aria-hidden": true }), "Template"] }), /* @__PURE__ */ jsxs(DropdownMenuSubContent, { children: [
9113
+ showPreferences && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9114
+ showTheme && /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Sun, { "aria-hidden": true }), "Tema"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(ModeItems, {}) })] }),
9115
+ showTemplate && /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Palette, { "aria-hidden": true }), "Template"] }), /* @__PURE__ */ jsxs(DropdownMenuSubContent, { children: [
9059
9116
  /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Modelo" }),
9060
9117
  /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
9061
9118
  value: brand.id,
@@ -9085,7 +9142,7 @@ function UserMenu() {
9085
9142
  }, b.id))
9086
9143
  })
9087
9144
  ] })] }),
9088
- /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(LayoutTemplate, { "aria-hidden": true }), "Layout"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(LayoutItems, {}) })] }),
9145
+ showLayout && /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(LayoutTemplate, { "aria-hidden": true }), "Layout"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(LayoutItems, {}) })] }),
9089
9146
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9090
9147
  /* @__PURE__ */ jsxs(DropdownMenuItem, {
9091
9148
  destructive: true,
@@ -9206,7 +9263,7 @@ function TopNav() {
9206
9263
  });
9207
9264
  }
9208
9265
  function Header() {
9209
- const { layout, setLayout, setMobileNavOpen, setSearchOpen, pageHelp, homeLabel, notifications } = useShell();
9266
+ const { layout, setLayout, setMobileNavOpen, setSearchOpen, pageHelp, homeLabel, notifications, headerLogo } = useShell();
9210
9267
  const { brand, resolvedMode } = useBrand();
9211
9268
  const Link = useRendraLink();
9212
9269
  const crumbs = useRendraBreadcrumbs();
@@ -9218,12 +9275,16 @@ function Header() {
9218
9275
  const topbar = layout.navigation === "topbar";
9219
9276
  const hasParent = Boolean(crumbs.length >= 2 && crumbs[crumbs.length - 2]?.to);
9220
9277
  const menuInFooter = layout.bottomNav || hasParent;
9221
- const pageTitle = /* @__PURE__ */ jsxs("div", {
9222
- className: "flex min-w-0 flex-1 flex-col justify-center",
9278
+ const pageTitle = /* @__PURE__ */ jsxs(Fragment, { children: [headerLogo && /* @__PURE__ */ jsx("span", {
9279
+ "aria-hidden": true,
9280
+ className: "flex min-w-0 flex-1 items-center md:hidden",
9281
+ children: headerLogo
9282
+ }), /* @__PURE__ */ jsxs("div", {
9283
+ className: cn("flex min-w-0 flex-1 flex-col justify-center", headerLogo && "max-md:flex-none"),
9223
9284
  children: [/* @__PURE__ */ jsxs("span", {
9224
9285
  className: "flex min-w-0 items-center gap-1",
9225
9286
  children: [/* @__PURE__ */ jsx("p", {
9226
- className: "truncate text-sm leading-tight font-semibold md:text-base",
9287
+ className: cn("truncate text-sm leading-tight font-semibold md:text-base", headerLogo && "max-md:sr-only"),
9227
9288
  children: crumbs[crumbs.length - 1]?.label ?? brand.productName
9228
9289
  }), pageHelp && /* @__PURE__ */ jsx(InfoHint, {
9229
9290
  title: crumbs[crumbs.length - 1]?.label ?? brand.productName,
@@ -9232,9 +9293,10 @@ function Header() {
9232
9293
  })]
9233
9294
  }), /* @__PURE__ */ jsx(Breadcrumb, {
9234
9295
  items: trail,
9235
- variant: "trail"
9296
+ variant: "trail",
9297
+ className: cn(headerLogo && "max-md:hidden")
9236
9298
  })]
9237
- });
9299
+ })] });
9238
9300
  const collapsed = layout.sidebar === "collapsed";
9239
9301
  return /* @__PURE__ */ jsxs("header", {
9240
9302
  className: "relative z-30 flex h-header shrink-0 items-center gap-1 border-b bg-card px-2 md:gap-2 md:px-4",
@@ -9798,7 +9860,7 @@ function BottomNav({ onMenu }) {
9798
9860
  * Título da aba e meta description (SEO) não são responsabilidade do AppShell: o
9799
9861
  * boilerplate (src/app/app-layout.tsx) aplica isso com useRouteMeta, que lê @/config/seo.
9800
9862
  */
9801
- function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenuItems = [], onLogout, homeLabel = "Início", quickActions = [], notifications, userConfigurable = true, children }) {
9863
+ function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenuItems = [], onLogout, homeLabel = "Início", quickActions = [], notifications, userMenu, workspaces, headerLogo, userConfigurable = true, children }) {
9802
9864
  const [userLayout, setUserLayout] = useState(() => userConfigurable ? readUserLayout() : {});
9803
9865
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
9804
9866
  const [searchOpen, setSearchOpen] = useState(false);
@@ -9861,7 +9923,10 @@ function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenu
9861
9923
  onLogout,
9862
9924
  homeLabel,
9863
9925
  quickActions,
9864
- notifications
9926
+ notifications,
9927
+ userMenu,
9928
+ workspaces,
9929
+ headerLogo
9865
9930
  };
9866
9931
  }, [
9867
9932
  layout,
@@ -9878,7 +9943,10 @@ function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenu
9878
9943
  onLogout,
9879
9944
  homeLabel,
9880
9945
  quickActions,
9881
- notifications
9946
+ notifications,
9947
+ userMenu,
9948
+ workspaces,
9949
+ headerLogo
9882
9950
  ]);
9883
9951
  return /* @__PURE__ */ jsx(ShellContext.Provider, {
9884
9952
  value,