@umodeapporg/ui 0.1.1 → 0.3.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 (41) hide show
  1. package/README.md +78 -0
  2. package/dist/components/Form/ControlledSelect.js +1 -1
  3. package/dist/components/Form/ControlledTabs.js +4 -4
  4. package/dist/components/Form/ControlledTabs.js.map +1 -1
  5. package/dist/components/Form/ControlledViewToggle.js +2 -2
  6. package/dist/components/Form/ControlledViewToggle.js.map +1 -1
  7. package/dist/components/Navigation/ControlledBreadcrumb.js +3 -2
  8. package/dist/components/Navigation/ControlledBreadcrumb.js.map +1 -1
  9. package/dist/components/Navigation/PageTitle.js +3 -6
  10. package/dist/components/Navigation/PageTitle.js.map +1 -1
  11. package/dist/components/Navigation/ReactRouterNavigationAdapter.d.ts +7 -0
  12. package/dist/components/Navigation/ReactRouterNavigationAdapter.js +26 -0
  13. package/dist/components/Navigation/ReactRouterNavigationAdapter.js.map +1 -0
  14. package/dist/components/Navigation/UiNavigationContext.d.ts +38 -0
  15. package/dist/components/Navigation/UiNavigationContext.js +43 -0
  16. package/dist/components/Navigation/UiNavigationContext.js.map +1 -0
  17. package/dist/components/Search/filters/useFilters.js +2 -2
  18. package/dist/components/Search/filters/useFilters.js.map +1 -1
  19. package/dist/components/UI/BaseDataCard.js +3 -2
  20. package/dist/components/UI/BaseDataCard.js.map +1 -1
  21. package/dist/components/UI/DataTable/DataTable.js +1 -1
  22. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +1 -1
  23. package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
  24. package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
  25. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
  26. package/dist/components/UI/UserMenu.d.ts +48 -0
  27. package/dist/components/UI/UserMenu.js +196 -0
  28. package/dist/components/UI/UserMenu.js.map +1 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +5 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/locales/en/ui.json.d.ts +7 -0
  33. package/dist/locales/en/ui.json.js +3 -0
  34. package/dist/locales/en/ui.json.js.map +1 -1
  35. package/dist/locales/index.d.ts +14 -0
  36. package/dist/locales/pt/ui.json.d.ts +7 -0
  37. package/dist/locales/pt/ui.json.js +3 -0
  38. package/dist/locales/pt/ui.json.js.map +1 -1
  39. package/package.json +8 -4
  40. package/src/assets/styles/base.css +489 -0
  41. package/src/assets/styles/index.css +2 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @umodeapporg/ui
2
+
3
+ A biblioteca de componentes e o design system da **uMode**: os componentes React, os tokens de
4
+ tema (claro/escuro), o preset Tailwind, o movimento e os helpers que dão a cara do uMode
5
+ Dashboard a qualquer aplicação.
6
+
7
+ - **Pacote**: [`@umodeapporg/ui` no npm](https://www.npmjs.com/package/@umodeapporg/ui) (registry público, sem token)
8
+ - **Repositório**: [UmodeApp/umode-ui](https://github.com/UmodeApp/umode-ui)
9
+
10
+ ```bash
11
+ npm install @umodeapporg/ui
12
+ ```
13
+
14
+ ## O que vem dentro
15
+
16
+ | Área | Conteúdo |
17
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `components/Form` | `ControlledButton`, `ControlledInput`, `ControlledSelect`, `ControlledDialog`, `ControlledDrawer`, `ControlledTabs`, `ControlledImage`, `RichTextEditor`... |
19
+ | `components/UI` | `DataTable`, `NoDataFound`, `OutlinedChip`, `ControlledTooltip`, `ControlledMenu`/`Popover`, `MaterialSymbol`, `Toast`, `ImageAnnotator`, `CustomChart`... |
20
+ | `components/Search` | `UnifiedSearchBar` e a arquitetura de filtros (`FiltersPanel`) |
21
+ | `components/Navigation` | `PageTitle` (breadcrumb automático via porta), `ControlledBreadcrumb`, `ErrorPage` |
22
+ | Design system | `@umodeapporg/ui/tailwind-preset`, `@umodeapporg/ui/theme.css` (tokens dos dois temas), `@umodeapporg/ui/base.css` (camada base completa), `@umodeapporg/ui/styles.css` |
23
+ | Infra de UI | hooks puros (`useResolvedTheme`, `useBreakpoint`, `useNotification`...), helpers puros (`formatNumber`, `capitalize`, `generateUUID`...), presets de motion, locales `pt`/`en` (`@umodeapporg/ui/locales`) |
24
+
25
+ Import raso para as primitivas comuns (`import { ControlledButton } from '@umodeapporg/ui'`)
26
+ ou profundo para qualquer módulo (`@umodeapporg/ui/components/UI/DataTable`).
27
+
28
+ ## Começando
29
+
30
+ O guia completo para montar um app novo com a identidade uMode (preset, `base.css`, fontes,
31
+ boot de tema sem flash, i18n, portas) é o **[.claude/APP-INTEGRATION.md](./.claude/APP-INTEGRATION.md)**. O mínimo:
32
+
33
+ ```js
34
+ // tailwind.config.js
35
+ import preset from '@umodeapporg/ui/tailwind-preset';
36
+ export default {
37
+ presets: [preset],
38
+ content: ['./index.html', './src/**/*.{ts,tsx}', './node_modules/@umodeapporg/ui/dist/**/*.js'],
39
+ };
40
+ ```
41
+
42
+ ```ts
43
+ // entry
44
+ import '@umodeapporg/ui/styles.css';
45
+ import './index.css'; // @import '@umodeapporg/ui/base.css';
46
+ ```
47
+
48
+ ## As regras
49
+
50
+ Este repositório é dirigido por documentação de regras (usada por pessoas e por agentes):
51
+
52
+ | Doc | Responde |
53
+ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
54
+ | [CLAUDE.md](./CLAUDE.md) | O índice e as regras críticas |
55
+ | [.claude/VISUAL-IDENTITY.md](./.claude/VISUAL-IDENTITY.md) | O que faz algo parecer uMode: marca, claro/escuro, contraste, coordenação, ciclos de vida |
56
+ | [.claude/APP-INTEGRATION.md](./.claude/APP-INTEGRATION.md) | App novo do zero, passo a passo |
57
+ | [.claude/COMPONENTS.md](./.claude/COMPONENTS.md) | O catálogo: qual peça para qual trabalho |
58
+ | [.claude/THEMING.md](./.claude/THEMING.md) / [MOTION.md](./.claude/MOTION.md) / [I18N.md](./.claude/I18N.md) | Tokens, movimento e i18n em profundidade |
59
+ | [.claude/NEW-COMPONENT.md](./.claude/NEW-COMPONENT.md) | Como nasce um componente |
60
+ | [.claude/RELEASE.md](./.claude/RELEASE.md) | Releases automáticos |
61
+
62
+ ## Desenvolvimento e release
63
+
64
+ ```bash
65
+ npm ci
66
+ npm run dev # playground (claro/escuro) em localhost
67
+ npm run lint && npm run audit:theme && npm run build
68
+ ```
69
+
70
+ Merge na `main` **é** o release: o semantic-release lê os Conventional Commits, calcula a
71
+ versão (`fix`/`perf`/`refactor` patch, `feat` minor, `feat!` major), publica no npm, cria a
72
+ tag e o changelog. PRs entram por squash com título conventional. Para testar uma PR num app
73
+ antes do merge, a label `canary` publica `0.0.0-canary.<sha>`.
74
+
75
+ ## Licença
76
+
77
+ Código-fonte visível e pacote publicamente instalável; todos os direitos reservados à uMode
78
+ (`UNLICENSED`). Uso fora de produtos uMode não é licenciado.
@@ -28,7 +28,7 @@ import "motion/react";
28
28
  import "react-dom";
29
29
  import "../../motion/motionPresets.js";
30
30
  import "../../motion/motionTokens.js";
31
- import "react-router-dom";
31
+ import "../Navigation/UiNavigationContext.js";
32
32
  import UnifiedSearchBar from "../Search/UnifiedSearchBar.js";
33
33
  import LoadingSpinner from "../UI/LoadingSpinner.js";
34
34
  /* empty css */
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { forwardRef, useMemo, useRef, useState, useImperativeHandle, useEffect, useCallback } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { useLocation, useSearchParams } from "react-router-dom";
5
+ import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
6
6
  import { useUiAccess } from "../UI/access/UiAccessContext.js";
7
7
  import ControlledTooltip from "../UI/ControlledTooltip.js";
8
8
  import { formatNumber } from "../../helpers/formatHelpers.js";
@@ -139,8 +139,7 @@ const ControlledTabs = forwardRef(
139
139
  () => dataTabsProp.filter((tabPanel) => !deniedTabValues.has(tabPanel.key)),
140
140
  [dataTabsProp, deniedTabValues]
141
141
  );
142
- const location = useLocation();
143
- const [searchParams, setSearchParams] = useSearchParams();
142
+ const { location, searchParams, setSearchParams } = useUiNavigation();
144
143
  const urlParamOwnerIdRef = useRef({});
145
144
  const scrollRef = useRef(null);
146
145
  const stickySentinelRef = useRef(null);
@@ -155,7 +154,8 @@ const ControlledTabs = forwardRef(
155
154
  const resolveInitialActiveTab = () => {
156
155
  const fallbackTab = tabs.some((tab) => tab.value === initialActiveTab) ? initialActiveTab : tabs[0]?.value ?? initialActiveTab;
157
156
  if (!syncWithUrl) return fallbackTab;
158
- if (location.state?.ignoreActiveTab === true) return fallbackTab;
157
+ if (location.state?.ignoreActiveTab === true)
158
+ return fallbackTab;
159
159
  const urlActiveTab = searchParams.get(TABS_URL_PARAM);
160
160
  const isValidUrlTab = urlActiveTab != null && tabs.some((tab) => tab.value === urlActiveTab);
161
161
  return isValidUrlTab ? urlActiveTab : fallbackTab;
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledTabs.js","sources":["../../../src/components/Form/ControlledTabs.tsx"],"sourcesContent":["import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { useLocation, useSearchParams } from 'react-router-dom';\n\nimport { TUiCapability, useUiAccess } from '@/components/UI/access/UiAccessContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatNumber } from '@/helpers/formatHelpers';\nimport {\n claimUrlParam,\n queueUrlParamUpdate,\n releaseUrlParam,\n TABS_URL_PARAM,\n URL_PARAM_PRIORITY,\n} from '@/helpers/urlParamSync';\n\nimport LoadingSpinner from '../UI/LoadingSpinner';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport ControlledButton from './ControlledButton';\nimport { Tab, TabPanel, TabsBody, TabsHeader, TabsRoot } from './ControlledTabsPrimitives';\n\ninterface IEditableTabLabelProps {\n label: string;\n count?: number;\n loading?: boolean;\n isVisible?: boolean;\n canToggleVisibility?: boolean;\n onRename: (newLabel: string) => void;\n onToggleVisibility?: () => void;\n}\n\nconst EditableTabLabel = ({\n label,\n count,\n loading,\n isVisible = true,\n canToggleVisibility = true,\n onRename,\n onToggleVisibility,\n}: IEditableTabLabelProps) => {\n const { t } = useTranslation('ui');\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(label);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (editing) {\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n }, [editing]);\n\n const handleEditClick = (e: React.MouseEvent) => {\n e.stopPropagation();\n setDraft(label);\n setEditing(true);\n };\n\n const commit = () => {\n const trimmed = draft.trim();\n if (trimmed && trimmed !== label) onRename(trimmed);\n setEditing(false);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n commit();\n }\n if (e.key === 'Escape') {\n setEditing(false);\n setDraft(label);\n }\n e.stopPropagation();\n };\n\n if (editing) {\n return (\n <div className=\"flex items-center\" onClick={(e) => e.stopPropagation()}>\n <input\n ref={inputRef}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n style={{ width: `${Math.max(draft.length + 2, 6)}ch` }}\n className=\"border-b border-brand-border bg-transparent px-0 py-0.5 text-sm font-medium leading-none focus:outline-none\"\n />\n </div>\n );\n }\n\n return (\n <div className=\"group/tab flex items-center gap-x-1.5 align-bottom\">\n <span>{label}</span>\n {count !== undefined &&\n (loading ? (\n <LoadingSpinner size={3} />\n ) : (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-muted-hover px-1.5 py-px text-[11px] font-medium leading-none text-foreground-subtle\">\n {formatNumber(count)}\n </span>\n ))}\n <ControlledTooltip content={t('controlledTabs.renameTab')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleEditClick}\n className=\"ml-0.5 flex items-center text-foreground-faint transition-colors hover:text-brand-emphasis\"\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"edit\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n {onToggleVisibility && (\n <ControlledTooltip\n content={\n !canToggleVisibility\n ? t('controlledTabs.mustKeepOneEnabled')\n : isVisible\n ? t('controlledTabs.disableTab')\n : t('controlledTabs.enableTabShort')\n }\n >\n <ControlledButton\n defaultHtmlButton\n onClick={(e) => {\n e.stopPropagation();\n if (canToggleVisibility) onToggleVisibility();\n }}\n className={classNames('ml-0.5 flex items-center transition-colors', {\n ['text-foreground-faint hover:text-warning-emphasis']: isVisible && canToggleVisibility,\n ['text-warning-emphasis hover:text-success-emphasis']: !isVisible,\n ['cursor-not-allowed text-foreground-faint']: isVisible && !canToggleVisibility,\n })}\n tabIndex={-1}\n >\n <MaterialSymbol icon={isVisible ? 'visibility' : 'visibility_off'} size={14} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n </div>\n );\n};\n\nexport interface ITabInfo {\n label: string;\n value: string;\n /** Missing capability removes the tab and never mounts its panel, so no protected request runs. */\n capability?: TUiCapability | Array<TUiCapability>;\n count?: number;\n /** Pendências/alertas da aba — badge âmbar ao lado do nome (oculto quando 0/ausente). */\n alertCount?: number;\n tooltip?: string;\n status?: number | string;\n isVisible?: boolean;\n /** When false, the tab keeps a plain label in the bar: no inline rename pencil and no visibility eye (e.g. aggregate tabs). */\n editable?: boolean;\n}\n\nexport interface IDataTab {\n key: string;\n element: ReactNode;\n}\n\ninterface IControlledTabsProps {\n initialActiveTab: string;\n activeTab?: string;\n tabs: Array<ITabInfo>;\n dataTabs: Array<IDataTab>;\n loading?: boolean;\n /** Blocks tab switching and dims the header, e.g. while a blocking operation runs in the active tab. */\n disabled?: boolean;\n handleActiveTabChildren?: () => void;\n useRenderValidation?: boolean;\n onTabChange?: (tab: string) => void;\n beforeTabChange?: (from: string, to: string) => boolean;\n tabsBodyClassName?: string;\n tabPanelClassName?: string;\n\n rootClassName?: string;\n /** Classes extras aplicadas a cada aba (ex.: reduzir o padding vertical em telas específicas). */\n tabClassName?: string;\n headerSlot?: ReactNode;\n /** Conteúdo fixo alinhado à direita do cabeçalho das abas (ex.: botão de ação principal). */\n actionsSlot?: ReactNode;\n onRenameTab?: (value: string, newLabel: string) => void;\n onToggleTabVisibility?: (value: string) => void;\n /**\n * Habilita arrastar-e-soltar das abas no cabeçalho (mesmo molde do drag nativo dos\n * campos do construtor de fichas). Recebe os values da aba arrastada e da aba alvo;\n * o consumidor decide como persistir a nova ordem (ex.: tabOrderPriority).\n */\n onReorderTab?: (fromValue: string, toValue: string) => void;\n /**\n * Renders the tab header as a single horizontally-scrollable line (browser-tab style)\n * with navigation arrows and a \"+N\" overflow menu, instead of wrapping onto several lines.\n */\n scrollableHeader?: boolean;\n /**\n * Faz o cabeçalho das abas grudar no topo do container rolável (`position: sticky`), com\n * fundo sólido e uma sombra sutil que aparece só ao rolar, além de um acabamento mais\n * delicado nas abas (pill no hover, sublinhado fino e transição suave). Use quando a barra\n * precisa ficar visível durante todo o scroll (ex.: ficha técnica do produto).\n */\n stickyHeader?: boolean;\n /**\n * Sincroniza a aba ativa com o query param `activeTab` da URL (ler no mount e\n * escrever a cada troca). Default `true`. Use `false` em abas dentro de modais/\n * dialogs, cujo estado é efêmero e não deve poluir a URL da página nem competir\n * com abas de nível de página que já usam `activeTab`.\n */\n syncWithUrl?: boolean;\n}\n\nconst ControlledTabs = forwardRef<unknown, IControlledTabsProps>(\n (\n {\n initialActiveTab,\n activeTab: controlledActiveTab,\n tabs: tabsProp,\n dataTabs: dataTabsProp,\n loading = false,\n disabled = false,\n handleActiveTabChildren,\n useRenderValidation = false,\n onTabChange,\n beforeTabChange,\n tabsBodyClassName,\n tabPanelClassName,\n rootClassName,\n tabClassName,\n headerSlot,\n actionsSlot,\n onRenameTab,\n onToggleTabVisibility,\n onReorderTab,\n scrollableHeader = false,\n stickyHeader = false,\n syncWithUrl = true,\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const { check } = useUiAccess();\n\n const deniedTabValues = useMemo(\n () => new Set(tabsProp.filter((tab) => tab.capability && !check(tab.capability)).map((tab) => tab.value)),\n [tabsProp, check],\n );\n\n const tabs = useMemo(() => tabsProp.filter((tab) => !deniedTabValues.has(tab.value)), [tabsProp, deniedTabValues]);\n\n const dataTabs = useMemo(\n () => dataTabsProp.filter((tabPanel) => !deniedTabValues.has(tabPanel.key)),\n [dataTabsProp, deniedTabValues],\n );\n\n const location = useLocation();\n const [searchParams, setSearchParams] = useSearchParams();\n const urlParamOwnerIdRef = useRef<object>({});\n const scrollRef = useRef<HTMLDivElement>(null);\n const stickySentinelRef = useRef<HTMLDivElement>(null);\n const stickyBarRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n // Abas que cruzam uma borda da área rolável (visíveis só pela metade). Ficam invisíveis para\n // que na borda só apareça aba inteira ou espaço vazio, nunca um pedaço de texto cortado.\n const [clippedTabValues, setClippedTabValues] = useState<Array<string>>([]);\n // Barra sticky: detecta quando a barra \"grudou\" (usuário já rolou) para só então mostrar a sombra.\n const [isStuck, setIsStuck] = useState(false);\n // Drag nativo das abas (opt-in via onReorderTab), mesmo molde dos campos do construtor.\n const dragTabValueRef = useRef<string | null>(null);\n const [draggingTabValue, setDraggingTabValue] = useState<string | null>(null);\n const [dragOverTabValue, setDragOverTabValue] = useState<string | null>(null);\n\n const resolveInitialActiveTab = () => {\n const fallbackTab = tabs.some((tab) => tab.value === initialActiveTab)\n ? initialActiveTab\n : (tabs[0]?.value ?? initialActiveTab);\n\n if (!syncWithUrl) return fallbackTab;\n if (location.state?.ignoreActiveTab === true) return fallbackTab;\n const urlActiveTab = searchParams.get(TABS_URL_PARAM);\n const isValidUrlTab = urlActiveTab != null && tabs.some((tab) => tab.value === urlActiveTab);\n return isValidUrlTab ? urlActiveTab : fallbackTab;\n };\n\n const [internalActiveTab, setInternalActiveTab] = useState(resolveInitialActiveTab);\n const isControlled = controlledActiveTab !== undefined;\n const activeTab = isControlled ? controlledActiveTab : internalActiveTab;\n\n const handleActiveTab = (tab: string) => {\n if (disabled) return;\n if (beforeTabChange && !beforeTabChange(activeTab, tab)) {\n if (!isControlled) {\n setInternalActiveTab(activeTab);\n }\n return;\n }\n if (handleActiveTabChildren) {\n handleActiveTabChildren();\n }\n if (!isControlled) {\n setInternalActiveTab(tab);\n }\n if (onTabChange) {\n onTabChange(tab);\n }\n\n if (syncWithUrl) {\n queueUrlParamUpdate(TABS_URL_PARAM, tab, setSearchParams);\n }\n };\n\n useImperativeHandle(ref, () => ({\n activeTab,\n setActiveTab: handleActiveTab,\n }));\n\n const handleToggleTabVisibility = (value: string) => {\n const visibleCount = tabs.filter((t) => t.isVisible !== false).length;\n const isCurrentlyVisible = tabs.find((t) => t.value === value)?.isVisible !== false;\n if (isCurrentlyVisible && visibleCount <= 1) return;\n onToggleTabVisibility?.(value);\n };\n\n // As abas são o topo da hierarquia de parâmetros: assumem `activeTab` mesmo montando depois de\n // um controle que já o tivesse reivindicado, e o liberam ao desmontar.\n useEffect(() => {\n if (!syncWithUrl) return;\n\n const ownerId = urlParamOwnerIdRef.current;\n claimUrlParam(TABS_URL_PARAM, ownerId, URL_PARAM_PRIORITY.tabs);\n\n return () => releaseUrlParam(TABS_URL_PARAM, ownerId);\n }, [syncWithUrl]);\n\n useEffect(() => {\n if (syncWithUrl && searchParams.get(TABS_URL_PARAM) !== activeTab) {\n queueUrlParamUpdate(TABS_URL_PARAM, activeTab, setSearchParams);\n }\n const tabButton = document.querySelector(`li[data-value=${activeTab}]`) as HTMLElement | null;\n if (tabButton) {\n tabButton.click();\n }\n }, [activeTab]);\n\n const updateOverflow = useCallback(() => {\n const container = scrollRef.current;\n if (!container) return;\n\n setCanScrollLeft(container.scrollLeft > 4);\n setCanScrollRight(container.scrollLeft + container.clientWidth < container.scrollWidth - 4);\n\n const EDGE_TOLERANCE = 1;\n const containerRect = container.getBoundingClientRect();\n const clipped: Array<string> = [];\n container.querySelectorAll('[data-value]').forEach((node) => {\n const element = node as HTMLElement;\n const value = element.getAttribute('data-value');\n if (!value || value === activeTab) return;\n const rect = element.getBoundingClientRect();\n const crossesLeftEdge =\n rect.left < containerRect.left - EDGE_TOLERANCE && rect.right > containerRect.left + EDGE_TOLERANCE;\n const crossesRightEdge =\n rect.right > containerRect.right + EDGE_TOLERANCE && rect.left < containerRect.right - EDGE_TOLERANCE;\n if (crossesLeftEdge || crossesRightEdge) clipped.push(value);\n });\n setClippedTabValues((prev) =>\n prev.length === clipped.length && prev.every((value, index) => value === clipped[index]) ? prev : clipped,\n );\n }, [activeTab]);\n\n useEffect(() => {\n if (!stickyHeader) return;\n const sentinel = stickySentinelRef.current;\n if (!sentinel || typeof IntersectionObserver === 'undefined') return;\n // Descobre o ancestral rolável para observar o sentinela em relação a ele. Quando o sentinela\n // (logo acima da barra) sai do topo do container, a barra grudou e ligamos a sombra.\n let scrollParent = sentinel.parentElement;\n while (scrollParent) {\n const overflowY = getComputedStyle(scrollParent).overflowY;\n if (overflowY === 'auto' || overflowY === 'scroll') break;\n scrollParent = scrollParent.parentElement;\n }\n const observer = new IntersectionObserver(([entry]) => setIsStuck(!entry.isIntersecting), {\n root: scrollParent ?? null,\n threshold: 0,\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [stickyHeader]);\n\n // Publica a altura da barra fixa como `--sticky-tabs-height` no container rolável, para\n // que conteúdo sticky mais abaixo (ex.: cabeçalho de material da ficha) possa grudar logo\n // abaixo da barra em vez de ficar escondido atrás dela.\n useEffect(() => {\n if (!stickyHeader) return;\n const bar = stickyBarRef.current;\n if (!bar) return;\n let scrollParent = bar.parentElement;\n while (scrollParent) {\n const overflowY = getComputedStyle(scrollParent).overflowY;\n if (overflowY === 'auto' || overflowY === 'scroll') break;\n scrollParent = scrollParent.parentElement;\n }\n if (!scrollParent) return;\n const target = scrollParent;\n let rafId = 0;\n let lastHeight = -1;\n // Escreve a variável só quando a altura muda de fato, e fora do ciclo de entrega do\n // ResizeObserver (via rAF): escrever um valor que afeta layout dentro do callback dispara\n // \"ResizeObserver loop completed with undelivered notifications\".\n const publishHeight = () => {\n const height = bar.offsetHeight;\n if (height === lastHeight) return;\n lastHeight = height;\n target.style.setProperty('--sticky-tabs-height', `${height}px`);\n };\n publishHeight();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n cancelAnimationFrame(rafId);\n rafId = requestAnimationFrame(publishHeight);\n });\n observer.observe(bar);\n return () => {\n cancelAnimationFrame(rafId);\n observer.disconnect();\n target.style.removeProperty('--sticky-tabs-height');\n };\n }, [stickyHeader]);\n\n useEffect(() => {\n if (!scrollableHeader) return;\n updateOverflow();\n const onResize = () => updateOverflow();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [scrollableHeader, tabs, updateOverflow]);\n\n const scrollByStep = (direction: number) => {\n const container = scrollRef.current;\n if (!container) return;\n const EDGE_TOLERANCE = 1;\n const containerRect = container.getBoundingClientRect();\n const nodes = Array.from(container.querySelectorAll('[data-value]')) as Array<HTMLElement>;\n\n if (direction > 0) {\n const nextHidden = nodes.find(\n (node) => node.getBoundingClientRect().right > containerRect.right + EDGE_TOLERANCE,\n );\n if (!nextHidden) return;\n container.scrollBy({ left: nextHidden.getBoundingClientRect().left - containerRect.left, behavior: 'smooth' });\n return;\n }\n\n const previousHidden = [...nodes]\n .reverse()\n .find((node) => node.getBoundingClientRect().left < containerRect.left - EDGE_TOLERANCE);\n if (!previousHidden) return;\n container.scrollBy({\n left: previousHidden.getBoundingClientRect().right - containerRect.right,\n behavior: 'smooth',\n });\n };\n\n const tabLabel = (label: string, count?: number, alertCount?: number) => {\n const showCount = count !== undefined;\n return (\n <div className=\"flex items-center gap-x-1.5 align-bottom\">\n {label}\n {loading && showCount ? (\n <LoadingSpinner size={3} />\n ) : showCount ? (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-muted-hover px-1.5 py-px text-[11px] font-medium leading-none text-foreground-subtle\">\n {formatNumber(count)}\n </span>\n ) : null}\n {Boolean(alertCount) && (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-warning-soft px-1.5 py-px text-[11px] font-semibold leading-none text-warning-emphasis\">\n {formatNumber(alertCount as number)}\n </span>\n )}\n </div>\n );\n };\n\n const handleTabDragEnd = () => {\n const from = dragTabValueRef.current;\n const to = dragOverTabValue;\n\n if (from && to && from !== to) {\n onReorderTab?.(from, to);\n }\n\n dragTabValueRef.current = null;\n setDraggingTabValue(null);\n setDragOverTabValue(null);\n };\n\n const tabElements = tabs.map((tab, index) => {\n const isActive = activeTab === tab.value && tab.isVisible !== false;\n const isInactive = activeTab !== tab.value && tab.isVisible !== false;\n const isTabDropTarget =\n Boolean(draggingTabValue) && dragOverTabValue === tab.value && draggingTabValue !== tab.value;\n const tabDragProps = onReorderTab\n ? {\n draggable: true,\n onDragStart: (event: React.DragEvent) => {\n // Arrastar a partir do input de renomear deve selecionar texto, não mover a aba.\n if ((event.target as HTMLElement).closest('input')) {\n event.preventDefault();\n return;\n }\n event.dataTransfer.effectAllowed = 'move';\n dragTabValueRef.current = tab.value;\n setDraggingTabValue(tab.value);\n },\n onDragEnter: (event: React.DragEvent) => {\n event.preventDefault();\n if (dragTabValueRef.current) setDragOverTabValue(tab.value);\n },\n onDragOver: (event: React.DragEvent) => event.preventDefault(),\n onDragEnd: handleTabDragEnd,\n }\n : {};\n return (\n <Tab\n key={`controlled-tab-${index}-${tab.value}`}\n value={tab.value}\n style={{ zIndex: 2 }}\n disabled={loading}\n onClick={() => handleActiveTab(tab.value)}\n {...tabDragProps}\n className={classNames(\n 'max-w-fit px-4',\n tabClassName,\n // Acabamento mais delicado quando a barra é sticky: pill sutil no hover das abas\n // inativas e peso de fonte mais leve ao trocar de aba.\n stickyHeader && 'rounded-md transition-interactive duration-fast',\n {\n [stickyHeader ? 'font-medium text-brand-emphasis' : 'text-brand-emphasis']: isActive,\n [stickyHeader ? 'font-normal opacity-70 hover:bg-muted-hover hover:opacity-100' : 'opacity-70']:\n isInactive,\n ['!text-foreground-faint line-through opacity-60 hover:!text-foreground-faint']: tab.isVisible === false,\n ['cursor-grab active:cursor-grabbing']: Boolean(onReorderTab),\n ['rounded-md bg-brand-soft !opacity-100 ring-1 ring-brand-border']: isTabDropTarget,\n ['opacity-40']: draggingTabValue === tab.value,\n ['invisible']: clippedTabValues.includes(tab.value),\n },\n )}\n >\n {onRenameTab && tab.editable !== false ? (\n <EditableTabLabel\n label={tab.label}\n count={tab.count}\n loading={loading}\n isVisible={tab.isVisible !== false}\n canToggleVisibility={tab.isVisible === false || tabs.filter((t) => t.isVisible !== false).length > 1}\n onRename={(newLabel) => onRenameTab(tab.value, newLabel)}\n onToggleVisibility={onToggleTabVisibility ? () => handleToggleTabVisibility(tab.value) : undefined}\n />\n ) : tab.tooltip ? (\n <ControlledTooltip content={tab.tooltip} className=\"max-w-60\">\n {tabLabel(tab.label, tab.count, tab.alertCount)}\n </ControlledTooltip>\n ) : (\n tabLabel(tab.label, tab.count, tab.alertCount)\n )}\n </Tab>\n );\n });\n\n // Sublinhado fino (2px). Na barra sticky mantemos a animação de deslize nativa (transform) com\n // transição suave de ~120ms; nas demais telas o indicador segue estático como antes.\n\n const scrollArrowClass =\n 'flex size-8 shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-colors hover:bg-brand-soft hover:text-brand-emphasis';\n\n return (\n <TabsRoot value={activeTab} className={classNames('!overflow-visible', rootClassName)}>\n {/* Sentinela de 1px: quando sai do topo do container rolável a barra já grudou e acende a sombra. */}\n {stickyHeader && <div ref={stickySentinelRef} className=\"h-px\" aria-hidden />}\n <div\n ref={stickyBarRef}\n className={classNames(\n 'flex shrink-0 items-center',\n // Com header rolável a barra é sempre UMA linha (nowrap): as abas cedem e viram scroll\n // horizontal, enquanto as ações (shrink-0 + ml-auto) permanecem pinadas à direita e nunca\n // caem para baixo. Sem header rolável, mantém o comportamento antigo de wrap.\n scrollableHeader ? 'flex-nowrap gap-x-3' : actionsSlot ? 'flex-wrap gap-x-3 gap-y-2' : 'gap-1',\n // Gruda logo abaixo do header do produto: fundo sólido para tapar o conteúdo que\n // rola por baixo e sombra sutil só depois que a barra encosta no topo.\n stickyHeader && 'sticky top-0 z-20 bg-surface transition-shadow duration-fast',\n stickyHeader && isStuck && 'shadow-elevation1',\n disabled && 'pointer-events-none select-none opacity-50',\n )}\n >\n {headerSlot && <>{headerSlot}</>}\n {scrollableHeader ? (\n <div className=\"flex min-w-0 flex-1 items-center gap-1 border-b border-border-subtle\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => scrollByStep(-1)}\n disabled={!canScrollLeft}\n className={classNames(scrollArrowClass, { ['pointer-events-none opacity-0']: !canScrollLeft })}\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"chevron_left\" size={20} />\n </ControlledButton>\n\n <div ref={scrollRef} onScroll={updateOverflow} className=\"scrollbar-hide min-w-0 flex-1 overflow-x-auto\">\n <TabsHeader className=\"flex w-max flex-nowrap items-center gap-1 rounded-none border-b-0 border-border-subtle bg-transparent p-0\">\n {tabElements}\n </TabsHeader>\n </div>\n\n <ControlledButton\n defaultHtmlButton\n onClick={() => scrollByStep(1)}\n disabled={!canScrollRight}\n className={classNames(scrollArrowClass, { ['pointer-events-none opacity-0']: !canScrollRight })}\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"chevron_right\" size={20} />\n </ControlledButton>\n </div>\n ) : (\n <TabsHeader className=\"flex flex-1 flex-wrap items-center gap-1 rounded-none border-b-0 border-border-subtle bg-transparent p-0\">\n {tabElements}\n </TabsHeader>\n )}\n {actionsSlot && <div className=\"ml-auto flex shrink-0 items-center\">{actionsSlot}</div>}\n </div>\n\n <TabsBody className={tabsBodyClassName}>\n {dataTabs.map((tabPanel) => {\n const isTabVisible = tabs.find((t) => t.value === tabPanel.key)?.isVisible !== false;\n return (\n <TabPanel key={tabPanel.key} value={tabPanel.key} className={classNames('size-full', tabPanelClassName)}>\n <div className={classNames('relative size-full', { ['pointer-events-none']: !isTabVisible })}>\n {useRenderValidation\n ? activeTab === tabPanel.key && (\n <div className=\"size-full animate-fade-in transition-interactive duration-slow ease-standard\">\n {tabPanel.element}\n </div>\n )\n : tabPanel.element}\n {!isTabVisible && (\n <div className=\"absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 rounded-lg bg-muted-hover/70 glass-veil\">\n <MaterialSymbol icon=\"visibility_off\" size={32} className=\"text-foreground-faint\" />\n <span className=\"text-sm font-medium text-foreground-subtle\">\n {t('controlledTabs.tabDisabled')}\n </span>\n {onToggleTabVisibility && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n className=\"pointer-events-auto mt-1 flex items-center gap-x-1\"\n onClick={() => onToggleTabVisibility(tabPanel.key)}\n >\n <MaterialSymbol icon=\"visibility\" size={16} />\n {t('controlledTabs.enableTab')}\n </ControlledButton>\n )}\n </div>\n )}\n </div>\n </TabPanel>\n );\n })}\n </TabsBody>\n </TabsRoot>\n );\n },\n);\n\nexport default ControlledTabs;\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"ControlledTabs.js","sources":["../../../src/components/Form/ControlledTabs.tsx"],"sourcesContent":["import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport { TUiCapability, useUiAccess } from '@/components/UI/access/UiAccessContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatNumber } from '@/helpers/formatHelpers';\nimport {\n claimUrlParam,\n queueUrlParamUpdate,\n releaseUrlParam,\n TABS_URL_PARAM,\n URL_PARAM_PRIORITY,\n} from '@/helpers/urlParamSync';\n\nimport LoadingSpinner from '../UI/LoadingSpinner';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport ControlledButton from './ControlledButton';\nimport { Tab, TabPanel, TabsBody, TabsHeader, TabsRoot } from './ControlledTabsPrimitives';\n\ninterface IEditableTabLabelProps {\n label: string;\n count?: number;\n loading?: boolean;\n isVisible?: boolean;\n canToggleVisibility?: boolean;\n onRename: (newLabel: string) => void;\n onToggleVisibility?: () => void;\n}\n\nconst EditableTabLabel = ({\n label,\n count,\n loading,\n isVisible = true,\n canToggleVisibility = true,\n onRename,\n onToggleVisibility,\n}: IEditableTabLabelProps) => {\n const { t } = useTranslation('ui');\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(label);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (editing) {\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n }, [editing]);\n\n const handleEditClick = (e: React.MouseEvent) => {\n e.stopPropagation();\n setDraft(label);\n setEditing(true);\n };\n\n const commit = () => {\n const trimmed = draft.trim();\n if (trimmed && trimmed !== label) onRename(trimmed);\n setEditing(false);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n commit();\n }\n if (e.key === 'Escape') {\n setEditing(false);\n setDraft(label);\n }\n e.stopPropagation();\n };\n\n if (editing) {\n return (\n <div className=\"flex items-center\" onClick={(e) => e.stopPropagation()}>\n <input\n ref={inputRef}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n style={{ width: `${Math.max(draft.length + 2, 6)}ch` }}\n className=\"border-b border-brand-border bg-transparent px-0 py-0.5 text-sm font-medium leading-none focus:outline-none\"\n />\n </div>\n );\n }\n\n return (\n <div className=\"group/tab flex items-center gap-x-1.5 align-bottom\">\n <span>{label}</span>\n {count !== undefined &&\n (loading ? (\n <LoadingSpinner size={3} />\n ) : (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-muted-hover px-1.5 py-px text-[11px] font-medium leading-none text-foreground-subtle\">\n {formatNumber(count)}\n </span>\n ))}\n <ControlledTooltip content={t('controlledTabs.renameTab')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleEditClick}\n className=\"ml-0.5 flex items-center text-foreground-faint transition-colors hover:text-brand-emphasis\"\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"edit\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n {onToggleVisibility && (\n <ControlledTooltip\n content={\n !canToggleVisibility\n ? t('controlledTabs.mustKeepOneEnabled')\n : isVisible\n ? t('controlledTabs.disableTab')\n : t('controlledTabs.enableTabShort')\n }\n >\n <ControlledButton\n defaultHtmlButton\n onClick={(e) => {\n e.stopPropagation();\n if (canToggleVisibility) onToggleVisibility();\n }}\n className={classNames('ml-0.5 flex items-center transition-colors', {\n ['text-foreground-faint hover:text-warning-emphasis']: isVisible && canToggleVisibility,\n ['text-warning-emphasis hover:text-success-emphasis']: !isVisible,\n ['cursor-not-allowed text-foreground-faint']: isVisible && !canToggleVisibility,\n })}\n tabIndex={-1}\n >\n <MaterialSymbol icon={isVisible ? 'visibility' : 'visibility_off'} size={14} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n </div>\n );\n};\n\nexport interface ITabInfo {\n label: string;\n value: string;\n /** Missing capability removes the tab and never mounts its panel, so no protected request runs. */\n capability?: TUiCapability | Array<TUiCapability>;\n count?: number;\n /** Pendências/alertas da aba — badge âmbar ao lado do nome (oculto quando 0/ausente). */\n alertCount?: number;\n tooltip?: string;\n status?: number | string;\n isVisible?: boolean;\n /** When false, the tab keeps a plain label in the bar: no inline rename pencil and no visibility eye (e.g. aggregate tabs). */\n editable?: boolean;\n}\n\nexport interface IDataTab {\n key: string;\n element: ReactNode;\n}\n\ninterface IControlledTabsProps {\n initialActiveTab: string;\n activeTab?: string;\n tabs: Array<ITabInfo>;\n dataTabs: Array<IDataTab>;\n loading?: boolean;\n /** Blocks tab switching and dims the header, e.g. while a blocking operation runs in the active tab. */\n disabled?: boolean;\n handleActiveTabChildren?: () => void;\n useRenderValidation?: boolean;\n onTabChange?: (tab: string) => void;\n beforeTabChange?: (from: string, to: string) => boolean;\n tabsBodyClassName?: string;\n tabPanelClassName?: string;\n\n rootClassName?: string;\n /** Classes extras aplicadas a cada aba (ex.: reduzir o padding vertical em telas específicas). */\n tabClassName?: string;\n headerSlot?: ReactNode;\n /** Conteúdo fixo alinhado à direita do cabeçalho das abas (ex.: botão de ação principal). */\n actionsSlot?: ReactNode;\n onRenameTab?: (value: string, newLabel: string) => void;\n onToggleTabVisibility?: (value: string) => void;\n /**\n * Habilita arrastar-e-soltar das abas no cabeçalho (mesmo molde do drag nativo dos\n * campos do construtor de fichas). Recebe os values da aba arrastada e da aba alvo;\n * o consumidor decide como persistir a nova ordem (ex.: tabOrderPriority).\n */\n onReorderTab?: (fromValue: string, toValue: string) => void;\n /**\n * Renders the tab header as a single horizontally-scrollable line (browser-tab style)\n * with navigation arrows and a \"+N\" overflow menu, instead of wrapping onto several lines.\n */\n scrollableHeader?: boolean;\n /**\n * Faz o cabeçalho das abas grudar no topo do container rolável (`position: sticky`), com\n * fundo sólido e uma sombra sutil que aparece só ao rolar, além de um acabamento mais\n * delicado nas abas (pill no hover, sublinhado fino e transição suave). Use quando a barra\n * precisa ficar visível durante todo o scroll (ex.: ficha técnica do produto).\n */\n stickyHeader?: boolean;\n /**\n * Sincroniza a aba ativa com o query param `activeTab` da URL (ler no mount e\n * escrever a cada troca). Default `true`. Use `false` em abas dentro de modais/\n * dialogs, cujo estado é efêmero e não deve poluir a URL da página nem competir\n * com abas de nível de página que já usam `activeTab`.\n */\n syncWithUrl?: boolean;\n}\n\nconst ControlledTabs = forwardRef<unknown, IControlledTabsProps>(\n (\n {\n initialActiveTab,\n activeTab: controlledActiveTab,\n tabs: tabsProp,\n dataTabs: dataTabsProp,\n loading = false,\n disabled = false,\n handleActiveTabChildren,\n useRenderValidation = false,\n onTabChange,\n beforeTabChange,\n tabsBodyClassName,\n tabPanelClassName,\n rootClassName,\n tabClassName,\n headerSlot,\n actionsSlot,\n onRenameTab,\n onToggleTabVisibility,\n onReorderTab,\n scrollableHeader = false,\n stickyHeader = false,\n syncWithUrl = true,\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const { check } = useUiAccess();\n\n const deniedTabValues = useMemo(\n () => new Set(tabsProp.filter((tab) => tab.capability && !check(tab.capability)).map((tab) => tab.value)),\n [tabsProp, check],\n );\n\n const tabs = useMemo(() => tabsProp.filter((tab) => !deniedTabValues.has(tab.value)), [tabsProp, deniedTabValues]);\n\n const dataTabs = useMemo(\n () => dataTabsProp.filter((tabPanel) => !deniedTabValues.has(tabPanel.key)),\n [dataTabsProp, deniedTabValues],\n );\n\n const { location, searchParams, setSearchParams } = useUiNavigation();\n const urlParamOwnerIdRef = useRef<object>({});\n const scrollRef = useRef<HTMLDivElement>(null);\n const stickySentinelRef = useRef<HTMLDivElement>(null);\n const stickyBarRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n // Abas que cruzam uma borda da área rolável (visíveis só pela metade). Ficam invisíveis para\n // que na borda só apareça aba inteira ou espaço vazio, nunca um pedaço de texto cortado.\n const [clippedTabValues, setClippedTabValues] = useState<Array<string>>([]);\n // Barra sticky: detecta quando a barra \"grudou\" (usuário já rolou) para só então mostrar a sombra.\n const [isStuck, setIsStuck] = useState(false);\n // Drag nativo das abas (opt-in via onReorderTab), mesmo molde dos campos do construtor.\n const dragTabValueRef = useRef<string | null>(null);\n const [draggingTabValue, setDraggingTabValue] = useState<string | null>(null);\n const [dragOverTabValue, setDragOverTabValue] = useState<string | null>(null);\n\n const resolveInitialActiveTab = () => {\n const fallbackTab = tabs.some((tab) => tab.value === initialActiveTab)\n ? initialActiveTab\n : (tabs[0]?.value ?? initialActiveTab);\n\n if (!syncWithUrl) return fallbackTab;\n if ((location.state as { ignoreActiveTab?: boolean } | null | undefined)?.ignoreActiveTab === true)\n return fallbackTab;\n const urlActiveTab = searchParams.get(TABS_URL_PARAM);\n const isValidUrlTab = urlActiveTab != null && tabs.some((tab) => tab.value === urlActiveTab);\n return isValidUrlTab ? urlActiveTab : fallbackTab;\n };\n\n const [internalActiveTab, setInternalActiveTab] = useState(resolveInitialActiveTab);\n const isControlled = controlledActiveTab !== undefined;\n const activeTab = isControlled ? controlledActiveTab : internalActiveTab;\n\n const handleActiveTab = (tab: string) => {\n if (disabled) return;\n if (beforeTabChange && !beforeTabChange(activeTab, tab)) {\n if (!isControlled) {\n setInternalActiveTab(activeTab);\n }\n return;\n }\n if (handleActiveTabChildren) {\n handleActiveTabChildren();\n }\n if (!isControlled) {\n setInternalActiveTab(tab);\n }\n if (onTabChange) {\n onTabChange(tab);\n }\n\n if (syncWithUrl) {\n queueUrlParamUpdate(TABS_URL_PARAM, tab, setSearchParams);\n }\n };\n\n useImperativeHandle(ref, () => ({\n activeTab,\n setActiveTab: handleActiveTab,\n }));\n\n const handleToggleTabVisibility = (value: string) => {\n const visibleCount = tabs.filter((t) => t.isVisible !== false).length;\n const isCurrentlyVisible = tabs.find((t) => t.value === value)?.isVisible !== false;\n if (isCurrentlyVisible && visibleCount <= 1) return;\n onToggleTabVisibility?.(value);\n };\n\n // As abas são o topo da hierarquia de parâmetros: assumem `activeTab` mesmo montando depois de\n // um controle que já o tivesse reivindicado, e o liberam ao desmontar.\n useEffect(() => {\n if (!syncWithUrl) return;\n\n const ownerId = urlParamOwnerIdRef.current;\n claimUrlParam(TABS_URL_PARAM, ownerId, URL_PARAM_PRIORITY.tabs);\n\n return () => releaseUrlParam(TABS_URL_PARAM, ownerId);\n }, [syncWithUrl]);\n\n useEffect(() => {\n if (syncWithUrl && searchParams.get(TABS_URL_PARAM) !== activeTab) {\n queueUrlParamUpdate(TABS_URL_PARAM, activeTab, setSearchParams);\n }\n const tabButton = document.querySelector(`li[data-value=${activeTab}]`) as HTMLElement | null;\n if (tabButton) {\n tabButton.click();\n }\n }, [activeTab]);\n\n const updateOverflow = useCallback(() => {\n const container = scrollRef.current;\n if (!container) return;\n\n setCanScrollLeft(container.scrollLeft > 4);\n setCanScrollRight(container.scrollLeft + container.clientWidth < container.scrollWidth - 4);\n\n const EDGE_TOLERANCE = 1;\n const containerRect = container.getBoundingClientRect();\n const clipped: Array<string> = [];\n container.querySelectorAll('[data-value]').forEach((node) => {\n const element = node as HTMLElement;\n const value = element.getAttribute('data-value');\n if (!value || value === activeTab) return;\n const rect = element.getBoundingClientRect();\n const crossesLeftEdge =\n rect.left < containerRect.left - EDGE_TOLERANCE && rect.right > containerRect.left + EDGE_TOLERANCE;\n const crossesRightEdge =\n rect.right > containerRect.right + EDGE_TOLERANCE && rect.left < containerRect.right - EDGE_TOLERANCE;\n if (crossesLeftEdge || crossesRightEdge) clipped.push(value);\n });\n setClippedTabValues((prev) =>\n prev.length === clipped.length && prev.every((value, index) => value === clipped[index]) ? prev : clipped,\n );\n }, [activeTab]);\n\n useEffect(() => {\n if (!stickyHeader) return;\n const sentinel = stickySentinelRef.current;\n if (!sentinel || typeof IntersectionObserver === 'undefined') return;\n // Descobre o ancestral rolável para observar o sentinela em relação a ele. Quando o sentinela\n // (logo acima da barra) sai do topo do container, a barra grudou e ligamos a sombra.\n let scrollParent = sentinel.parentElement;\n while (scrollParent) {\n const overflowY = getComputedStyle(scrollParent).overflowY;\n if (overflowY === 'auto' || overflowY === 'scroll') break;\n scrollParent = scrollParent.parentElement;\n }\n const observer = new IntersectionObserver(([entry]) => setIsStuck(!entry.isIntersecting), {\n root: scrollParent ?? null,\n threshold: 0,\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [stickyHeader]);\n\n // Publica a altura da barra fixa como `--sticky-tabs-height` no container rolável, para\n // que conteúdo sticky mais abaixo (ex.: cabeçalho de material da ficha) possa grudar logo\n // abaixo da barra em vez de ficar escondido atrás dela.\n useEffect(() => {\n if (!stickyHeader) return;\n const bar = stickyBarRef.current;\n if (!bar) return;\n let scrollParent = bar.parentElement;\n while (scrollParent) {\n const overflowY = getComputedStyle(scrollParent).overflowY;\n if (overflowY === 'auto' || overflowY === 'scroll') break;\n scrollParent = scrollParent.parentElement;\n }\n if (!scrollParent) return;\n const target = scrollParent;\n let rafId = 0;\n let lastHeight = -1;\n // Escreve a variável só quando a altura muda de fato, e fora do ciclo de entrega do\n // ResizeObserver (via rAF): escrever um valor que afeta layout dentro do callback dispara\n // \"ResizeObserver loop completed with undelivered notifications\".\n const publishHeight = () => {\n const height = bar.offsetHeight;\n if (height === lastHeight) return;\n lastHeight = height;\n target.style.setProperty('--sticky-tabs-height', `${height}px`);\n };\n publishHeight();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n cancelAnimationFrame(rafId);\n rafId = requestAnimationFrame(publishHeight);\n });\n observer.observe(bar);\n return () => {\n cancelAnimationFrame(rafId);\n observer.disconnect();\n target.style.removeProperty('--sticky-tabs-height');\n };\n }, [stickyHeader]);\n\n useEffect(() => {\n if (!scrollableHeader) return;\n updateOverflow();\n const onResize = () => updateOverflow();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [scrollableHeader, tabs, updateOverflow]);\n\n const scrollByStep = (direction: number) => {\n const container = scrollRef.current;\n if (!container) return;\n const EDGE_TOLERANCE = 1;\n const containerRect = container.getBoundingClientRect();\n const nodes = Array.from(container.querySelectorAll('[data-value]')) as Array<HTMLElement>;\n\n if (direction > 0) {\n const nextHidden = nodes.find(\n (node) => node.getBoundingClientRect().right > containerRect.right + EDGE_TOLERANCE,\n );\n if (!nextHidden) return;\n container.scrollBy({ left: nextHidden.getBoundingClientRect().left - containerRect.left, behavior: 'smooth' });\n return;\n }\n\n const previousHidden = [...nodes]\n .reverse()\n .find((node) => node.getBoundingClientRect().left < containerRect.left - EDGE_TOLERANCE);\n if (!previousHidden) return;\n container.scrollBy({\n left: previousHidden.getBoundingClientRect().right - containerRect.right,\n behavior: 'smooth',\n });\n };\n\n const tabLabel = (label: string, count?: number, alertCount?: number) => {\n const showCount = count !== undefined;\n return (\n <div className=\"flex items-center gap-x-1.5 align-bottom\">\n {label}\n {loading && showCount ? (\n <LoadingSpinner size={3} />\n ) : showCount ? (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-muted-hover px-1.5 py-px text-[11px] font-medium leading-none text-foreground-subtle\">\n {formatNumber(count)}\n </span>\n ) : null}\n {Boolean(alertCount) && (\n <span className=\"inline-flex min-w-[18px] items-center justify-center rounded-full bg-warning-soft px-1.5 py-px text-[11px] font-semibold leading-none text-warning-emphasis\">\n {formatNumber(alertCount as number)}\n </span>\n )}\n </div>\n );\n };\n\n const handleTabDragEnd = () => {\n const from = dragTabValueRef.current;\n const to = dragOverTabValue;\n\n if (from && to && from !== to) {\n onReorderTab?.(from, to);\n }\n\n dragTabValueRef.current = null;\n setDraggingTabValue(null);\n setDragOverTabValue(null);\n };\n\n const tabElements = tabs.map((tab, index) => {\n const isActive = activeTab === tab.value && tab.isVisible !== false;\n const isInactive = activeTab !== tab.value && tab.isVisible !== false;\n const isTabDropTarget =\n Boolean(draggingTabValue) && dragOverTabValue === tab.value && draggingTabValue !== tab.value;\n const tabDragProps = onReorderTab\n ? {\n draggable: true,\n onDragStart: (event: React.DragEvent) => {\n // Arrastar a partir do input de renomear deve selecionar texto, não mover a aba.\n if ((event.target as HTMLElement).closest('input')) {\n event.preventDefault();\n return;\n }\n event.dataTransfer.effectAllowed = 'move';\n dragTabValueRef.current = tab.value;\n setDraggingTabValue(tab.value);\n },\n onDragEnter: (event: React.DragEvent) => {\n event.preventDefault();\n if (dragTabValueRef.current) setDragOverTabValue(tab.value);\n },\n onDragOver: (event: React.DragEvent) => event.preventDefault(),\n onDragEnd: handleTabDragEnd,\n }\n : {};\n return (\n <Tab\n key={`controlled-tab-${index}-${tab.value}`}\n value={tab.value}\n style={{ zIndex: 2 }}\n disabled={loading}\n onClick={() => handleActiveTab(tab.value)}\n {...tabDragProps}\n className={classNames(\n 'max-w-fit px-4',\n tabClassName,\n // Acabamento mais delicado quando a barra é sticky: pill sutil no hover das abas\n // inativas e peso de fonte mais leve ao trocar de aba.\n stickyHeader && 'rounded-md transition-interactive duration-fast',\n {\n [stickyHeader ? 'font-medium text-brand-emphasis' : 'text-brand-emphasis']: isActive,\n [stickyHeader ? 'font-normal opacity-70 hover:bg-muted-hover hover:opacity-100' : 'opacity-70']:\n isInactive,\n ['!text-foreground-faint line-through opacity-60 hover:!text-foreground-faint']: tab.isVisible === false,\n ['cursor-grab active:cursor-grabbing']: Boolean(onReorderTab),\n ['rounded-md bg-brand-soft !opacity-100 ring-1 ring-brand-border']: isTabDropTarget,\n ['opacity-40']: draggingTabValue === tab.value,\n ['invisible']: clippedTabValues.includes(tab.value),\n },\n )}\n >\n {onRenameTab && tab.editable !== false ? (\n <EditableTabLabel\n label={tab.label}\n count={tab.count}\n loading={loading}\n isVisible={tab.isVisible !== false}\n canToggleVisibility={tab.isVisible === false || tabs.filter((t) => t.isVisible !== false).length > 1}\n onRename={(newLabel) => onRenameTab(tab.value, newLabel)}\n onToggleVisibility={onToggleTabVisibility ? () => handleToggleTabVisibility(tab.value) : undefined}\n />\n ) : tab.tooltip ? (\n <ControlledTooltip content={tab.tooltip} className=\"max-w-60\">\n {tabLabel(tab.label, tab.count, tab.alertCount)}\n </ControlledTooltip>\n ) : (\n tabLabel(tab.label, tab.count, tab.alertCount)\n )}\n </Tab>\n );\n });\n\n // Sublinhado fino (2px). Na barra sticky mantemos a animação de deslize nativa (transform) com\n // transição suave de ~120ms; nas demais telas o indicador segue estático como antes.\n\n const scrollArrowClass =\n 'flex size-8 shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-colors hover:bg-brand-soft hover:text-brand-emphasis';\n\n return (\n <TabsRoot value={activeTab} className={classNames('!overflow-visible', rootClassName)}>\n {/* Sentinela de 1px: quando sai do topo do container rolável a barra já grudou e acende a sombra. */}\n {stickyHeader && <div ref={stickySentinelRef} className=\"h-px\" aria-hidden />}\n <div\n ref={stickyBarRef}\n className={classNames(\n 'flex shrink-0 items-center',\n // Com header rolável a barra é sempre UMA linha (nowrap): as abas cedem e viram scroll\n // horizontal, enquanto as ações (shrink-0 + ml-auto) permanecem pinadas à direita e nunca\n // caem para baixo. Sem header rolável, mantém o comportamento antigo de wrap.\n scrollableHeader ? 'flex-nowrap gap-x-3' : actionsSlot ? 'flex-wrap gap-x-3 gap-y-2' : 'gap-1',\n // Gruda logo abaixo do header do produto: fundo sólido para tapar o conteúdo que\n // rola por baixo e sombra sutil só depois que a barra encosta no topo.\n stickyHeader && 'sticky top-0 z-20 bg-surface transition-shadow duration-fast',\n stickyHeader && isStuck && 'shadow-elevation1',\n disabled && 'pointer-events-none select-none opacity-50',\n )}\n >\n {headerSlot && <>{headerSlot}</>}\n {scrollableHeader ? (\n <div className=\"flex min-w-0 flex-1 items-center gap-1 border-b border-border-subtle\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => scrollByStep(-1)}\n disabled={!canScrollLeft}\n className={classNames(scrollArrowClass, { ['pointer-events-none opacity-0']: !canScrollLeft })}\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"chevron_left\" size={20} />\n </ControlledButton>\n\n <div ref={scrollRef} onScroll={updateOverflow} className=\"scrollbar-hide min-w-0 flex-1 overflow-x-auto\">\n <TabsHeader className=\"flex w-max flex-nowrap items-center gap-1 rounded-none border-b-0 border-border-subtle bg-transparent p-0\">\n {tabElements}\n </TabsHeader>\n </div>\n\n <ControlledButton\n defaultHtmlButton\n onClick={() => scrollByStep(1)}\n disabled={!canScrollRight}\n className={classNames(scrollArrowClass, { ['pointer-events-none opacity-0']: !canScrollRight })}\n tabIndex={-1}\n >\n <MaterialSymbol icon=\"chevron_right\" size={20} />\n </ControlledButton>\n </div>\n ) : (\n <TabsHeader className=\"flex flex-1 flex-wrap items-center gap-1 rounded-none border-b-0 border-border-subtle bg-transparent p-0\">\n {tabElements}\n </TabsHeader>\n )}\n {actionsSlot && <div className=\"ml-auto flex shrink-0 items-center\">{actionsSlot}</div>}\n </div>\n\n <TabsBody className={tabsBodyClassName}>\n {dataTabs.map((tabPanel) => {\n const isTabVisible = tabs.find((t) => t.value === tabPanel.key)?.isVisible !== false;\n return (\n <TabPanel key={tabPanel.key} value={tabPanel.key} className={classNames('size-full', tabPanelClassName)}>\n <div className={classNames('relative size-full', { ['pointer-events-none']: !isTabVisible })}>\n {useRenderValidation\n ? activeTab === tabPanel.key && (\n <div className=\"size-full animate-fade-in transition-interactive duration-slow ease-standard\">\n {tabPanel.element}\n </div>\n )\n : tabPanel.element}\n {!isTabVisible && (\n <div className=\"absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 rounded-lg bg-muted-hover/70 glass-veil\">\n <MaterialSymbol icon=\"visibility_off\" size={32} className=\"text-foreground-faint\" />\n <span className=\"text-sm font-medium text-foreground-subtle\">\n {t('controlledTabs.tabDisabled')}\n </span>\n {onToggleTabVisibility && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n className=\"pointer-events-auto mt-1 flex items-center gap-x-1\"\n onClick={() => onToggleTabVisibility(tabPanel.key)}\n >\n <MaterialSymbol icon=\"visibility\" size={16} />\n {t('controlledTabs.enableTab')}\n </ControlledButton>\n )}\n </div>\n )}\n </div>\n </TabPanel>\n );\n })}\n </TabsBody>\n </TabsRoot>\n );\n },\n);\n\nexport default ControlledTabs;\n"],"names":["t"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useRef, useEffect, Fragment } from "react";
3
3
  import classNames from "classnames";
4
- import { useSearchParams } from "react-router-dom";
5
4
  import ControlledButton from "./ControlledButton.js";
5
+ import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
6
6
  import ControlledTooltip from "../UI/ControlledTooltip.js";
7
7
  import LoadingSpinner from "../UI/LoadingSpinner.js";
8
8
  import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
@@ -43,7 +43,7 @@ const ControlledViewToggle = (props) => {
43
43
  className
44
44
  } = props;
45
45
  const styles = SIZE_STYLES[size];
46
- const [searchParams, setSearchParams] = useSearchParams();
46
+ const { searchParams, setSearchParams } = useUiNavigation();
47
47
  const safeOptions = useMemo(() => Array.isArray(options) ? options : [], [options]);
48
48
  const selectedValues = props.multiple ? Array.isArray(props.value) ? props.value : [] : [props.value];
49
49
  const serializedValue = selectedValues.join(",");
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledViewToggle.js","sources":["../../../src/components/Form/ControlledViewToggle.tsx"],"sourcesContent":["import { Fragment, useEffect, useMemo, useRef } from 'react';\nimport classNames from 'classnames';\nimport { useSearchParams } from 'react-router-dom';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { formatNumber } from '@/helpers/formatHelpers';\nimport { uiLogger } from '@/helpers/uiLogger';\nimport {\n claimUrlParam,\n ownsUrlParam,\n queueUrlParamUpdate,\n releaseUrlParam,\n TABS_URL_PARAM,\n URL_PARAM_PRIORITY,\n} from '@/helpers/urlParamSync';\n\nexport type TControlledViewToggleSize = 'sm' | 'md';\n\nexport interface IControlledViewToggleOption<V extends string = string> {\n value: V;\n /** Optional icon; options without one render their label as text instead. */\n icon?: SymbolCodepoints;\n /**\n * Option name. Rendered as visible text when the option has no icon or when\n * `showLabels` is on; otherwise shown as the option's tooltip.\n */\n label: string;\n /**\n * Replaces the label in the tooltip when the option needs to explain itself, not just be named.\n * The label stays the accessible name, so it must remain short.\n */\n tooltip?: string;\n /** Counter badge next to the label, as in ControlledTabs. Hidden when null/undefined. */\n count?: number | null;\n /** Replaces the counter badge with a spinner while this option's data loads. */\n loading?: boolean;\n}\n\ninterface IControlledViewToggleBaseProps<V extends string = string> {\n options: Array<IControlledViewToggleOption<V>>;\n /** Renders every option's label as visible text next to its icon. */\n showLabels?: boolean;\n /** `md` (default) pairs with page-level controls; `sm` matches compact filter bars. */\n size?: TControlledViewToggleSize;\n /** Disables every option, keeping the current selection visible. */\n disabled?: boolean;\n /**\n * Mirrors the selection in the URL, on by default like `ControlledTabs`. Turn it off for toggles\n * that hold form state (a weekday picker, an AND/OR combinator) or that live inside a drawer,\n * where the selection is not page state and should not reach the URL.\n */\n syncWithUrl?: boolean;\n /**\n * Query param used when `syncWithUrl` is on, defaults to `view`. A page holding more than one\n * synced toggle must give each a unique key: the first one mounted keeps the param and the others\n * stop writing (with a warning in development) instead of overwriting each other.\n *\n * Lifecycle mirrors `ControlledTabs`: on mount an existing valid param wins over `value` and is\n * pushed to the host through `onChange`; with no param (or an invalid one) the current value is\n * stamped into the URL right away. From there on the host selection is the authority and every\n * change rewrites the param, always preserving the other params and replacing the history entry.\n * In `multiple` mode values are comma separated and an empty selection removes the param.\n */\n urlParamKey?: string;\n className?: string;\n}\n\ninterface IControlledViewToggleSingleProps<V extends string = string> extends IControlledViewToggleBaseProps<V> {\n multiple?: false;\n value: V;\n onChange: (value: V) => void;\n}\n\ninterface IControlledViewToggleMultipleProps<V extends string = string> extends IControlledViewToggleBaseProps<V> {\n multiple: true;\n value: Array<V>;\n /** Receives the whole next selection, which may be empty. */\n onChange: (values: Array<V>) => void;\n}\n\nexport type TControlledViewToggleProps<V extends string = string> =\n IControlledViewToggleSingleProps<V> | IControlledViewToggleMultipleProps<V>;\n\nconst DEFAULT_URL_PARAM_KEY = 'view';\n\nconst SIZE_STYLES: Record<\n TControlledViewToggleSize,\n { container: string; button: string; textClass: string; inactive: string; icon: number }\n> = {\n sm: {\n // Inner radius mirrors the container radius minus its padding, so the active\n // segment stays concentric with the outer border.\n container: 'h-9 gap-1 rounded-xl',\n button: 'h-7 rounded-lg px-2.5',\n textClass: 'text-xs',\n // One step darker than md so the segments match the neighbouring controls\n // in the compact filter bars this size is used by.\n inactive: 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n icon: 16,\n },\n // leading-[18px] matches the icon height so icon-only, text-only and\n // icon + text options all keep the same button height.\n md: {\n container: 'gap-1 rounded-lg shadow-sm',\n button: 'rounded-md px-2.5 py-1.5',\n textClass: 'text-sm leading-[18px]',\n inactive: 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted',\n icon: 18,\n },\n};\n\n/**\n * Segmented toggle for switching between visualization modes (e.g. cards /\n * list / grouped). Options can be icon-only, text-only or icon + text, and the\n * component is generic over the option value union so hosts keep their own mode\n * types. Clicking the already-active option fires `onChange` again, letting\n * hosts react to re-selection (e.g. reopening a configuration dialog).\n *\n * With `multiple`, it becomes a segmented multi-select: `value` is the selected\n * array, clicking an option adds or removes it, and `onChange` receives the\n * whole next selection, which may be empty.\n */\nconst ControlledViewToggle = <V extends string = string>(props: TControlledViewToggleProps<V>) => {\n const {\n options,\n showLabels = false,\n size = 'md',\n disabled = false,\n syncWithUrl = true,\n urlParamKey = DEFAULT_URL_PARAM_KEY,\n className,\n } = props;\n const styles = SIZE_STYLES[size];\n const [searchParams, setSearchParams] = useSearchParams();\n\n const safeOptions = useMemo(() => (Array.isArray(options) ? options : []), [options]);\n const selectedValues = props.multiple ? (Array.isArray(props.value) ? props.value : []) : [props.value];\n const serializedValue = selectedValues.join(',');\n\n const hasReadUrlSelectionRef = useRef(false);\n const pendingUrlSelectionRef = useRef<string | null>(null);\n const ownerIdRef = useRef<object>({});\n\n const isUrlParamReserved = urlParamKey === TABS_URL_PARAM;\n const isUrlSyncEnabled = syncWithUrl && !isUrlParamReserved;\n\n useEffect(() => {\n if (!syncWithUrl) return;\n\n if (isUrlParamReserved) {\n uiLogger.warn(\n `[ControlledViewToggle] \"${TABS_URL_PARAM}\" belongs to ControlledTabs. ` +\n 'Give this toggle its own urlParamKey: it will not write to the URL.',\n );\n return;\n }\n\n const ownerId = ownerIdRef.current;\n\n if (!claimUrlParam(urlParamKey, ownerId, URL_PARAM_PRIORITY.viewToggle)) {\n uiLogger.warn(\n `[ControlledViewToggle] More than one control syncs \"${urlParamKey}\". ` +\n 'Give each one a unique urlParamKey: this instance will not write to the URL.',\n );\n }\n\n return () => releaseUrlParam(urlParamKey, ownerId);\n }, [syncWithUrl, isUrlParamReserved, urlParamKey]);\n\n // Um efeito só, em duas fases. Na montagem o parâmetro manda: existindo um valor válido ele é\n // aplicado no host, e a escrita fica suspensa até o host adotá-lo (senão o valor padrão seria\n // gravado por cima do que veio na URL). Não existindo, o valor atual é carimbado na hora. Depois\n // disso a seleção do host é a autoridade e a URL apenas a reflete.\n useEffect(() => {\n if (!isUrlSyncEnabled || !ownsUrlParam(urlParamKey, ownerIdRef.current)) return;\n\n const rawParam = searchParams.get(urlParamKey) ?? '';\n\n if (pendingUrlSelectionRef.current !== null) {\n if (serializedValue === pendingUrlSelectionRef.current) pendingUrlSelectionRef.current = null;\n return;\n }\n\n if (!hasReadUrlSelectionRef.current) {\n hasReadUrlSelectionRef.current = true;\n\n const optionValues = new Set(safeOptions.map((option) => option.value));\n const urlValues = rawParam\n .split(',')\n .map((rawValue) => rawValue.trim())\n .filter((rawValue): rawValue is V => optionValues.has(rawValue as V));\n\n if (urlValues.length) {\n const urlSelection = urlValues.join(',');\n if (urlSelection !== serializedValue) {\n pendingUrlSelectionRef.current = urlSelection;\n if (props.multiple) props.onChange(urlValues);\n else props.onChange(urlValues[0]);\n }\n return;\n }\n }\n\n if (rawParam === serializedValue) return;\n\n queueUrlParamUpdate(urlParamKey, serializedValue, setSearchParams);\n }, [isUrlSyncEnabled, urlParamKey, searchParams, safeOptions, serializedValue, setSearchParams, props]);\n\n const handleSelectOption = (optionValue: V) => {\n if (!props.multiple) {\n props.onChange(optionValue);\n return;\n }\n\n const currentValues = Array.isArray(props.value) ? props.value : [];\n props.onChange(\n currentValues.includes(optionValue)\n ? currentValues.filter((selectedValue) => selectedValue !== optionValue)\n : [...currentValues, optionValue],\n );\n };\n\n const renderOptionCounter = (option: IControlledViewToggleOption<V>, isActive: boolean) => {\n if (option.loading) return <LoadingSpinner size={3} />;\n if (option.count === undefined || option.count === null) return null;\n\n return (\n <span\n className={classNames(\n 'inline-flex min-w-[18px] items-center justify-center rounded-full px-1.5 py-px text-[11px] font-medium leading-none',\n isActive ? 'bg-glass/25 text-white' : 'bg-muted-hover text-foreground-subtle',\n )}\n >\n {formatNumber(option.count)}\n </span>\n );\n };\n\n return (\n <div\n className={classNames(\n 'inline-flex shrink-0 items-center border border-border bg-surface p-1',\n styles.container,\n className,\n )}\n >\n {safeOptions.map((option) => {\n const isActive = selectedValues.includes(option.value);\n const showText = showLabels || !option.icon;\n\n const button = (\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isActive}\n disabled={disabled}\n onClick={() => handleSelectOption(option.value)}\n className={classNames(\n 'flex items-center justify-center gap-1.5 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-brand-border disabled:cursor-not-allowed',\n styles.button,\n isActive ? 'bg-brand text-brand-fg shadow-sm' : styles.inactive,\n )}\n >\n {option.icon && <MaterialSymbol icon={option.icon} size={styles.icon} />}\n {showText && (\n <span className={classNames('whitespace-nowrap font-medium', styles.textClass)}>{option.label}</span>\n )}\n {renderOptionCounter(option, isActive)}\n </ControlledButton>\n );\n\n // The tooltip only adds value when the label is not already visible.\n return showText ? (\n <Fragment key={option.value}>{button}</Fragment>\n ) : (\n <ControlledTooltip key={option.value} content={option.tooltip ?? option.label}>\n {button}\n </ControlledTooltip>\n );\n })}\n </div>\n );\n};\n\nexport default ControlledViewToggle;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ControlledViewToggle.js","sources":["../../../src/components/Form/ControlledViewToggle.tsx"],"sourcesContent":["import { Fragment, useEffect, useMemo, useRef } from 'react';\nimport classNames from 'classnames';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { formatNumber } from '@/helpers/formatHelpers';\nimport { uiLogger } from '@/helpers/uiLogger';\nimport {\n claimUrlParam,\n ownsUrlParam,\n queueUrlParamUpdate,\n releaseUrlParam,\n TABS_URL_PARAM,\n URL_PARAM_PRIORITY,\n} from '@/helpers/urlParamSync';\n\nexport type TControlledViewToggleSize = 'sm' | 'md';\n\nexport interface IControlledViewToggleOption<V extends string = string> {\n value: V;\n /** Optional icon; options without one render their label as text instead. */\n icon?: SymbolCodepoints;\n /**\n * Option name. Rendered as visible text when the option has no icon or when\n * `showLabels` is on; otherwise shown as the option's tooltip.\n */\n label: string;\n /**\n * Replaces the label in the tooltip when the option needs to explain itself, not just be named.\n * The label stays the accessible name, so it must remain short.\n */\n tooltip?: string;\n /** Counter badge next to the label, as in ControlledTabs. Hidden when null/undefined. */\n count?: number | null;\n /** Replaces the counter badge with a spinner while this option's data loads. */\n loading?: boolean;\n}\n\ninterface IControlledViewToggleBaseProps<V extends string = string> {\n options: Array<IControlledViewToggleOption<V>>;\n /** Renders every option's label as visible text next to its icon. */\n showLabels?: boolean;\n /** `md` (default) pairs with page-level controls; `sm` matches compact filter bars. */\n size?: TControlledViewToggleSize;\n /** Disables every option, keeping the current selection visible. */\n disabled?: boolean;\n /**\n * Mirrors the selection in the URL, on by default like `ControlledTabs`. Turn it off for toggles\n * that hold form state (a weekday picker, an AND/OR combinator) or that live inside a drawer,\n * where the selection is not page state and should not reach the URL.\n */\n syncWithUrl?: boolean;\n /**\n * Query param used when `syncWithUrl` is on, defaults to `view`. A page holding more than one\n * synced toggle must give each a unique key: the first one mounted keeps the param and the others\n * stop writing (with a warning in development) instead of overwriting each other.\n *\n * Lifecycle mirrors `ControlledTabs`: on mount an existing valid param wins over `value` and is\n * pushed to the host through `onChange`; with no param (or an invalid one) the current value is\n * stamped into the URL right away. From there on the host selection is the authority and every\n * change rewrites the param, always preserving the other params and replacing the history entry.\n * In `multiple` mode values are comma separated and an empty selection removes the param.\n */\n urlParamKey?: string;\n className?: string;\n}\n\ninterface IControlledViewToggleSingleProps<V extends string = string> extends IControlledViewToggleBaseProps<V> {\n multiple?: false;\n value: V;\n onChange: (value: V) => void;\n}\n\ninterface IControlledViewToggleMultipleProps<V extends string = string> extends IControlledViewToggleBaseProps<V> {\n multiple: true;\n value: Array<V>;\n /** Receives the whole next selection, which may be empty. */\n onChange: (values: Array<V>) => void;\n}\n\nexport type TControlledViewToggleProps<V extends string = string> =\n IControlledViewToggleSingleProps<V> | IControlledViewToggleMultipleProps<V>;\n\nconst DEFAULT_URL_PARAM_KEY = 'view';\n\nconst SIZE_STYLES: Record<\n TControlledViewToggleSize,\n { container: string; button: string; textClass: string; inactive: string; icon: number }\n> = {\n sm: {\n // Inner radius mirrors the container radius minus its padding, so the active\n // segment stays concentric with the outer border.\n container: 'h-9 gap-1 rounded-xl',\n button: 'h-7 rounded-lg px-2.5',\n textClass: 'text-xs',\n // One step darker than md so the segments match the neighbouring controls\n // in the compact filter bars this size is used by.\n inactive: 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n icon: 16,\n },\n // leading-[18px] matches the icon height so icon-only, text-only and\n // icon + text options all keep the same button height.\n md: {\n container: 'gap-1 rounded-lg shadow-sm',\n button: 'rounded-md px-2.5 py-1.5',\n textClass: 'text-sm leading-[18px]',\n inactive: 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted',\n icon: 18,\n },\n};\n\n/**\n * Segmented toggle for switching between visualization modes (e.g. cards /\n * list / grouped). Options can be icon-only, text-only or icon + text, and the\n * component is generic over the option value union so hosts keep their own mode\n * types. Clicking the already-active option fires `onChange` again, letting\n * hosts react to re-selection (e.g. reopening a configuration dialog).\n *\n * With `multiple`, it becomes a segmented multi-select: `value` is the selected\n * array, clicking an option adds or removes it, and `onChange` receives the\n * whole next selection, which may be empty.\n */\nconst ControlledViewToggle = <V extends string = string>(props: TControlledViewToggleProps<V>) => {\n const {\n options,\n showLabels = false,\n size = 'md',\n disabled = false,\n syncWithUrl = true,\n urlParamKey = DEFAULT_URL_PARAM_KEY,\n className,\n } = props;\n const styles = SIZE_STYLES[size];\n const { searchParams, setSearchParams } = useUiNavigation();\n\n const safeOptions = useMemo(() => (Array.isArray(options) ? options : []), [options]);\n const selectedValues = props.multiple ? (Array.isArray(props.value) ? props.value : []) : [props.value];\n const serializedValue = selectedValues.join(',');\n\n const hasReadUrlSelectionRef = useRef(false);\n const pendingUrlSelectionRef = useRef<string | null>(null);\n const ownerIdRef = useRef<object>({});\n\n const isUrlParamReserved = urlParamKey === TABS_URL_PARAM;\n const isUrlSyncEnabled = syncWithUrl && !isUrlParamReserved;\n\n useEffect(() => {\n if (!syncWithUrl) return;\n\n if (isUrlParamReserved) {\n uiLogger.warn(\n `[ControlledViewToggle] \"${TABS_URL_PARAM}\" belongs to ControlledTabs. ` +\n 'Give this toggle its own urlParamKey: it will not write to the URL.',\n );\n return;\n }\n\n const ownerId = ownerIdRef.current;\n\n if (!claimUrlParam(urlParamKey, ownerId, URL_PARAM_PRIORITY.viewToggle)) {\n uiLogger.warn(\n `[ControlledViewToggle] More than one control syncs \"${urlParamKey}\". ` +\n 'Give each one a unique urlParamKey: this instance will not write to the URL.',\n );\n }\n\n return () => releaseUrlParam(urlParamKey, ownerId);\n }, [syncWithUrl, isUrlParamReserved, urlParamKey]);\n\n // Um efeito só, em duas fases. Na montagem o parâmetro manda: existindo um valor válido ele é\n // aplicado no host, e a escrita fica suspensa até o host adotá-lo (senão o valor padrão seria\n // gravado por cima do que veio na URL). Não existindo, o valor atual é carimbado na hora. Depois\n // disso a seleção do host é a autoridade e a URL apenas a reflete.\n useEffect(() => {\n if (!isUrlSyncEnabled || !ownsUrlParam(urlParamKey, ownerIdRef.current)) return;\n\n const rawParam = searchParams.get(urlParamKey) ?? '';\n\n if (pendingUrlSelectionRef.current !== null) {\n if (serializedValue === pendingUrlSelectionRef.current) pendingUrlSelectionRef.current = null;\n return;\n }\n\n if (!hasReadUrlSelectionRef.current) {\n hasReadUrlSelectionRef.current = true;\n\n const optionValues = new Set(safeOptions.map((option) => option.value));\n const urlValues = rawParam\n .split(',')\n .map((rawValue) => rawValue.trim())\n .filter((rawValue): rawValue is V => optionValues.has(rawValue as V));\n\n if (urlValues.length) {\n const urlSelection = urlValues.join(',');\n if (urlSelection !== serializedValue) {\n pendingUrlSelectionRef.current = urlSelection;\n if (props.multiple) props.onChange(urlValues);\n else props.onChange(urlValues[0]);\n }\n return;\n }\n }\n\n if (rawParam === serializedValue) return;\n\n queueUrlParamUpdate(urlParamKey, serializedValue, setSearchParams);\n }, [isUrlSyncEnabled, urlParamKey, searchParams, safeOptions, serializedValue, setSearchParams, props]);\n\n const handleSelectOption = (optionValue: V) => {\n if (!props.multiple) {\n props.onChange(optionValue);\n return;\n }\n\n const currentValues = Array.isArray(props.value) ? props.value : [];\n props.onChange(\n currentValues.includes(optionValue)\n ? currentValues.filter((selectedValue) => selectedValue !== optionValue)\n : [...currentValues, optionValue],\n );\n };\n\n const renderOptionCounter = (option: IControlledViewToggleOption<V>, isActive: boolean) => {\n if (option.loading) return <LoadingSpinner size={3} />;\n if (option.count === undefined || option.count === null) return null;\n\n return (\n <span\n className={classNames(\n 'inline-flex min-w-[18px] items-center justify-center rounded-full px-1.5 py-px text-[11px] font-medium leading-none',\n isActive ? 'bg-glass/25 text-white' : 'bg-muted-hover text-foreground-subtle',\n )}\n >\n {formatNumber(option.count)}\n </span>\n );\n };\n\n return (\n <div\n className={classNames(\n 'inline-flex shrink-0 items-center border border-border bg-surface p-1',\n styles.container,\n className,\n )}\n >\n {safeOptions.map((option) => {\n const isActive = selectedValues.includes(option.value);\n const showText = showLabels || !option.icon;\n\n const button = (\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isActive}\n disabled={disabled}\n onClick={() => handleSelectOption(option.value)}\n className={classNames(\n 'flex items-center justify-center gap-1.5 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-brand-border disabled:cursor-not-allowed',\n styles.button,\n isActive ? 'bg-brand text-brand-fg shadow-sm' : styles.inactive,\n )}\n >\n {option.icon && <MaterialSymbol icon={option.icon} size={styles.icon} />}\n {showText && (\n <span className={classNames('whitespace-nowrap font-medium', styles.textClass)}>{option.label}</span>\n )}\n {renderOptionCounter(option, isActive)}\n </ControlledButton>\n );\n\n // The tooltip only adds value when the label is not already visible.\n return showText ? (\n <Fragment key={option.value}>{button}</Fragment>\n ) : (\n <ControlledTooltip key={option.value} content={option.tooltip ?? option.label}>\n {button}\n </ControlledTooltip>\n );\n })}\n </div>\n );\n};\n\nexport default ControlledViewToggle;\n"],"names":[],"mappings":"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"}
@@ -1,19 +1,20 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, Fragment } from "react";
3
3
  import classNames from "classnames";
4
- import { Link } from "react-router-dom";
5
4
  import ControlledButton from "../Form/ControlledButton.js";
5
+ import { useUiNavigation } from "./UiNavigationContext.js";
6
6
  import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
7
7
  const ARIA_CURRENT_PAGE = "page";
8
8
  const CRUMB_LINK_CLASS = "rounded text-foreground-subtle transition-colors hover:text-brand-emphasis focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-border";
9
9
  const ControlledBreadcrumb = ({ items, ariaLabel, className }) => {
10
+ const { Link: NavigationLink } = useUiNavigation();
10
11
  const trail = useMemo(() => Array.isArray(items) ? items : [], [items]);
11
12
  if (trail.length === 0) return null;
12
13
  return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, className: classNames("flex flex-wrap items-center gap-1 text-sm", className), children: trail.map((item, position) => {
13
14
  const isLast = position === trail.length - 1;
14
15
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15
16
  position > 0 && /* @__PURE__ */ jsx(MaterialSymbol, { icon: "chevron_right", size: 16, color: "rgb(var(--color-foreground-subtle))" }),
16
- isLast ? /* @__PURE__ */ jsx("span", { "aria-current": ARIA_CURRENT_PAGE, className: "font-semibold text-foreground", children: item.label }) : item.to ? /* @__PURE__ */ jsx(Link, { to: item.to, className: CRUMB_LINK_CLASS, children: item.label }) : /* @__PURE__ */ jsx(ControlledButton, { defaultHtmlButton: true, type: "button", onClick: item.onClick, className: CRUMB_LINK_CLASS, children: item.label })
17
+ isLast ? /* @__PURE__ */ jsx("span", { "aria-current": ARIA_CURRENT_PAGE, className: "font-semibold text-foreground", children: item.label }) : item.to ? /* @__PURE__ */ jsx(NavigationLink, { to: item.to, className: CRUMB_LINK_CLASS, children: item.label }) : /* @__PURE__ */ jsx(ControlledButton, { defaultHtmlButton: true, type: "button", onClick: item.onClick, className: CRUMB_LINK_CLASS, children: item.label })
17
18
  ] }, item.key);
18
19
  }) });
19
20
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledBreadcrumb.js","sources":["../../../src/components/Navigation/ControlledBreadcrumb.tsx"],"sourcesContent":["import { Fragment, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { Link } from 'react-router-dom';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nexport interface IControlledBreadcrumbItem {\n key: string;\n label: string;\n to?: string;\n onClick?: () => void;\n}\n\ninterface IControlledBreadcrumbProps {\n items: Array<IControlledBreadcrumbItem>;\n ariaLabel: string;\n className?: string;\n}\n\nconst ARIA_CURRENT_PAGE = 'page';\nconst CRUMB_LINK_CLASS =\n 'rounded text-foreground-subtle transition-colors hover:text-brand-emphasis focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-border';\n\nconst ControlledBreadcrumb: React.FC<IControlledBreadcrumbProps> = ({ items, ariaLabel, className }) => {\n const trail = useMemo(() => (Array.isArray(items) ? items : []), [items]);\n\n if (trail.length === 0) return null;\n\n return (\n <nav aria-label={ariaLabel} className={classNames('flex flex-wrap items-center gap-1 text-sm', className)}>\n {trail.map((item, position) => {\n const isLast = position === trail.length - 1;\n\n return (\n <Fragment key={item.key}>\n {position > 0 && (\n <MaterialSymbol icon=\"chevron_right\" size={16} color=\"rgb(var(--color-foreground-subtle))\" />\n )}\n {isLast ? (\n <span aria-current={ARIA_CURRENT_PAGE} className=\"font-semibold text-foreground\">\n {item.label}\n </span>\n ) : item.to ? (\n <Link to={item.to} className={CRUMB_LINK_CLASS}>\n {item.label}\n </Link>\n ) : (\n <ControlledButton defaultHtmlButton type=\"button\" onClick={item.onClick} className={CRUMB_LINK_CLASS}>\n {item.label}\n </ControlledButton>\n )}\n </Fragment>\n );\n })}\n </nav>\n );\n};\n\nexport default ControlledBreadcrumb;\n"],"names":[],"mappings":";;;;;;AAoBA,MAAM,oBAAoB;AAC1B,MAAM,mBACJ;AAEF,MAAM,uBAA6D,CAAC,EAAE,OAAO,WAAW,gBAAgB;AACtG,QAAM,QAAQ,QAAQ,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA,GAAK,CAAC,KAAK,CAAC;AAExE,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,OAAA,EAAI,cAAY,WAAW,WAAW,WAAW,6CAA6C,SAAS,GACrG,UAAA,MAAM,IAAI,CAAC,MAAM,aAAa;AAC7B,UAAM,SAAS,aAAa,MAAM,SAAS;AAE3C,gCACG,UAAA,EACE,UAAA;AAAA,MAAA,WAAW,yBACT,gBAAA,EAAe,MAAK,iBAAgB,MAAM,IAAI,OAAM,sCAAA,CAAsC;AAAA,MAE5F,SACC,oBAAC,QAAA,EAAK,gBAAc,mBAAmB,WAAU,iCAC9C,UAAA,KAAK,OACR,IACE,KAAK,KACP,oBAAC,MAAA,EAAK,IAAI,KAAK,IAAI,WAAW,kBAC3B,UAAA,KAAK,MAAA,CACR,IAEA,oBAAC,oBAAiB,mBAAiB,MAAC,MAAK,UAAS,SAAS,KAAK,SAAS,WAAW,kBACjF,eAAK,MAAA,CACR;AAAA,IAAA,EAAA,GAfW,KAAK,GAiBpB;AAAA,EAEJ,CAAC,EAAA,CACH;AAEJ;"}
1
+ {"version":3,"file":"ControlledBreadcrumb.js","sources":["../../../src/components/Navigation/ControlledBreadcrumb.tsx"],"sourcesContent":["import { Fragment, useMemo } from 'react';\nimport classNames from 'classnames';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nexport interface IControlledBreadcrumbItem {\n key: string;\n label: string;\n to?: string;\n onClick?: () => void;\n}\n\ninterface IControlledBreadcrumbProps {\n items: Array<IControlledBreadcrumbItem>;\n ariaLabel: string;\n className?: string;\n}\n\nconst ARIA_CURRENT_PAGE = 'page';\nconst CRUMB_LINK_CLASS =\n 'rounded text-foreground-subtle transition-colors hover:text-brand-emphasis focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-border';\n\nconst ControlledBreadcrumb: React.FC<IControlledBreadcrumbProps> = ({ items, ariaLabel, className }) => {\n const { Link: NavigationLink } = useUiNavigation();\n const trail = useMemo(() => (Array.isArray(items) ? items : []), [items]);\n\n if (trail.length === 0) return null;\n\n return (\n <nav aria-label={ariaLabel} className={classNames('flex flex-wrap items-center gap-1 text-sm', className)}>\n {trail.map((item, position) => {\n const isLast = position === trail.length - 1;\n\n return (\n <Fragment key={item.key}>\n {position > 0 && (\n <MaterialSymbol icon=\"chevron_right\" size={16} color=\"rgb(var(--color-foreground-subtle))\" />\n )}\n {isLast ? (\n <span aria-current={ARIA_CURRENT_PAGE} className=\"font-semibold text-foreground\">\n {item.label}\n </span>\n ) : item.to ? (\n <NavigationLink to={item.to} className={CRUMB_LINK_CLASS}>\n {item.label}\n </NavigationLink>\n ) : (\n <ControlledButton defaultHtmlButton type=\"button\" onClick={item.onClick} className={CRUMB_LINK_CLASS}>\n {item.label}\n </ControlledButton>\n )}\n </Fragment>\n );\n })}\n </nav>\n );\n};\n\nexport default ControlledBreadcrumb;\n"],"names":[],"mappings":";;;;;;AAoBA,MAAM,oBAAoB;AAC1B,MAAM,mBACJ;AAEF,MAAM,uBAA6D,CAAC,EAAE,OAAO,WAAW,gBAAgB;AACtG,QAAM,EAAE,MAAM,eAAA,IAAmB,gBAAA;AACjC,QAAM,QAAQ,QAAQ,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA,GAAK,CAAC,KAAK,CAAC;AAExE,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,OAAA,EAAI,cAAY,WAAW,WAAW,WAAW,6CAA6C,SAAS,GACrG,UAAA,MAAM,IAAI,CAAC,MAAM,aAAa;AAC7B,UAAM,SAAS,aAAa,MAAM,SAAS;AAE3C,gCACG,UAAA,EACE,UAAA;AAAA,MAAA,WAAW,yBACT,gBAAA,EAAe,MAAK,iBAAgB,MAAM,IAAI,OAAM,sCAAA,CAAsC;AAAA,MAE5F,SACC,oBAAC,QAAA,EAAK,gBAAc,mBAAmB,WAAU,iCAC9C,UAAA,KAAK,OACR,IACE,KAAK,KACP,oBAAC,gBAAA,EAAe,IAAI,KAAK,IAAI,WAAW,kBACrC,UAAA,KAAK,MAAA,CACR,IAEA,oBAAC,oBAAiB,mBAAiB,MAAC,MAAK,UAAS,SAAS,KAAK,SAAS,WAAW,kBACjF,eAAK,MAAA,CACR;AAAA,IAAA,EAAA,GAfW,KAAK,GAiBpB;AAAA,EAEJ,CAAC,EAAA,CACH;AAEJ;"}
@@ -2,13 +2,13 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { useNavigate, useLocation } from "react-router-dom";
6
5
  import ControlledButton from "../Form/ControlledButton.js";
7
6
  import ControlledTooltip from "../UI/ControlledTooltip.js";
8
7
  import { capitalizeAllLetters } from "../../helpers/stringHelpers.js";
9
8
  import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
10
9
  import ControlledBreadcrumb from "./ControlledBreadcrumb.js";
11
10
  import { usePageTitleNavigation } from "./PageTitleNavigationContext.js";
11
+ import { useUiNavigation } from "./UiNavigationContext.js";
12
12
  const PageTitle = ({
13
13
  canBack = false,
14
14
  canFloat = false,
@@ -35,8 +35,7 @@ const PageTitle = ({
35
35
  disableBack = false
36
36
  }) => {
37
37
  const { t } = useTranslation("ui");
38
- const navigate = useNavigate();
39
- const location = useLocation();
38
+ const { navigate, goBack, location } = useUiNavigation();
40
39
  const isFirefox = () => typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("firefox");
41
40
  const handleBack = () => {
42
41
  if (disableBack) return;
@@ -45,9 +44,7 @@ const PageTitle = ({
45
44
  } else if (canBackTo) {
46
45
  navigate(canBackTo);
47
46
  } else {
48
- navigate(-1, {
49
- state: { ignoreActiveTab: true }
50
- });
47
+ goBack();
51
48
  }
52
49
  };
53
50
  const { resolveTrail } = usePageTitleNavigation();
@@ -1 +1 @@
1
- {"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { useLocation, useNavigate } from 'react-router-dom';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const navigate = useNavigate();\n const location = useLocation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n navigate(-1 as unknown as string, {\n state: { ignoreActiveTab: true },\n });\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n return (\n <div\n className={classNames('flex flex-col', {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n className,\n })}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap items-center justify-between gap-x-4 gap-y-2', {\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', compact ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n compact ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={compact ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {resolvedTitleIcon && (\n <span\n className={classNames(\n 'flex shrink-0 items-center justify-center',\n compact ? 'size-8 rounded-lg' : 'size-10 rounded-xl',\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={compact ? 18 : 22} fill className=\"text-inherit\" />\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n compact\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && <div className=\"ml-auto flex shrink-0 flex-wrap items-center justify-end gap-2\">{slot}</div>}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', compact ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":";;;;;;;;;;;AAoDA,MAAM,YAAuC,CAAC;AAAA,EAC5C,UAAU;AAAA,EACV,WAAW;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,WAAW,YAAA;AACjB,QAAM,WAAW,YAAA;AAEjB,QAAM,YAAY,MAAM,OAAO,cAAc,eAAe,UAAU,UAAU,YAAA,EAAc,SAAS,SAAS;AAEhH,QAAM,aAAa,MAAM;AACvB,QAAI,YAAa;AAEjB,QAAI,aAAa;AACf,kBAAA;AAAA,IACF,WAAW,WAAW;AACpB,eAAS,SAAS;AAAA,IACpB,OAAO;AACL,eAAS,IAAyB;AAAA,QAChC,OAAO,EAAE,iBAAiB,KAAA;AAAA,MAAK,CAChC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,EAAE,aAAA,IAAiB,uBAAA;AAEzB,QAAM,YAAY,QAAQ,MAAM;AAC9B,UAAM,aAAc,kBAAkB,CAAC,cAAc,CAAC,YAAc,YAAY,CAAC;AACjF,WAAO,aAAa,aAAa,SAAS,QAAQ,IAAI,CAAA;AAAA,EACxD,GAAG,CAAC,gBAAgB,YAAY,UAAU,UAAU,WAAW,SAAS,UAAU,YAAY,CAAC;AAE/F,QAAM,qBACJ,eACC,kBAAkB,CAAC,YAAY,UAAU,UAAU,IAClD;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,EAAE,2BAA2B;AAAA,MACxC,OAAO,UAAU,IAAI,CAAC,MAAM,WAAW;AAAA,QACrC,KAAK,KAAK;AAAA,QACV,OAAO,KAAK;AAAA,QACZ,IAAI,QAAQ,UAAU,SAAS,IAAI,KAAK,KAAK;AAAA,MAAA,EAC7C;AAAA,IAAA;AAAA,EAAA,IAEF;AAEN,QAAM,oBAAoB,cAAc,WAAW,UAAU,UAAU,SAAS,CAAC,GAAG,OAAO;AAE3F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,WAAW,iBAAiB;AAAA,QACrC,+BAA+B;AAAA,QAC/B;AAAA,MAAA,CACD;AAAA,MACD,OAAO;AAAA,QACL,OAAO,WAAY,cAAc,mBAAmB,2BAA4B;AAAA,MAAA;AAAA,MAGjF,UAAA;AAAA,QAAA,sBAAsB,oBAAC,OAAA,EAAI,WAAU,iBAAiB,UAAA,oBAAmB;AAAA,QAE1E;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,WAAW,sEAAsE;AAAA,cAC1F,mBAAmB;AAAA,YAAA,CACpB;AAAA,YAED,UAAA;AAAA,cAAA,qBAAC,SAAI,WAAW,WAAW,6CAA6C,UAAU,UAAU,OAAO,GAChG,UAAA;AAAA,gBAAA;AAAA,gBAEA,WACC,oBAAC,mBAAA,EAAkB,SAAS,EAAE,cAAc,GAC1C,UAAA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,mBAAiB;AAAA,oBACjB,SAAS;AAAA,oBACT,UAAU;AAAA,oBACV,WAAW;AAAA,sBACT;AAAA,sBACA,UAAU,WAAW;AAAA,oBAAA;AAAA,oBAGvB,8BAAC,gBAAA,EAAe,MAAK,cAAa,MAAM,UAAU,KAAK,GAAA,CAAI;AAAA,kBAAA;AAAA,gBAAA,GAE/D;AAAA,gBAGD,qBACC;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAW;AAAA,sBACT;AAAA,sBACA,UAAU,sBAAsB;AAAA,sBAChC,sBAAsB;AAAA,oBAAA;AAAA,oBAGxB,UAAA,oBAAC,gBAAA,EAAe,MAAM,mBAAmB,MAAM,UAAU,KAAK,IAAI,MAAI,MAAC,WAAU,eAAA,CAAe;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAIpG,qBAAC,OAAA,EAAI,WAAU,gCACZ,UAAA;AAAA,kBAAA,WACC,oBAAC,QAAA,EAAK,WAAU,gFACb,UAAA,SACH;AAAA,kBAGF,qBAAC,OAAA,EAAI,WAAU,mCACZ,UAAA;AAAA,oBAAA,gBACC;AAAA,sBAAC;AAAA,sBAAA;AAAA,wBACC,WAAW;AAAA,0BACT;AAAA,0BACA,UACI,0CACA;AAAA,0BACJ;AAAA,wBAAA;AAAA,wBAGD,UAAA,kBAAkB,qBAAqB,KAAK,IAAI;AAAA,sBAAA;AAAA,oBAAA;AAAA,oBAIpD;AAAA,kBAAA,GACH;AAAA,kBAEC,YAAY,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,UAAS;AAAA,kBAE9E,gBAAgB,oBAAC,OAAA,EAAI,WAAU,oCAAoC,UAAA,aAAA,CAAa;AAAA,gBAAA,EAAA,CACnF;AAAA,cAAA,GACF;AAAA,cAEC,QAAQ,oBAAC,OAAA,EAAI,WAAU,kEAAkE,UAAA,KAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEhG,eAAe,CAAC,aAAa,MAAM,SAAS,KAAK,QAAQ,YAAY,MACpE,oBAAC,QAAG,WAAW,WAAW,wBAAwB,UAAU,cAAc,WAAW,EAAA,CAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIhG;"}
1
+ {"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n return (\n <div\n className={classNames('flex flex-col', {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n className,\n })}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap items-center justify-between gap-x-4 gap-y-2', {\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', compact ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n compact ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={compact ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {resolvedTitleIcon && (\n <span\n className={classNames(\n 'flex shrink-0 items-center justify-center',\n compact ? 'size-8 rounded-lg' : 'size-10 rounded-xl',\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={compact ? 18 : 22} fill className=\"text-inherit\" />\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n compact\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && <div className=\"ml-auto flex shrink-0 flex-wrap items-center justify-end gap-2\">{slot}</div>}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', compact ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":";;;;;;;;;;;AAoDA,MAAM,YAAuC,CAAC;AAAA,EAC5C,UAAU;AAAA,EACV,WAAW;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,UAAU,QAAQ,SAAA,IAAa,gBAAA;AAEvC,QAAM,YAAY,MAAM,OAAO,cAAc,eAAe,UAAU,UAAU,YAAA,EAAc,SAAS,SAAS;AAEhH,QAAM,aAAa,MAAM;AACvB,QAAI,YAAa;AAEjB,QAAI,aAAa;AACf,kBAAA;AAAA,IACF,WAAW,WAAW;AACpB,eAAS,SAAS;AAAA,IACpB,OAAO;AACL,aAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,EAAE,aAAA,IAAiB,uBAAA;AAEzB,QAAM,YAAY,QAAQ,MAAM;AAC9B,UAAM,aAAc,kBAAkB,CAAC,cAAc,CAAC,YAAc,YAAY,CAAC;AACjF,WAAO,aAAa,aAAa,SAAS,QAAQ,IAAI,CAAA;AAAA,EACxD,GAAG,CAAC,gBAAgB,YAAY,UAAU,UAAU,WAAW,SAAS,UAAU,YAAY,CAAC;AAE/F,QAAM,qBACJ,eACC,kBAAkB,CAAC,YAAY,UAAU,UAAU,IAClD;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,EAAE,2BAA2B;AAAA,MACxC,OAAO,UAAU,IAAI,CAAC,MAAM,WAAW;AAAA,QACrC,KAAK,KAAK;AAAA,QACV,OAAO,KAAK;AAAA,QACZ,IAAI,QAAQ,UAAU,SAAS,IAAI,KAAK,KAAK;AAAA,MAAA,EAC7C;AAAA,IAAA;AAAA,EAAA,IAEF;AAEN,QAAM,oBAAoB,cAAc,WAAW,UAAU,UAAU,SAAS,CAAC,GAAG,OAAO;AAE3F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,WAAW,iBAAiB;AAAA,QACrC,+BAA+B;AAAA,QAC/B;AAAA,MAAA,CACD;AAAA,MACD,OAAO;AAAA,QACL,OAAO,WAAY,cAAc,mBAAmB,2BAA4B;AAAA,MAAA;AAAA,MAGjF,UAAA;AAAA,QAAA,sBAAsB,oBAAC,OAAA,EAAI,WAAU,iBAAiB,UAAA,oBAAmB;AAAA,QAE1E;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,WAAW,sEAAsE;AAAA,cAC1F,mBAAmB;AAAA,YAAA,CACpB;AAAA,YAED,UAAA;AAAA,cAAA,qBAAC,SAAI,WAAW,WAAW,6CAA6C,UAAU,UAAU,OAAO,GAChG,UAAA;AAAA,gBAAA;AAAA,gBAEA,WACC,oBAAC,mBAAA,EAAkB,SAAS,EAAE,cAAc,GAC1C,UAAA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,mBAAiB;AAAA,oBACjB,SAAS;AAAA,oBACT,UAAU;AAAA,oBACV,WAAW;AAAA,sBACT;AAAA,sBACA,UAAU,WAAW;AAAA,oBAAA;AAAA,oBAGvB,8BAAC,gBAAA,EAAe,MAAK,cAAa,MAAM,UAAU,KAAK,GAAA,CAAI;AAAA,kBAAA;AAAA,gBAAA,GAE/D;AAAA,gBAGD,qBACC;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAW;AAAA,sBACT;AAAA,sBACA,UAAU,sBAAsB;AAAA,sBAChC,sBAAsB;AAAA,oBAAA;AAAA,oBAGxB,UAAA,oBAAC,gBAAA,EAAe,MAAM,mBAAmB,MAAM,UAAU,KAAK,IAAI,MAAI,MAAC,WAAU,eAAA,CAAe;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAIpG,qBAAC,OAAA,EAAI,WAAU,gCACZ,UAAA;AAAA,kBAAA,WACC,oBAAC,QAAA,EAAK,WAAU,gFACb,UAAA,SACH;AAAA,kBAGF,qBAAC,OAAA,EAAI,WAAU,mCACZ,UAAA;AAAA,oBAAA,gBACC;AAAA,sBAAC;AAAA,sBAAA;AAAA,wBACC,WAAW;AAAA,0BACT;AAAA,0BACA,UACI,0CACA;AAAA,0BACJ;AAAA,wBAAA;AAAA,wBAGD,UAAA,kBAAkB,qBAAqB,KAAK,IAAI;AAAA,sBAAA;AAAA,oBAAA;AAAA,oBAIpD;AAAA,kBAAA,GACH;AAAA,kBAEC,YAAY,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,UAAS;AAAA,kBAE9E,gBAAgB,oBAAC,OAAA,EAAI,WAAU,oCAAoC,UAAA,aAAA,CAAa;AAAA,gBAAA,EAAA,CACnF;AAAA,cAAA,GACF;AAAA,cAEC,QAAQ,oBAAC,OAAA,EAAI,WAAU,kEAAkE,UAAA,KAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAEhG,eAAe,CAAC,aAAa,MAAM,SAAS,KAAK,QAAQ,YAAY,MACpE,oBAAC,QAAG,WAAW,WAAW,wBAAwB,UAAU,cAAc,WAAW,EAAA,CAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIhG;"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Binds the navigation port to react-router. The ONLY module in the package that imports
3
+ * `react-router-dom` (an optional peer): hosts without it simply never import this file.
4
+ */
5
+ export declare const UiReactRouterNavigationProvider: React.FC<{
6
+ children: React.ReactNode;
7
+ }>;
@@ -0,0 +1,26 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { useLocation, useNavigate, useSearchParams, Link } from "react-router-dom";
4
+ import { UiNavigationProvider } from "./UiNavigationContext.js";
5
+ const RouterLink = ({ to, children, ...rest }) => /* @__PURE__ */ jsx(Link, { to, ...rest, children });
6
+ const UiReactRouterNavigationProvider = ({ children }) => {
7
+ const location = useLocation();
8
+ const navigate = useNavigate();
9
+ const [searchParams, setSearchParams] = useSearchParams();
10
+ const value = useMemo(
11
+ () => ({
12
+ location: { pathname: location.pathname, search: location.search, state: location.state },
13
+ searchParams,
14
+ navigate: (to, options) => navigate(to, options),
15
+ goBack: () => navigate(-1),
16
+ setSearchParams: (updater, options) => setSearchParams(updater, options),
17
+ Link: RouterLink
18
+ }),
19
+ [location.pathname, location.search, location.state, searchParams, navigate, setSearchParams]
20
+ );
21
+ return /* @__PURE__ */ jsx(UiNavigationProvider, { value, children });
22
+ };
23
+ export {
24
+ UiReactRouterNavigationProvider
25
+ };
26
+ //# sourceMappingURL=ReactRouterNavigationAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactRouterNavigationAdapter.js","sources":["../../../src/components/Navigation/ReactRouterNavigationAdapter.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { Link as ReactRouterDomLink, useLocation, useNavigate, useSearchParams } from 'react-router-dom';\n\nimport { IUiLinkProps, IUiNavigationApi, UiNavigationProvider } from './UiNavigationContext';\n\nconst RouterLink: React.FC<IUiLinkProps> = ({ to, children, ...rest }) => (\n <ReactRouterDomLink to={to} {...rest}>\n {children}\n </ReactRouterDomLink>\n);\n\n/**\n * Binds the navigation port to react-router. The ONLY module in the package that imports\n * `react-router-dom` (an optional peer): hosts without it simply never import this file.\n */\nexport const UiReactRouterNavigationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n const location = useLocation();\n const navigate = useNavigate();\n const [searchParams, setSearchParams] = useSearchParams();\n\n const value = useMemo<IUiNavigationApi>(\n () => ({\n location: { pathname: location.pathname, search: location.search, state: location.state },\n searchParams,\n navigate: (to, options) => navigate(to, options),\n goBack: () => navigate(-1),\n setSearchParams: (updater, options) => setSearchParams(updater, options),\n Link: RouterLink,\n }),\n [location.pathname, location.search, location.state, searchParams, navigate, setSearchParams],\n );\n\n return <UiNavigationProvider value={value}>{children}</UiNavigationProvider>;\n};\n"],"names":["ReactRouterDomLink"],"mappings":";;;;AAKA,MAAM,aAAqC,CAAC,EAAE,IAAI,UAAU,GAAG,KAAA,MAC7D,oBAACA,MAAA,EAAmB,IAAS,GAAG,MAC7B,SAAA,CACH;AAOK,MAAM,kCAA2E,CAAC,EAAE,eAAe;AACxG,QAAM,WAAW,YAAA;AACjB,QAAM,WAAW,YAAA;AACjB,QAAM,CAAC,cAAc,eAAe,IAAI,gBAAA;AAExC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL,UAAU,EAAE,UAAU,SAAS,UAAU,QAAQ,SAAS,QAAQ,OAAO,SAAS,MAAA;AAAA,MAClF;AAAA,MACA,UAAU,CAAC,IAAI,YAAY,SAAS,IAAI,OAAO;AAAA,MAC/C,QAAQ,MAAM,SAAS,EAAE;AAAA,MACzB,iBAAiB,CAAC,SAAS,YAAY,gBAAgB,SAAS,OAAO;AAAA,MACvE,MAAM;AAAA,IAAA;AAAA,IAER,CAAC,SAAS,UAAU,SAAS,QAAQ,SAAS,OAAO,cAAc,UAAU,eAAe;AAAA,EAAA;AAG9F,SAAO,oBAAC,sBAAA,EAAqB,OAAe,SAAA,CAAS;AACvD;"}