@rendra-ui/web 2.3.0 → 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 (49) hide show
  1. package/README.md +83 -60
  2. package/bin/rendra.mjs +19 -7
  3. package/dist/base.css +1 -1
  4. package/dist/{button-DWhICtIG.js → button-C6rjowQi.js} +4 -4
  5. package/dist/{button-DWhICtIG.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-DDZQKUVV.js → cn-DXDpTGsu.js} +2 -2
  17. package/dist/{cn-DDZQKUVV.js.map → cn-DXDpTGsu.js.map} +1 -1
  18. package/dist/{components-Cq_PmN4y.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-C491qfzf.js → empty-state-CdaDLcsB.js} +50 -30
  23. package/dist/empty-state-CdaDLcsB.js.map +1 -0
  24. package/dist/index.js +120 -63
  25. package/dist/index.js.map +1 -1
  26. package/dist/{rendra-provider-BRtk5Qa1.js → rendra-provider-7jUgUHzR.js} +2 -2
  27. package/dist/{rendra-provider-BRtk5Qa1.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-DsNlnBSY.js → tooltip-bdmbX6vC.js} +3 -3
  32. package/dist/{tooltip-DsNlnBSY.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/wizard.d.ts +14 -1
  43. package/dist/types/lib/initials.d.ts +2 -0
  44. package/dist/{use-breakpoint-C3ivDt2R.js → use-breakpoint-Dyp-0E_c.js} +2 -2
  45. package/dist/{use-breakpoint-C3ivDt2R.js.map → use-breakpoint-Dyp-0E_c.js.map} +1 -1
  46. package/dist/widget-grid.js +2 -2
  47. package/package.json +2 -1
  48. package/dist/components-Cq_PmN4y.js.map +0 -1
  49. package/dist/empty-state-C491qfzf.js.map +0 -1
@@ -1,5 +1,6 @@
1
- /*! Rendra Design System v2.3.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DDZQKUVV.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-C491qfzf.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.3.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DDZQKUVV.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-Cq_PmN4y.js";
4
- import { i as usePrefersReducedMotion, n as buttonVariants, r as Spinner, t as Button } from "./button-DWhICtIG.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-DsNlnBSY.js";
6
- import { a as BrandContext, i as useBrand, n as BrandFeedbackIcon, r as feedbackLabels, t as EmptyState } from "./empty-state-C491qfzf.js";
7
- import { a as useRendraNavigate, i as useRendraLink, n as useCurrentPath, r as useRendraBreadcrumbs, t as RendraProvider } from "./rendra-provider-BRtk5Qa1.js";
8
- import { n as useBreakpoint, t as breakpoints } from "./use-breakpoint-C3ivDt2R.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
  }),
@@ -4017,10 +4031,11 @@ var content = {
4017
4031
  * Tela de erro única (404 e 500), com o ícone de feedback da marca animado.
4018
4032
  * Ações seguem a regra 30/70: secundária à esquerda, principal à direita.
4019
4033
  */
4020
- function ErrorPage({ code, title, description, fullScreen = false }) {
4034
+ function ErrorPage({ code, title, description, fullScreen = false, back = true }) {
4021
4035
  const Link = useRendraLink();
4022
4036
  const { goBack } = useRendraNavigate();
4023
4037
  const c = content[code];
4038
+ const principal = back ? "col-span-7" : "col-span-10";
4024
4039
  return /* @__PURE__ */ jsx("div", {
4025
4040
  "data-rendra": "ERRO-001",
4026
4041
  className: cn("flex w-full items-center justify-center px-4 py-12", fullScreen ? "min-h-dvh" : "min-h-full"),
@@ -4058,7 +4073,7 @@ function ErrorPage({ code, title, description, fullScreen = false }) {
4058
4073
  }),
4059
4074
  /* @__PURE__ */ jsxs("div", {
4060
4075
  className: "grid w-full grid-cols-10 gap-3",
4061
- children: [/* @__PURE__ */ jsx(Button, {
4076
+ children: [back && /* @__PURE__ */ jsx(Button, {
4062
4077
  variant: "outline",
4063
4078
  icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
4064
4079
  className: "col-span-3",
@@ -4070,12 +4085,12 @@ function ErrorPage({ code, title, description, fullScreen = false }) {
4070
4085
  })
4071
4086
  }), code === 500 ? /* @__PURE__ */ jsx(Button, {
4072
4087
  icon: /* @__PURE__ */ jsx(RotateCw, {}),
4073
- className: "col-span-7",
4088
+ className: principal,
4074
4089
  onClick: () => location.reload(),
4075
4090
  children: "Tentar de novo"
4076
4091
  }) : /* @__PURE__ */ jsx(Button, {
4077
4092
  asChild: true,
4078
- className: "col-span-7",
4093
+ className: principal,
4079
4094
  children: /* @__PURE__ */ jsxs(Link, {
4080
4095
  to: "/",
4081
4096
  children: [/* @__PURE__ */ jsx(Home, { "aria-hidden": true }), "Ir para o painel"]
@@ -8329,7 +8344,7 @@ function Stepper({ steps, current, errors = [], orientation = "horizontal", onSt
8329
8344
  });
8330
8345
  }
8331
8346
  /** Cadastro em etapas, com validação por etapa e rodapé Voltar / Avançar. */
8332
- 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 }) {
8333
8348
  const [current, setCurrent] = useState(0);
8334
8349
  const [errors, setErrors] = useState([]);
8335
8350
  const [busy, setBusy] = useState(false);
@@ -8348,6 +8363,11 @@ function Wizard({ steps, children, onValidateStep, onFinish, onCancel, finishLab
8348
8363
  setBusy(false);
8349
8364
  }
8350
8365
  };
8366
+ const skip = () => {
8367
+ setErrors((e) => e.filter((x) => x !== current));
8368
+ setCurrent((c) => c + 1);
8369
+ onSkip?.();
8370
+ };
8351
8371
  const body = /* @__PURE__ */ jsx("div", {
8352
8372
  className: "flex min-w-0 flex-col gap-6",
8353
8373
  children: children[current]
@@ -8367,15 +8387,19 @@ function Wizard({ steps, children, onValidateStep, onFinish, onCancel, finishLab
8367
8387
  children: [body, /* @__PURE__ */ jsx(ActionBar, {
8368
8388
  sticky: stickyFooter,
8369
8389
  cancel: current > 0 ? {
8370
- label: "Voltar",
8390
+ label: backLabel,
8371
8391
  icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
8372
8392
  onClick: () => setCurrent((c) => c - 1)
8373
8393
  } : onCancel ? {
8374
- label: "Cancelar",
8394
+ label: cancelLabel,
8375
8395
  onClick: onCancel
8376
8396
  } : void 0,
8397
+ secondary: onSkip && !last ? [{
8398
+ label: skipLabel,
8399
+ onClick: skip
8400
+ }] : void 0,
8377
8401
  primary: {
8378
- label: last ? finishLabel : "Avançar",
8402
+ label: last ? finishLabel : nextLabel,
8379
8403
  icon: last ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(ArrowRight, {}),
8380
8404
  onClick: next,
8381
8405
  loading: busy,
@@ -8548,7 +8572,7 @@ function Section({ title, description, actions, level = 2, gap = "4", className,
8548
8572
  * O título fica no header (com a trilha); aqui ele só é lido por leitores de tela,
8549
8573
  * para que cada página tenha um único título principal (h1).
8550
8574
  */
8551
- 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 }) {
8552
8576
  const shell = useContext(ShellContext);
8553
8577
  const setPageHelp = shell?.setPageHelp;
8554
8578
  useEffect(() => {
@@ -8557,18 +8581,24 @@ function PageHeader({ title, showTitle = false, description, help, breadcrumb, a
8557
8581
  return () => setPageHelp(null);
8558
8582
  }, [help, setPageHelp]);
8559
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";
8560
8587
  return /* @__PURE__ */ jsxs("header", {
8561
- 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),
8562
8589
  children: [
8563
8590
  breadcrumb && /* @__PURE__ */ jsx(Breadcrumb, { items: breadcrumb }),
8564
8591
  /* @__PURE__ */ jsxs("div", {
8565
8592
  className: "flex min-w-0 flex-col gap-4 md:flex-row md:items-center md:justify-between md:gap-6",
8566
- children: [/* @__PURE__ */ jsxs("div", {
8593
+ children: [collapseTitle ? /* @__PURE__ */ jsx("h1", {
8594
+ className: "sr-only",
8595
+ children: title
8596
+ }) : /* @__PURE__ */ jsxs("div", {
8567
8597
  className: "flex min-w-0 flex-col gap-1",
8568
8598
  children: [/* @__PURE__ */ jsxs("div", {
8569
8599
  className: "flex min-w-0 items-center gap-1",
8570
8600
  children: [/* @__PURE__ */ jsx("h1", {
8571
- className: showTitle || inlineHelp ? "text-2xl md:text-3xl" : "sr-only",
8601
+ className: titleClass,
8572
8602
  children: title
8573
8603
  }), inlineHelp && /* @__PURE__ */ jsx(InfoHint, {
8574
8604
  title: typeof title === "string" ? title : "Sobre esta tela",
@@ -9025,14 +9055,18 @@ function LayoutItems() {
9025
9055
  }
9026
9056
  function UserMenu() {
9027
9057
  const Link = useRendraLink();
9028
- const { user, userMenuItems, onLogout } = useShell();
9058
+ const { user, userMenuItems, onLogout, userMenu, workspaces } = useShell();
9029
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;
9030
9064
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
9031
9065
  asChild: true,
9032
9066
  children: /* @__PURE__ */ jsx(Button, {
9033
9067
  variant: "ghost",
9034
9068
  iconOnly: true,
9035
- "aria-label": `Menu de ${user.name}`,
9069
+ "aria-label": `${initials(user.name)}, menu de ${user.name}`,
9036
9070
  children: /* @__PURE__ */ jsx(Avatar, {
9037
9071
  name: user.name,
9038
9072
  src: user.avatarUrl
@@ -9051,6 +9085,18 @@ function UserMenu() {
9051
9085
  children: user.email
9052
9086
  })]
9053
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
+ ] }),
9054
9100
  userMenuItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), userMenuItems.map((item) => {
9055
9101
  const Icon = item.icon;
9056
9102
  return item.to ? /* @__PURE__ */ jsx(DropdownMenuItem, {
@@ -9064,9 +9110,9 @@ function UserMenu() {
9064
9110
  children: [Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true }), item.label]
9065
9111
  }, item.label);
9066
9112
  })] }),
9067
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9068
- /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [/* @__PURE__ */ jsx(Sun, { "aria-hidden": true }), "Tema"] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(ModeItems, {}) })] }),
9069
- /* @__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: [
9070
9116
  /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Modelo" }),
9071
9117
  /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
9072
9118
  value: brand.id,
@@ -9096,7 +9142,7 @@ function UserMenu() {
9096
9142
  }, b.id))
9097
9143
  })
9098
9144
  ] })] }),
9099
- /* @__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, {}) })] }),
9100
9146
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
9101
9147
  /* @__PURE__ */ jsxs(DropdownMenuItem, {
9102
9148
  destructive: true,
@@ -9217,7 +9263,7 @@ function TopNav() {
9217
9263
  });
9218
9264
  }
9219
9265
  function Header() {
9220
- const { layout, setLayout, setMobileNavOpen, setSearchOpen, pageHelp, homeLabel, notifications } = useShell();
9266
+ const { layout, setLayout, setMobileNavOpen, setSearchOpen, pageHelp, homeLabel, notifications, headerLogo } = useShell();
9221
9267
  const { brand, resolvedMode } = useBrand();
9222
9268
  const Link = useRendraLink();
9223
9269
  const crumbs = useRendraBreadcrumbs();
@@ -9229,12 +9275,16 @@ function Header() {
9229
9275
  const topbar = layout.navigation === "topbar";
9230
9276
  const hasParent = Boolean(crumbs.length >= 2 && crumbs[crumbs.length - 2]?.to);
9231
9277
  const menuInFooter = layout.bottomNav || hasParent;
9232
- const pageTitle = /* @__PURE__ */ jsxs("div", {
9233
- 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"),
9234
9284
  children: [/* @__PURE__ */ jsxs("span", {
9235
9285
  className: "flex min-w-0 items-center gap-1",
9236
9286
  children: [/* @__PURE__ */ jsx("p", {
9237
- 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"),
9238
9288
  children: crumbs[crumbs.length - 1]?.label ?? brand.productName
9239
9289
  }), pageHelp && /* @__PURE__ */ jsx(InfoHint, {
9240
9290
  title: crumbs[crumbs.length - 1]?.label ?? brand.productName,
@@ -9243,9 +9293,10 @@ function Header() {
9243
9293
  })]
9244
9294
  }), /* @__PURE__ */ jsx(Breadcrumb, {
9245
9295
  items: trail,
9246
- variant: "trail"
9296
+ variant: "trail",
9297
+ className: cn(headerLogo && "max-md:hidden")
9247
9298
  })]
9248
- });
9299
+ })] });
9249
9300
  const collapsed = layout.sidebar === "collapsed";
9250
9301
  return /* @__PURE__ */ jsxs("header", {
9251
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",
@@ -9809,7 +9860,7 @@ function BottomNav({ onMenu }) {
9809
9860
  * Título da aba e meta description (SEO) não são responsabilidade do AppShell: o
9810
9861
  * boilerplate (src/app/app-layout.tsx) aplica isso com useRouteMeta, que lê @/config/seo.
9811
9862
  */
9812
- 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 }) {
9813
9864
  const [userLayout, setUserLayout] = useState(() => userConfigurable ? readUserLayout() : {});
9814
9865
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
9815
9866
  const [searchOpen, setSearchOpen] = useState(false);
@@ -9872,7 +9923,10 @@ function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenu
9872
9923
  onLogout,
9873
9924
  homeLabel,
9874
9925
  quickActions,
9875
- notifications
9926
+ notifications,
9927
+ userMenu,
9928
+ workspaces,
9929
+ headerLogo
9876
9930
  };
9877
9931
  }, [
9878
9932
  layout,
@@ -9889,7 +9943,10 @@ function AppShell({ navigation, layout: layoutProp, user = defaultUser, userMenu
9889
9943
  onLogout,
9890
9944
  homeLabel,
9891
9945
  quickActions,
9892
- notifications
9946
+ notifications,
9947
+ userMenu,
9948
+ workspaces,
9949
+ headerLogo
9893
9950
  ]);
9894
9951
  return /* @__PURE__ */ jsx(ShellContext.Provider, {
9895
9952
  value,