@umodeapporg/ui 0.12.0 → 0.13.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 (35) hide show
  1. package/dist/components/Form/ControlledButton.d.ts +12 -0
  2. package/dist/components/Form/ControlledButton.js +10 -5
  3. package/dist/components/Form/ControlledButton.js.map +1 -1
  4. package/dist/components/Form/ControlledDialog.d.ts +9 -2
  5. package/dist/components/Form/ControlledDialog.js +14 -8
  6. package/dist/components/Form/ControlledDialog.js.map +1 -1
  7. package/dist/components/Form/ControlledDialogPrimitives.d.ts +23 -0
  8. package/dist/components/Form/ControlledDialogPrimitives.js +114 -5
  9. package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
  10. package/dist/components/Form/ControlledSelect.d.ts +11 -2
  11. package/dist/components/Form/ControlledSelect.js +273 -48
  12. package/dist/components/Form/ControlledSelect.js.map +1 -1
  13. package/dist/components/Form/ControlledSwitch.d.ts +9 -2
  14. package/dist/components/Form/ControlledSwitch.js +88 -69
  15. package/dist/components/Form/ControlledSwitch.js.map +1 -1
  16. package/dist/components/Navigation/LinkButton.d.ts +26 -0
  17. package/dist/components/Navigation/LinkButton.js +82 -0
  18. package/dist/components/Navigation/LinkButton.js.map +1 -0
  19. package/dist/components/UI/ControlledMenu.d.ts +6 -0
  20. package/dist/components/UI/ControlledMenu.js +3 -4
  21. package/dist/components/UI/ControlledMenu.js.map +1 -1
  22. package/dist/components/UI/DataTable/DataTable.js +1 -0
  23. package/dist/components/UI/DataTable/DataTable.js.map +1 -1
  24. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +2 -1
  25. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js.map +1 -1
  26. package/dist/components/UI/DataTable/DataTableFilters.js +1 -0
  27. package/dist/components/UI/DataTable/DataTableFilters.js.map +1 -1
  28. package/dist/components/UI/DataTable/DataTableToolbar.js +2 -1
  29. package/dist/components/UI/DataTable/DataTableToolbar.js.map +1 -1
  30. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -0
  31. package/dist/components/UI/DataTable/applyDataTableFilters.js.map +1 -1
  32. package/dist/index.d.ts +2 -1
  33. package/dist/index.js +50 -47
  34. package/dist/index.js.map +1 -1
  35. package/package.json +9 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledSelect.js","sources":["../../../src/components/Form/ControlledSelect.tsx"],"sourcesContent":["import { ReactNode, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport InfiniteScroll from 'react-infinite-scroll-component';\nimport { FixedSizeList as List } from 'react-window';\n\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters, formatFilter } from '@/helpers/stringHelpers';\nimport { uiLogger } from '@/helpers/uiLogger';\n\nimport { IFilterOption } from '../../interfaces/IFilterOption';\nimport { UnifiedSearchBar } from '../Search';\nimport OutlinedChip from '../UI/Chips/OutlinedChip';\nimport LoadingSpinner from '../UI/LoadingSpinner';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\nimport NoDataFound from '../UI/NoDataFound';\n\nimport ControlledCheckbox from './ControlledCheckbox';\nimport ControlledInput from './ControlledInput';\n\nimport '../../styles/virtualizedSelect.css';\n\n/**\n * Contract of `fetchMoreOptions`, the single loader of infinite-scroll selects.\n * `reset: true` means \"replace the list with page 1 of `search`\" (a new search, a cleared\n * search, or the restore to the unfiltered baseline). `reset: false` means \"append the NEXT\n * page of the same `search`\". The consumer owns the page counter and the accumulated\n * `options` prop; this component never accumulates results itself.\n */\nexport interface IControlledSelectFetchParams {\n search: string;\n reset: boolean;\n}\n\ntype TControlledSelectProps = {\n label?: string;\n // Mantém a label flutuante visível (encolhida na borda) mesmo com um valor selecionado.\n // Padrão: false — a label some ao selecionar, assumindo que há um rótulo externo acima.\n persistentLabel?: boolean;\n options: Array<IFilterOption>;\n selectedValue: string | Array<string>;\n error?: boolean;\n multiple?: boolean;\n searchable?: boolean;\n disabled?: boolean;\n clearable?: boolean;\n className?: string;\n listClassName?: string;\n onChangeHook: (selected: string | Array<string>) => void;\n infiniteScroll?: boolean;\n fetchMoreOptions?: (params: IControlledSelectFetchParams) => Promise<unknown>;\n hasMoreOptions?: boolean;\n loading?: boolean;\n onClearSearch?: () => void;\n onOpen?: () => void;\n selectedItemsPosition?: 'bottom' | 'top' | 'left' | 'right';\n // When false, the selected-items chips are not rendered beside the trigger;\n // only the count summary (\"N itens selecionados\") shows inside the trigger.\n showSelectedItems?: boolean;\n // Multi-select only: render the selected values as chips inside the trigger\n // field (with a \"+N\" overflow indicator and a tooltip listing all of them)\n // instead of the generic \"N itens selecionados\" count.\n inFieldChips?: boolean;\n // Por padrão as labels passam por capitalizeAllLetters (Primeira Maiúscula, resto\n // minúsculo). Use true quando a label é um identificador literal — nome de tabela,\n // coluna, chave — em que o caixa original é significativo e não pode ser alterado.\n preserveLabelCase?: boolean;\n // Shape of the option swatch. Colors read as the square chip the libraries use;\n // the dot stays the default for tags and categories.\n swatchShape?: 'dot' | 'square';\n // Accessible name of the trigger, for a field whose meaning comes from the\n // surrounding layout and therefore renders no visible label or placeholder.\n ariaLabel?: string;\n // Marks the combobox trigger as required for assistive tech (aria-required).\n ariaRequired?: boolean;\n // Classes for the trigger input itself, for a select embedded in a composed\n // control (a segmented field) that must drop its own border, fill or minimum\n // width. `className` styles the wrapper and cannot reach the input.\n inputClassName?: string;\n // The dropdown has a fixed width (w-80) that fits a field of the default size.\n // Set this when the field is wider (full-width in a dialog, for instance) so the\n // panel, and with it the search box, follows the field instead of staying narrow.\n matchTriggerWidth?: boolean;\n compact?: boolean;\n};\n\nconst ITEM_HEIGHT = 40;\nconst MAX_LIST_HEIGHT = 300;\nconst OVERSCAN_COUNT = 10;\nconst SEARCH_DEBOUNCE_MS = 300;\n// How many selected chips fit inside the trigger before collapsing into \"+N\".\nconst MAX_IN_FIELD_CHIPS = 2;\n\n// Marks the search field so focus can be handed back to it without matching the\n// option checkboxes, which are inputs too.\nconst SEARCH_INPUT_MARKER_CLASS = 'controlled-select-search-input';\n\n/* Padding wrapper. The click is stopped here so a press on the search field is never read\n as a press on an option by the list above it. */\nconst SelectSearchSlot: React.FC<{ children?: ReactNode }> = ({ children }) => (\n <div className=\"px-1 py-0.5\" onClick={(event) => event.stopPropagation()}>\n {children}\n </div>\n);\n\nexport function ControlledSelect({\n label,\n persistentLabel = false,\n options: optionsProp,\n selectedValue,\n error = false,\n multiple = false,\n disabled = false,\n searchable = true,\n clearable = true,\n className,\n listClassName,\n onChangeHook,\n onClearSearch,\n infiniteScroll = false,\n fetchMoreOptions,\n hasMoreOptions = false,\n loading = false,\n onOpen,\n selectedItemsPosition = 'bottom',\n showSelectedItems = true,\n inFieldChips = false,\n preserveLabelCase = false,\n swatchShape = 'dot',\n ariaLabel,\n ariaRequired,\n inputClassName,\n matchTriggerWidth = false,\n compact = false,\n}: TControlledSelectProps) {\n const { t } = useTranslation('ui');\n const options = useMemo(() => (Array.isArray(optionsProp) ? optionsProp : []), [optionsProp]);\n const formatLabel = useCallback(\n (value?: string) => (preserveLabelCase ? (value ?? '') : capitalizeAllLetters(value)),\n [preserveLabelCase],\n );\n const normalizeSelected = useCallback(\n (value: string | Array<string>): string | Array<string> => {\n if (multiple) {\n if (typeof value === 'string') {\n return value === '' ? [] : [value];\n }\n if (Array.isArray(value)) {\n // Legacy multi values arrive as one comma-joined string. A legitimate single value\n // that happens to contain a comma is recognizable by matching a loaded option.\n if (value.length === 1 && value[0].includes(',') && !options.some((option) => option.value === value[0])) {\n return value[0]\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item !== '');\n }\n return value;\n }\n return [];\n } else {\n return typeof value === 'string' ? value : '';\n }\n },\n [multiple, options],\n );\n\n const [selected, setSelected] = useState<string | Array<string>>(() => normalizeSelected(selectedValue));\n\n useEffect(() => {\n setSelected(normalizeSelected(selectedValue));\n }, [selectedValue, normalizeSelected]);\n\n const [filteredOptions, setFilteredOptions] = useState(options);\n const [isOpen, setIsOpen] = useState(false);\n const menuListRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const [currentSearchText, setCurrentSearchText] = useState('');\n\n // Unique per instance: a fixed id would make InfiniteScroll attach its scroll listener to\n // the FIRST matching node in the document, which can be another select's list still\n // mounted while its close animation plays.\n const scrollableContainerId = useId();\n const scrollableContainerRef = useRef<HTMLDivElement>(null);\n const isLoadingMoreRef = useRef(false);\n\n // Re-sync the rendered list when the consumer's options change. In client-filtered mode an\n // active search survives the re-sync (same predicate as UnifiedSearchBar), otherwise a parent\n // re-render would wipe the filter while the term stays in the search box.\n useEffect(() => {\n if (!infiniteScroll && currentSearchText) {\n setFilteredOptions(\n options.filter((option) =>\n [option.label, option.secondaryLabel].some((field) => formatFilter(String(field ?? ''), currentSearchText)),\n ),\n );\n return;\n }\n setFilteredOptions(options);\n }, [options]);\n\n const requestPagedOptions = useCallback(\n async (params: IControlledSelectFetchParams) => {\n if (!fetchMoreOptions) return;\n try {\n await fetchMoreOptions(params);\n } catch (fetchError) {\n uiLogger.error('Failed to fetch select options', fetchError);\n }\n },\n [fetchMoreOptions],\n );\n\n const handleLoadMoreFromScroll = useCallback(async () => {\n if (!infiniteScroll || !hasMoreOptions || isLoadingMoreRef.current) return;\n isLoadingMoreRef.current = true;\n try {\n await requestPagedOptions({ search: currentSearchText, reset: false });\n } finally {\n isLoadingMoreRef.current = false;\n }\n }, [infiniteScroll, hasMoreOptions, requestPagedOptions, currentSearchText]);\n\n const handleServerSearch = useCallback(\n (searchText: string) => {\n setCurrentSearchText(searchText);\n if (!infiniteScroll) return;\n if (scrollableContainerRef.current) scrollableContainerRef.current.scrollTop = 0;\n void requestPagedOptions({ search: searchText, reset: true });\n },\n [infiniteScroll, requestPagedOptions],\n );\n\n // Closing with an active search would leave the consumer's list holding only the search\n // results, so the next open would show a filtered list with an empty search box. Restore\n // the unfiltered baseline as the menu closes.\n useEffect(() => {\n if (isOpen || !infiniteScroll || currentSearchText === '') return;\n setCurrentSearchText('');\n void requestPagedOptions({ search: '', reset: true });\n }, [isOpen]);\n\n const handleSelect = useCallback(\n (option: string, event: React.MouseEvent) => {\n if (multiple) {\n event.stopPropagation();\n event.preventDefault();\n\n const currentSelection = selected as Array<string>;\n if (currentSelection.includes(option)) {\n const newSelection = currentSelection.filter((item) => item !== option);\n setSelected(newSelection);\n onChangeHook(newSelection);\n } else {\n const newSelection = [...currentSelection, option];\n setSelected(newSelection);\n onChangeHook(newSelection);\n }\n } else {\n setSelected(option);\n onChangeHook(option);\n if (!infiniteScroll) {\n setCurrentSearchText('');\n setFilteredOptions(options);\n }\n setIsOpen(false);\n }\n },\n [multiple, selected, onChangeHook, options, infiniteScroll],\n );\n\n const handleClearSelection = useCallback(() => {\n if (multiple) {\n setSelected([]);\n onChangeHook([]);\n } else {\n setSelected('');\n onChangeHook('');\n }\n\n if (!infiniteScroll) {\n setCurrentSearchText('');\n setFilteredOptions(options);\n }\n\n if (onClearSearch) {\n onClearSearch();\n }\n }, [multiple, onChangeHook, onClearSearch, options, infiniteScroll]);\n\n const getDisplayLabel = useCallback(() => {\n if (multiple) {\n const selectedArray = selected as Array<string>;\n if (selectedArray.length === 0) {\n return '';\n } else if (selectedArray.length === 1) {\n const foundOption = options.find((item) => item.value === selectedArray[0]);\n\n return formatLabel(foundOption?.label || selectedArray[0]);\n } else {\n return `${selectedArray.length} itens selecionados`;\n }\n } else {\n const foundOption = options.find((item) => item.value === selected);\n\n return formatLabel(foundOption?.label || (selected as string) || label);\n }\n }, [multiple, selected, options, label, formatLabel]);\n\n const renderOptionItem = useCallback(\n (option: IFilterOption, style?: React.CSSProperties) => {\n const isSelected = multiple\n ? Array.isArray(selected) && selected.includes(option.value)\n : option.value === selected;\n\n // A disabled option explains itself on hover over the WHOLE row, not just the info icon.\n const isDisabledWithReason = Boolean(option.disabled && option.tooltip);\n\n const menuItem = (\n <ControlledMenuItem\n key={isDisabledWithReason ? undefined : option.value}\n disabled={option.disabled}\n onClick={(event: React.MouseEvent) => handleSelect(option.value, event)}\n className={classNames('relative flex items-center p-2 pl-3 text-foreground', {\n // The hover variant has to live inside the selected branch: the menu item's own\n // default hover would otherwise wipe the selected tint.\n 'bg-brand-soft font-medium text-brand-emphasis hover:!bg-brand-soft focus:!bg-brand-soft': isSelected,\n 'hover:!bg-muted': !isSelected,\n })}\n style={{\n ...(isDisabledWithReason ? {} : style),\n height: ITEM_HEIGHT - 1,\n }}\n >\n {isSelected && <span className=\"absolute inset-y-1 left-0 w-0.5 rounded-full bg-brand\" />}\n {multiple && <ControlledCheckbox checked={isSelected} onChange={() => {}} />}\n {option.tooltip && !isDisabledWithReason && (\n <ControlledTooltip content={option.tooltip} className=\"w-full max-w-72\">\n <MaterialSymbol icon=\"info\" size={15} color=\"rgb(var(--color-foreground-subtle))\" className=\"mr-1\" />\n </ControlledTooltip>\n )}\n {option.swatch && (\n <span\n className={classNames(\n 'mr-2 size-3.5 shrink-0 border border-border',\n swatchShape === 'square' ? 'rounded' : 'rounded-full',\n )}\n style={{ backgroundColor: option.swatch }}\n />\n )}\n <span className=\"truncate text-sm\">{formatLabel(option.label)}</span>\n {option.secondaryLabel && (\n <span className=\"ml-auto max-w-[45%] shrink-0 truncate pl-3 text-xs text-foreground-faint\">\n {option.secondaryLabel}\n </span>\n )}\n </ControlledMenuItem>\n );\n\n // The wrapper div carries the virtualized position because the tooltip clones its child and\n // injects its own ref.\n return isDisabledWithReason ? (\n <ControlledTooltip key={option.value} content={option.tooltip} className=\"z-[9999] max-w-72\">\n <div style={style}>{menuItem}</div>\n </ControlledTooltip>\n ) : (\n menuItem\n );\n },\n [multiple, selected, handleSelect, formatLabel],\n );\n\n const ItemRenderer = useCallback(\n ({ index, style }: { index: number; style: React.CSSProperties }) => {\n const option = filteredOptions[index];\n if (!option) return null;\n\n return renderOptionItem(option, style);\n },\n [filteredOptions, renderOptionItem],\n );\n\n const listHeight = useMemo(\n () => Math.min(filteredOptions.length * ITEM_HEIGHT, MAX_LIST_HEIGHT),\n [filteredOptions.length],\n );\n\n const selectedSwatch = useMemo(\n () => (multiple ? undefined : options.find((option) => option.value === selected)?.swatch),\n [multiple, options, selected],\n );\n\n // Com uma opção selecionada, o próprio valor identifica o campo: a label flutuante\n // some (evita duplicar quando já há um rótulo externo acima do select).\n const selectedHasValue = multiple ? (selected as Array<string>).length > 0 : selected !== '';\n\n /**\n * Opção declarada com valor vazio (\"Todas\", \"Nenhum\", \"Sem recorte\"): é uma\n * escolha de verdade, não a ausência de escolha, então o campo precisa exibir\n * o rótulo dela em vez de ficar em branco.\n */\n const hasEmptyValueOption = useMemo(\n () => !multiple && options.some((option) => option.value === ''),\n [multiple, options],\n );\n\n // The dropdown is portaled, so a percentage width would resolve against the\n // page, and a runtime pixel value cannot become a Tailwind arbitrary class.\n // The measured width is written straight onto the node: the floating logic\n // overwrites the MenuList `style` prop with its own position, and React never\n // clears `width` because it is not part of that style object.\n useEffect(() => {\n const menuList = menuListRef.current;\n const trigger = triggerRef.current;\n if (!matchTriggerWidth || !isOpen || !menuList || !trigger) return undefined;\n\n const applyTriggerWidth = () => {\n menuList.style.width = `${trigger.getBoundingClientRect().width}px`;\n };\n\n applyTriggerWidth();\n\n const resizeObserver = new ResizeObserver(applyTriggerWidth);\n resizeObserver.observe(trigger);\n\n return () => resizeObserver.disconnect();\n }, [matchTriggerWidth, isOpen]);\n\n // Moving the pointer inside the menu makes floating-ui's list navigation pull\n // focus off the search field (it focuses the hovered option, or the menu\n // container on pointer leave). Take focus back whenever the pointer caused it,\n // while leaving keyboard navigation into the options alone.\n useEffect(() => {\n const menuList = menuListRef.current;\n if (!isOpen || !searchable || !menuList) return undefined;\n\n let isKeyboardInteraction = false;\n let restoreFrame = 0;\n\n const markKeyboardInteraction = () => {\n isKeyboardInteraction = true;\n };\n\n const markPointerInteraction = () => {\n isKeyboardInteraction = false;\n };\n\n const restoreSearchFocus = (event: FocusEvent) => {\n const searchInput = menuList.querySelector<HTMLInputElement>(`.${SEARCH_INPUT_MARKER_CLASS}`);\n if (!searchInput || event.target !== searchInput || isKeyboardInteraction) return;\n\n const nextFocused = event.relatedTarget as Node | null;\n if (nextFocused && !menuList.contains(nextFocused)) return;\n\n // The steal happens as this event is dispatched, so reclaim on the next frame.\n restoreFrame = window.requestAnimationFrame(() => searchInput.focus({ preventScroll: true }));\n };\n\n menuList.addEventListener('keydown', markKeyboardInteraction, true);\n menuList.addEventListener('pointermove', markPointerInteraction, true);\n menuList.addEventListener('focusout', restoreSearchFocus);\n\n return () => {\n window.cancelAnimationFrame(restoreFrame);\n menuList.removeEventListener('keydown', markKeyboardInteraction, true);\n menuList.removeEventListener('pointermove', markPointerInteraction, true);\n menuList.removeEventListener('focusout', restoreSearchFocus);\n };\n }, [isOpen, searchable]);\n\n const handleClearSearch = useCallback(() => {\n setCurrentSearchText('');\n\n if (infiniteScroll) {\n if (scrollableContainerRef.current) scrollableContainerRef.current.scrollTop = 0;\n void requestPagedOptions({ search: '', reset: true });\n } else {\n setFilteredOptions(options);\n }\n\n if (onClearSearch) {\n onClearSearch();\n }\n }, [infiniteScroll, requestPagedOptions, onClearSearch, options]);\n\n const getSelectedOptionsLabels = useCallback(() => {\n if (!multiple || !selected || (selected as Array<string>).length === 0) {\n return [];\n }\n\n const selectedArray = selected as Array<string>;\n\n return selectedArray.map((value) => {\n const trimmedValue = value.trim();\n const foundOption = options.find((option) => option.value === trimmedValue);\n return {\n value: trimmedValue,\n label: foundOption?.label || trimmedValue,\n swatch: foundOption?.swatch,\n };\n });\n }, [multiple, selected, options]);\n\n const isLateralPosition = selectedItemsPosition === 'left' || selectedItemsPosition === 'right';\n\n const inFieldChipItems = inFieldChips ? getSelectedOptionsLabels() : [];\n const showInFieldChips = inFieldChips && multiple && inFieldChipItems.length > 0;\n const visibleInFieldChips = inFieldChipItems.slice(0, MAX_IN_FIELD_CHIPS);\n const inFieldOverflow = inFieldChipItems.length - visibleInFieldChips.length;\n const inFieldChipsTooltip = inFieldChipItems.map((option) => formatLabel(option.label)).join(', ');\n\n const selectedItemsChips = showSelectedItems && multiple && getSelectedOptionsLabels().length > 1 && (\n <div className=\"flex flex-wrap gap-1\">\n {getSelectedOptionsLabels().map((option) => (\n <OutlinedChip\n key={option.value}\n label={option.label}\n preserveLabelCase={preserveLabelCase}\n dot={Boolean(option.swatch)}\n dotColor={option.swatch}\n className={classNames('bg-muted-active text-lg', { 'px-4 py-1': isLateralPosition })}\n labelClassName={isLateralPosition ? 'text-sm' : undefined}\n onClose={(event: React.MouseEvent) => handleSelect(option.value, event)}\n canClose\n />\n ))}\n </div>\n );\n\n const showClear =\n clearable &&\n !disabled &&\n Boolean(selected) &&\n (multiple ? (selected as Array<string>).length > 0 : selected !== '');\n\n const cursorClassName = disabled ? 'cursor-not-allowed' : 'cursor-pointer';\n\n return (\n <ControlledMenuRoot\n placement=\"bottom-start\"\n open={isOpen && !disabled}\n handler={(next) => {\n if (disabled) return;\n setIsOpen(next);\n if (next) onOpen?.();\n }}\n dismiss={{ itemPress: !multiple }}\n >\n <ControlledMenuHandler>\n <div\n className={classNames('flex', {\n 'flex-col gap-y-2': selectedItemsPosition === 'bottom' || selectedItemsPosition === 'top',\n 'flex-row items-start gap-x-2': selectedItemsPosition === 'left' || selectedItemsPosition === 'right',\n })}\n >\n {(selectedItemsPosition === 'top' || selectedItemsPosition === 'left') && selectedItemsChips}\n <div ref={triggerRef} className={classNames('relative', cursorClassName, className)}>\n {selectedSwatch && (\n <span\n className={classNames(\n 'pointer-events-none absolute left-3 top-1/2 z-10 size-3.5 -translate-y-1/2 border border-border',\n swatchShape === 'square' ? 'rounded' : 'rounded-full',\n )}\n style={{ backgroundColor: selectedSwatch }}\n />\n )}\n {showInFieldChips && (\n <ControlledTooltip content={inFieldChipsTooltip} className=\"max-w-72\">\n <div\n className={classNames(\n 'absolute inset-y-0 left-2.5 right-14 z-10 flex items-center gap-1 overflow-hidden',\n cursorClassName,\n )}\n >\n {visibleInFieldChips.map((option) => (\n <OutlinedChip\n key={option.value}\n label={option.label}\n preserveLabelCase={preserveLabelCase}\n dot={Boolean(option.swatch)}\n dotColor={option.swatch}\n className=\"min-w-0 shrink-0 bg-muted-active\"\n labelClassName=\"text-xxs\"\n />\n ))}\n {inFieldOverflow > 0 && (\n <OutlinedChip\n label={t('controlledSelect.moreItems', { count: inFieldOverflow })}\n className=\"min-w-0 shrink-0 bg-muted-active\"\n labelClassName=\"text-xxs\"\n />\n )}\n </div>\n </ControlledTooltip>\n )}\n <ControlledInput\n readOnly\n role=\"combobox\"\n // eslint-disable-next-line i18n-check/no-literal-jsx-strings -- ARIA token, not user-facing text\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={scrollableContainerId}\n onKeyDown={(event) => {\n if (disabled) return;\n if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {\n event.preventDefault();\n setIsOpen(true);\n onOpen?.();\n } else if (event.key === 'Escape' && isOpen) {\n setIsOpen(false);\n }\n }}\n className={classNames(\n 'controlled-select-input flex w-full min-w-0 justify-between truncate text-left',\n !compact && 'tablet:min-w-52',\n cursorClassName,\n selectedSwatch && '!pl-9',\n // O \"x\" é absoluto SOBRE o input: sem esta reserva o rótulo passa por baixo dele.\n showClear && '!pr-14',\n inputClassName,\n )}\n error={error}\n autoComplete=\"off\"\n rightSlot={<MaterialSymbol className={cursorClassName} icon=\"keyboard_arrow_down\" size={15} />}\n label={selectedHasValue && !persistentLabel ? undefined : label}\n aria-label={ariaLabel ?? label}\n aria-required={ariaRequired ? 'true' : undefined}\n value={showInFieldChips ? ' ' : selectedHasValue || hasEmptyValueOption ? getDisplayLabel() : ''}\n disabled={disabled}\n />\n\n {showClear && (\n <ControlledTooltip content={t('dates.clearSelection')}>\n <div\n onClick={handleClearSelection}\n className=\"absolute inset-y-0 right-8 top-0.5 flex cursor-pointer items-center\"\n >\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n\n {(selectedItemsPosition === 'bottom' || selectedItemsPosition === 'right') && selectedItemsChips}\n </div>\n </ControlledMenuHandler>\n\n <ControlledMenuList\n ref={menuListRef}\n className={classNames('zIndexMax w-80 max-w-[calc(100vw-1.5rem)] overflow-hidden', listClassName)}\n >\n {searchable && (\n <SelectSearchSlot>\n <UnifiedSearchBar\n mode=\"default\"\n label={t('actions.search')}\n debounceTime={SEARCH_DEBOUNCE_MS}\n // Em modo infinito quem filtra é o servidor: sem itens, a barra só reporta o\n // termo via onSearchTermChange e nunca escreve na lista local.\n searchableItems={infiniteScroll ? null : options}\n searchKeys={['label', 'secondaryLabel']}\n setFilteredItems={setFilteredOptions}\n onSearchTermChange={handleServerSearch}\n className={classNames('w-full', SEARCH_INPUT_MARKER_CLASS)}\n onClearFilter={handleClearSearch}\n initialValue={currentSearchText}\n />\n </SelectSearchSlot>\n )}\n\n <div\n ref={scrollableContainerRef}\n className={classNames({\n 'max-h-[250px] overflow-y-auto overscroll-contain': infiniteScroll,\n 'overflow-hidden': !infiniteScroll,\n })}\n id={scrollableContainerId}\n >\n {infiniteScroll ? (\n <InfiniteScroll\n dataLength={filteredOptions.length}\n // O loader do InfiniteScroll é renderizado sempre que `hasMore` é verdadeiro, ao lado\n // do conteúdo. Com a lista vazia isso empilhava o spinner sobre o estado vazio (ou\n // sobre o próprio spinner de `loading`): sem opções, quem manda é o bloco abaixo.\n hasMore={hasMoreOptions && filteredOptions.length > 0}\n next={handleLoadMoreFromScroll}\n loader={\n <div className=\"flex size-full items-center justify-center py-1\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n }\n scrollableTarget={scrollableContainerId}\n // `overflow-hidden` makes this a scroll port the user cannot scroll, so\n // `overscroll-none` here stopped the wheel from ever reaching the container\n // that does scroll: the list only moved by dragging its bar. Containment\n // belongs to that container, which already declares `overscroll-contain`.\n className=\"!overflow-hidden\"\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option) => renderOptionItem(option))\n ) : loading ? (\n <div className=\"flex w-full items-center justify-center py-4\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n ) : (\n <ControlledMenuItem disabled>\n <NoDataFound compact message={t('controlledSelect.noDataToShow')} />\n </ControlledMenuItem>\n )}\n </InfiniteScroll>\n ) : filteredOptions.length > 0 ? (\n <List\n className=\"virtualized-select-list\"\n height={listHeight}\n // eslint-disable-next-line i18n-check/no-literal-jsx-strings\n width=\"100%\"\n itemCount={filteredOptions.length}\n itemSize={ITEM_HEIGHT}\n overscanCount={OVERSCAN_COUNT}\n >\n {ItemRenderer}\n </List>\n ) : loading ? (\n <div className=\"flex w-full items-center justify-center py-4\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n ) : (\n <ControlledMenuItem disabled>\n <NoDataFound compact message={t('controlledSelect.noDataToShow')} />\n </ControlledMenuItem>\n )}\n </div>\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n}\n"],"names":["List"],"mappings":"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"}
1
+ {"version":3,"file":"ControlledSelect.js","sources":["../../../src/components/Form/ControlledSelect.tsx"],"sourcesContent":["import { ReactNode, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport InfiniteScroll from 'react-infinite-scroll-component';\nimport { FixedSizeList as List } from 'react-window';\n\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters, formatFilter } from '@/helpers/stringHelpers';\nimport { uiLogger } from '@/helpers/uiLogger';\n\nimport { IFilterOption } from '../../interfaces/IFilterOption';\nimport UnifiedSearchBar from '../Search/UnifiedSearchBar';\nimport OutlinedChip from '../UI/Chips/OutlinedChip';\nimport LoadingSpinner from '../UI/LoadingSpinner';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\nimport NoDataFound from '../UI/NoDataFound';\n\nimport ControlledCheckbox from './ControlledCheckbox';\nimport ControlledInput from './ControlledInput';\n\nimport '../../styles/virtualizedSelect.css';\n\n/**\n * Contract of `fetchMoreOptions`, the single loader of infinite-scroll selects.\n * `reset: true` means \"replace the list with page 1 of `search`\" (a new search, a cleared\n * search, or the restore to the unfiltered baseline). `reset: false` means \"append the NEXT\n * page of the same `search`\". The consumer owns the page counter and the accumulated\n * `options` prop; this component never accumulates results itself.\n */\nexport interface IControlledSelectFetchParams {\n search: string;\n reset: boolean;\n}\n\ntype TDataAttributes = { [key: `data-${string}`]: string | number | boolean | undefined };\n\n/**\n * Native and ARIA attributes forwarded to the combobox trigger: `id` (so a `<label htmlFor>`\n * names it), `aria-labelledby` / `aria-describedby` / `aria-invalid`, `data-*`, `onBlur`.\n * `name` is submitted through hidden inputs carrying the selected value(s), never the label.\n */\ntype TControlledSelectTriggerProps = Pick<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'id' | 'name' | 'required' | 'onBlur' | 'onFocus' | 'autoFocus'\n> &\n React.AriaAttributes &\n TDataAttributes;\n\ntype TControlledSelectProps = TControlledSelectTriggerProps & {\n label?: string;\n // Mantém a label flutuante visível (encolhida na borda) mesmo com um valor selecionado.\n // Padrão: false — a label some ao selecionar, assumindo que há um rótulo externo acima.\n persistentLabel?: boolean;\n options: Array<IFilterOption>;\n selectedValue: string | Array<string>;\n error?: boolean;\n multiple?: boolean;\n searchable?: boolean;\n disabled?: boolean;\n clearable?: boolean;\n className?: string;\n listClassName?: string;\n onChangeHook: (selected: string | Array<string>) => void;\n infiniteScroll?: boolean;\n fetchMoreOptions?: (params: IControlledSelectFetchParams) => Promise<unknown>;\n hasMoreOptions?: boolean;\n loading?: boolean;\n onClearSearch?: () => void;\n onOpen?: () => void;\n selectedItemsPosition?: 'bottom' | 'top' | 'left' | 'right';\n // When false, the selected-items chips are not rendered beside the trigger;\n // only the count summary (\"N itens selecionados\") shows inside the trigger.\n showSelectedItems?: boolean;\n // Multi-select only: render the selected values as chips inside the trigger\n // field (with a \"+N\" overflow indicator and a tooltip listing all of them)\n // instead of the generic \"N itens selecionados\" count.\n inFieldChips?: boolean;\n // Por padrão as labels passam por capitalizeAllLetters (Primeira Maiúscula, resto\n // minúsculo). Use true quando a label é um identificador literal — nome de tabela,\n // coluna, chave — em que o caixa original é significativo e não pode ser alterado.\n preserveLabelCase?: boolean;\n // Shape of the option swatch. Colors read as the square chip the libraries use;\n // the dot stays the default for tags and categories.\n swatchShape?: 'dot' | 'square';\n // Accessible name of the trigger, for a field whose meaning comes from the\n // surrounding layout and therefore renders no visible label or placeholder.\n ariaLabel?: string;\n // Marks the combobox trigger as required for assistive tech (aria-required).\n ariaRequired?: boolean;\n // Classes for the trigger input itself, for a select embedded in a composed\n // control (a segmented field) that must drop its own border, fill or minimum\n // width. `className` styles the wrapper and cannot reach the input.\n inputClassName?: string;\n // The dropdown has a fixed width (w-80) that fits a field of the default size.\n // Set this when the field is wider (full-width in a dialog, for instance) so the\n // panel, and with it the search box, follows the field instead of staying narrow.\n matchTriggerWidth?: boolean;\n compact?: boolean;\n};\n\nconst ITEM_HEIGHT = 40;\nconst MAX_LIST_HEIGHT = 300;\nconst OVERSCAN_COUNT = 10;\nconst SEARCH_DEBOUNCE_MS = 300;\n// How many selected chips fit inside the trigger before collapsing into \"+N\".\nconst MAX_IN_FIELD_CHIPS = 2;\n\n// Marks the search field so focus can be handed back to it without matching the\n// option checkboxes, which are inputs too.\nconst SEARCH_INPUT_MARKER_CLASS = 'controlled-select-search-input';\nconst TYPEAHEAD_RESET_MS = 500;\nconst FOCUS_RETRY_FRAMES = 6;\n\nconst isPrintableKey = (event: React.KeyboardEvent) =>\n event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;\n\n/* Padding wrapper. The click is stopped here so a press on the search field is never read\n as a press on an option by the list above it. */\nconst SelectSearchSlot: React.FC<{ children?: ReactNode }> = ({ children }) => (\n <div className=\"px-1 py-0.5\" onClick={(event) => event.stopPropagation()}>\n {children}\n </div>\n);\n\nexport function ControlledSelect({\n label,\n persistentLabel = false,\n options: optionsProp,\n selectedValue,\n error = false,\n multiple = false,\n disabled = false,\n searchable = true,\n clearable = true,\n className,\n listClassName,\n onChangeHook,\n onClearSearch,\n infiniteScroll = false,\n fetchMoreOptions,\n hasMoreOptions = false,\n loading = false,\n onOpen,\n selectedItemsPosition = 'bottom',\n showSelectedItems = true,\n inFieldChips = false,\n preserveLabelCase = false,\n swatchShape = 'dot',\n ariaLabel,\n ariaRequired,\n inputClassName,\n matchTriggerWidth = false,\n compact = false,\n id,\n name,\n required,\n onBlur,\n onFocus,\n autoFocus,\n ...triggerAttributes\n}: TControlledSelectProps) {\n const { t } = useTranslation('ui');\n const options = useMemo(() => (Array.isArray(optionsProp) ? optionsProp : []), [optionsProp]);\n const formatLabel = useCallback(\n (value?: string) => (preserveLabelCase ? (value ?? '') : capitalizeAllLetters(value)),\n [preserveLabelCase],\n );\n const normalizeSelected = useCallback(\n (value: string | Array<string>): string | Array<string> => {\n if (multiple) {\n if (typeof value === 'string') {\n return value === '' ? [] : [value];\n }\n if (Array.isArray(value)) {\n // Legacy multi values arrive as one comma-joined string. A legitimate single value\n // that happens to contain a comma is recognizable by matching a loaded option.\n if (value.length === 1 && value[0].includes(',') && !options.some((option) => option.value === value[0])) {\n return value[0]\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item !== '');\n }\n return value;\n }\n return [];\n } else {\n return typeof value === 'string' ? value : '';\n }\n },\n [multiple, options],\n );\n\n const [selected, setSelected] = useState<string | Array<string>>(() => normalizeSelected(selectedValue));\n\n useEffect(() => {\n setSelected(normalizeSelected(selectedValue));\n }, [selectedValue, normalizeSelected]);\n\n const [filteredOptions, setFilteredOptions] = useState(options);\n const [isOpen, setIsOpen] = useState(false);\n const menuListRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const triggerInputRef = useRef<HTMLInputElement>(null);\n const virtualListRef = useRef<List>(null);\n const [activeIndex, setActiveIndex] = useState(-1);\n const [focusRequest, setFocusRequest] = useState(0);\n const pendingFocusIndexRef = useRef<number | null>(null);\n const typeaheadRef = useRef({ buffer: '', timer: 0 });\n const [currentSearchText, setCurrentSearchText] = useState('');\n\n // Unique per instance: a fixed id would make InfiniteScroll attach its scroll listener to\n // the FIRST matching node in the document, which can be another select's list still\n // mounted while its close animation plays.\n const scrollableContainerId = useId();\n const scrollableContainerRef = useRef<HTMLDivElement>(null);\n const isLoadingMoreRef = useRef(false);\n\n // Re-sync the rendered list when the consumer's options change. In client-filtered mode an\n // active search survives the re-sync (same predicate as UnifiedSearchBar), otherwise a parent\n // re-render would wipe the filter while the term stays in the search box.\n useEffect(() => {\n if (!infiniteScroll && currentSearchText) {\n setFilteredOptions(\n options.filter((option) =>\n [option.label, option.secondaryLabel].some((field) => formatFilter(String(field ?? ''), currentSearchText)),\n ),\n );\n return;\n }\n setFilteredOptions(options);\n }, [options]);\n\n const requestPagedOptions = useCallback(\n async (params: IControlledSelectFetchParams) => {\n if (!fetchMoreOptions) return;\n try {\n await fetchMoreOptions(params);\n } catch (fetchError) {\n uiLogger.error('Failed to fetch select options', fetchError);\n }\n },\n [fetchMoreOptions],\n );\n\n const handleLoadMoreFromScroll = useCallback(async () => {\n if (!infiniteScroll || !hasMoreOptions || isLoadingMoreRef.current) return;\n isLoadingMoreRef.current = true;\n try {\n await requestPagedOptions({ search: currentSearchText, reset: false });\n } finally {\n isLoadingMoreRef.current = false;\n }\n }, [infiniteScroll, hasMoreOptions, requestPagedOptions, currentSearchText]);\n\n const handleServerSearch = useCallback(\n (searchText: string) => {\n setCurrentSearchText(searchText);\n if (!infiniteScroll) return;\n if (scrollableContainerRef.current) scrollableContainerRef.current.scrollTop = 0;\n void requestPagedOptions({ search: searchText, reset: true });\n },\n [infiniteScroll, requestPagedOptions],\n );\n\n // Closing with an active search would leave the consumer's list holding only the search\n // results, so the next open would show a filtered list with an empty search box. Restore\n // the unfiltered baseline as the menu closes.\n useEffect(() => {\n if (isOpen || !infiniteScroll || currentSearchText === '') return;\n setCurrentSearchText('');\n void requestPagedOptions({ search: '', reset: true });\n }, [isOpen]);\n\n const handleSelect = useCallback(\n (option: string, event: React.MouseEvent) => {\n if (multiple) {\n event.stopPropagation();\n event.preventDefault();\n\n const currentSelection = selected as Array<string>;\n if (currentSelection.includes(option)) {\n const newSelection = currentSelection.filter((item) => item !== option);\n setSelected(newSelection);\n onChangeHook(newSelection);\n } else {\n const newSelection = [...currentSelection, option];\n setSelected(newSelection);\n onChangeHook(newSelection);\n }\n } else {\n setSelected(option);\n onChangeHook(option);\n if (!infiniteScroll) {\n setCurrentSearchText('');\n setFilteredOptions(options);\n }\n setIsOpen(false);\n }\n },\n [multiple, selected, onChangeHook, options, infiniteScroll],\n );\n\n const handleClearSelection = useCallback(() => {\n if (multiple) {\n setSelected([]);\n onChangeHook([]);\n } else {\n setSelected('');\n onChangeHook('');\n }\n\n if (!infiniteScroll) {\n setCurrentSearchText('');\n setFilteredOptions(options);\n }\n\n if (onClearSearch) {\n onClearSearch();\n }\n }, [multiple, onChangeHook, onClearSearch, options, infiniteScroll]);\n\n const getDisplayLabel = useCallback(() => {\n if (multiple) {\n const selectedArray = selected as Array<string>;\n if (selectedArray.length === 0) {\n return '';\n } else if (selectedArray.length === 1) {\n const foundOption = options.find((item) => item.value === selectedArray[0]);\n\n return formatLabel(foundOption?.label || selectedArray[0]);\n } else {\n return `${selectedArray.length} itens selecionados`;\n }\n } else {\n const foundOption = options.find((item) => item.value === selected);\n\n return formatLabel(foundOption?.label || (selected as string) || label);\n }\n }, [multiple, selected, options, label, formatLabel]);\n\n const getOptionId = useCallback(\n (index: number) => `${scrollableContainerId}-option-${index}`,\n [scrollableContainerId],\n );\n\n const isOptionEnabled = useCallback(\n (index: number) => {\n const option = filteredOptions[index];\n return Boolean(option) && !option.disabled;\n },\n [filteredOptions],\n );\n\n /** Next enabled index from `start` walking by `step`, or -1. Never wraps (APG select-only). */\n const findEnabledIndex = useCallback(\n (start: number, step: 1 | -1) => {\n for (let index = start; index >= 0 && index < filteredOptions.length; index += step) {\n if (isOptionEnabled(index)) return index;\n }\n return -1;\n },\n [filteredOptions.length, isOptionEnabled],\n );\n\n const getSelectedIndex = useCallback(() => {\n const firstSelected = multiple ? (selected as Array<string>)[0] : (selected as string);\n const index = filteredOptions.findIndex((option) => option.value === firstSelected);\n return index >= 0 && isOptionEnabled(index) ? index : -1;\n }, [multiple, selected, filteredOptions, isOptionEnabled]);\n\n const focusOptionAt = useCallback((index: number) => {\n if (index < 0) return;\n setActiveIndex(index);\n pendingFocusIndexRef.current = index;\n setFocusRequest((request) => request + 1);\n }, []);\n\n const focusSearchInput = useCallback(() => {\n const searchInput = menuListRef.current?.querySelector<HTMLInputElement>(`.${SEARCH_INPUT_MARKER_CLASS}`);\n if (!searchInput) return false;\n searchInput.focus();\n return true;\n }, []);\n\n const findTypeaheadMatch = useCallback(\n (key: string, fromIndex: number) => {\n const state = typeaheadRef.current;\n window.clearTimeout(state.timer);\n state.buffer += key.toLowerCase();\n state.timer = window.setTimeout(() => {\n state.buffer = '';\n }, TYPEAHEAD_RESET_MS);\n\n // Repeating one letter cycles through the options that start with it.\n const isCycling = state.buffer.split('').every((char) => char === state.buffer[0]);\n const term = isCycling ? state.buffer[0] : state.buffer;\n const start = isCycling ? fromIndex + 1 : Math.max(fromIndex, 0);\n const total = filteredOptions.length;\n\n for (let offset = 0; offset < total; offset += 1) {\n const index = (start + offset) % total;\n const label = formatLabel(filteredOptions[index]?.label).toLowerCase();\n if (isOptionEnabled(index) && label.startsWith(term)) return index;\n }\n return -1;\n },\n [filteredOptions, formatLabel, isOptionEnabled],\n );\n\n type TOpenTarget = 'selected' | 'first' | 'last' | number;\n\n const openAndFocus = useCallback(\n (target: TOpenTarget) => {\n if (disabled) return;\n if (!isOpen) {\n setIsOpen(true);\n onOpen?.();\n }\n\n if (searchable && target === 'selected') {\n // With a search field the keyboard lands on it; ArrowDown then walks into the options.\n pendingFocusIndexRef.current = -1;\n setFocusRequest((request) => request + 1);\n return;\n }\n\n let index: number;\n if (typeof target === 'number') index = target;\n else if (target === 'first') index = findEnabledIndex(0, 1);\n else if (target === 'last') index = findEnabledIndex(filteredOptions.length - 1, -1);\n else index = getSelectedIndex() >= 0 ? getSelectedIndex() : findEnabledIndex(0, 1);\n focusOptionAt(index);\n },\n [disabled, isOpen, onOpen, searchable, findEnabledIndex, filteredOptions.length, getSelectedIndex, focusOptionAt],\n );\n\n // Moves DOM focus to the requested option once it is rendered: a virtualized row far from\n // the viewport only exists after react-window re-renders at the new scroll offset.\n useEffect(() => {\n const index = pendingFocusIndexRef.current;\n if (!isOpen || index === null) return undefined;\n\n if (index >= 0) virtualListRef.current?.scrollToItem(index);\n\n let attempts = 0;\n let frame = 0;\n const tryFocus = () => {\n if (index < 0) {\n if (focusSearchInput()) {\n pendingFocusIndexRef.current = null;\n return;\n }\n } else {\n const node = document.getElementById(getOptionId(index));\n if (node) {\n pendingFocusIndexRef.current = null;\n node.focus({ preventScroll: Boolean(virtualListRef.current) });\n if (!virtualListRef.current) node.scrollIntoView?.({ block: 'nearest' });\n return;\n }\n }\n attempts += 1;\n if (attempts < FOCUS_RETRY_FRAMES) frame = window.requestAnimationFrame(tryFocus);\n };\n\n tryFocus();\n return () => window.cancelAnimationFrame(frame);\n }, [isOpen, focusRequest]);\n\n // Closing hands focus back to the trigger when it was inside the list (a keyboard or pointer\n // selection, ESC) or was lost with it; a click elsewhere keeps the focus it moved.\n const wasOpenRef = useRef(false);\n useEffect(() => {\n if (isOpen) {\n wasOpenRef.current = true;\n return;\n }\n if (!wasOpenRef.current) return;\n wasOpenRef.current = false;\n setActiveIndex(-1);\n pendingFocusIndexRef.current = null;\n\n const active = document.activeElement;\n const focusWasInList = Boolean(active && menuListRef.current?.contains(active));\n if (focusWasInList || active === document.body || active === null) {\n triggerInputRef.current?.focus({ preventScroll: true });\n }\n }, [isOpen]);\n\n useEffect(() => {\n setActiveIndex(-1);\n }, [filteredOptions]);\n\n // A multi-select toggle re-renders the virtualized rows, which replaces the focused node:\n // put focus back on the same option so the keyboard user keeps their place.\n useEffect(() => {\n if (!isOpen || activeIndex < 0) return;\n const active = document.activeElement;\n if (active === null || active === document.body) focusOptionAt(activeIndex);\n }, [selected]);\n\n useEffect(() => () => window.clearTimeout(typeaheadRef.current.timer), []);\n\n // The list is named like its trigger, including a trigger named only by `<label htmlFor>`.\n const listboxLabel =\n triggerAttributes['aria-label'] ??\n ariaLabel ??\n label ??\n (isOpen ? triggerInputRef.current?.labels?.[0]?.textContent?.trim() || undefined : undefined);\n\n const handleTriggerKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n event.preventDefault();\n openAndFocus(isOpen && event.key === 'ArrowDown' && activeIndex >= 0 ? activeIndex : 'selected');\n return;\n case 'ArrowUp':\n event.preventDefault();\n openAndFocus(getSelectedIndex() >= 0 ? getSelectedIndex() : 'last');\n return;\n case 'Home':\n event.preventDefault();\n openAndFocus('first');\n return;\n case 'End':\n event.preventDefault();\n openAndFocus('last');\n return;\n case 'Escape':\n if (!isOpen) return;\n // Marks it handled so an enclosing dialog does not close too.\n event.preventDefault();\n setIsOpen(false);\n return;\n case 'Tab':\n if (isOpen) setIsOpen(false);\n return;\n default:\n if (!isPrintableKey(event) || searchable) return;\n event.preventDefault();\n openAndFocus(findTypeaheadMatch(event.key, getSelectedIndex()));\n }\n };\n\n const handleListKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n const target = event.target as HTMLElement;\n const isOnSearch = target.classList.contains(SEARCH_INPUT_MARKER_CLASS);\n const optionNode = target.closest<HTMLElement>('[role=\"option\"]');\n const currentIndex = optionNode ? Number(optionNode.dataset.optionIndex ?? -1) : activeIndex;\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const next = isOnSearch ? findEnabledIndex(0, 1) : findEnabledIndex(currentIndex + 1, 1);\n focusOptionAt(next);\n return;\n }\n case 'ArrowUp': {\n if (isOnSearch) return;\n event.preventDefault();\n const previous = findEnabledIndex(currentIndex - 1, -1);\n if (previous >= 0) focusOptionAt(previous);\n else if (searchable) focusSearchInput();\n return;\n }\n case 'Home':\n case 'End':\n if (isOnSearch) return;\n event.preventDefault();\n focusOptionAt(event.key === 'Home' ? findEnabledIndex(0, 1) : findEnabledIndex(filteredOptions.length - 1, -1));\n return;\n case 'Escape':\n event.preventDefault();\n setIsOpen(false);\n return;\n case 'Tab':\n event.preventDefault();\n setIsOpen(false);\n triggerInputRef.current?.focus({ preventScroll: true });\n return;\n default:\n if (isOnSearch || !isPrintableKey(event) || event.key === ' ') return;\n event.preventDefault();\n focusOptionAt(findTypeaheadMatch(event.key, currentIndex));\n }\n };\n\n const renderOptionItem = useCallback(\n (option: IFilterOption, index: number, style?: React.CSSProperties) => {\n const isSelected = multiple\n ? Array.isArray(selected) && selected.includes(option.value)\n : option.value === selected;\n\n // A disabled option explains itself on hover over the WHOLE row, not just the info icon.\n const isDisabledWithReason = Boolean(option.disabled && option.tooltip);\n\n const menuItem = (\n <ControlledMenuItem\n key={isDisabledWithReason ? undefined : option.value}\n disabled={option.disabled}\n onClick={(event: React.MouseEvent) => handleSelect(option.value, event)}\n role=\"option\"\n id={getOptionId(index)}\n tabIndex={-1}\n aria-selected={isSelected}\n aria-setsize={filteredOptions.length}\n aria-posinset={index + 1}\n data-option-index={index}\n className={classNames(\n 'relative flex items-center p-2 pl-3 text-foreground',\n 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',\n {\n // The hover variant has to live inside the selected branch: the menu item's own\n // default hover would otherwise wipe the selected tint.\n 'bg-brand-soft font-medium text-brand-emphasis hover:!bg-brand-soft focus:!bg-brand-soft': isSelected,\n 'hover:!bg-muted': !isSelected,\n },\n )}\n style={{\n ...(isDisabledWithReason ? {} : style),\n height: ITEM_HEIGHT - 1,\n }}\n >\n {isSelected && <span className=\"absolute inset-y-1 left-0 w-0.5 rounded-full bg-brand\" />}\n {multiple && (\n // The option itself carries aria-selected; the checkbox is only its picture.\n <span aria-hidden className=\"pointer-events-none\">\n <ControlledCheckbox checked={isSelected} onChange={() => {}} />\n </span>\n )}\n {option.tooltip && !isDisabledWithReason && (\n <ControlledTooltip content={option.tooltip} className=\"w-full max-w-72\">\n <MaterialSymbol icon=\"info\" size={15} color=\"rgb(var(--color-foreground-subtle))\" className=\"mr-1\" />\n </ControlledTooltip>\n )}\n {option.swatch && (\n <span\n className={classNames(\n 'mr-2 size-3.5 shrink-0 border border-border',\n swatchShape === 'square' ? 'rounded' : 'rounded-full',\n )}\n style={{ backgroundColor: option.swatch }}\n />\n )}\n <span className=\"truncate text-sm\">{formatLabel(option.label)}</span>\n {option.secondaryLabel && (\n <span className=\"ml-auto max-w-[45%] shrink-0 truncate pl-3 text-xs text-foreground-faint\">\n {option.secondaryLabel}\n </span>\n )}\n </ControlledMenuItem>\n );\n\n // The wrapper div carries the virtualized position because the tooltip clones its child and\n // injects its own ref.\n return isDisabledWithReason ? (\n <ControlledTooltip key={option.value} content={option.tooltip} className=\"z-[9999] max-w-72\">\n <div style={style}>{menuItem}</div>\n </ControlledTooltip>\n ) : (\n menuItem\n );\n },\n [multiple, selected, handleSelect, formatLabel, getOptionId, filteredOptions.length],\n );\n\n const ItemRenderer = useCallback(\n ({ index, style }: { index: number; style: React.CSSProperties }) => {\n const option = filteredOptions[index];\n if (!option) return null;\n\n return renderOptionItem(option, index, style);\n },\n [filteredOptions, renderOptionItem],\n );\n\n const listHeight = useMemo(\n () => Math.min(filteredOptions.length * ITEM_HEIGHT, MAX_LIST_HEIGHT),\n [filteredOptions.length],\n );\n\n const selectedSwatch = useMemo(\n () => (multiple ? undefined : options.find((option) => option.value === selected)?.swatch),\n [multiple, options, selected],\n );\n\n // Com uma opção selecionada, o próprio valor identifica o campo: a label flutuante\n // some (evita duplicar quando já há um rótulo externo acima do select).\n const selectedHasValue = multiple ? (selected as Array<string>).length > 0 : selected !== '';\n\n /**\n * Opção declarada com valor vazio (\"Todas\", \"Nenhum\", \"Sem recorte\"): é uma\n * escolha de verdade, não a ausência de escolha, então o campo precisa exibir\n * o rótulo dela em vez de ficar em branco.\n */\n const hasEmptyValueOption = useMemo(\n () => !multiple && options.some((option) => option.value === ''),\n [multiple, options],\n );\n\n // The dropdown is portaled, so a percentage width would resolve against the\n // page, and a runtime pixel value cannot become a Tailwind arbitrary class.\n // The measured width is written straight onto the node: the floating logic\n // overwrites the MenuList `style` prop with its own position, and React never\n // clears `width` because it is not part of that style object.\n useEffect(() => {\n const menuList = menuListRef.current;\n const trigger = triggerRef.current;\n if (!matchTriggerWidth || !isOpen || !menuList || !trigger) return undefined;\n\n const applyTriggerWidth = () => {\n menuList.style.width = `${trigger.getBoundingClientRect().width}px`;\n };\n\n applyTriggerWidth();\n\n const resizeObserver = new ResizeObserver(applyTriggerWidth);\n resizeObserver.observe(trigger);\n\n return () => resizeObserver.disconnect();\n }, [matchTriggerWidth, isOpen]);\n\n // Moving the pointer inside the menu makes floating-ui's list navigation pull\n // focus off the search field (it focuses the hovered option, or the menu\n // container on pointer leave). Take focus back whenever the pointer caused it,\n // while leaving keyboard navigation into the options alone.\n useEffect(() => {\n const menuList = menuListRef.current;\n if (!isOpen || !searchable || !menuList) return undefined;\n\n let isKeyboardInteraction = false;\n let restoreFrame = 0;\n\n const markKeyboardInteraction = () => {\n isKeyboardInteraction = true;\n };\n\n const markPointerInteraction = () => {\n isKeyboardInteraction = false;\n };\n\n const restoreSearchFocus = (event: FocusEvent) => {\n const searchInput = menuList.querySelector<HTMLInputElement>(`.${SEARCH_INPUT_MARKER_CLASS}`);\n if (!searchInput || event.target !== searchInput || isKeyboardInteraction) return;\n\n const nextFocused = event.relatedTarget as Node | null;\n if (nextFocused && !menuList.contains(nextFocused)) return;\n\n // The steal happens as this event is dispatched, so reclaim on the next frame.\n restoreFrame = window.requestAnimationFrame(() => searchInput.focus({ preventScroll: true }));\n };\n\n menuList.addEventListener('keydown', markKeyboardInteraction, true);\n menuList.addEventListener('pointermove', markPointerInteraction, true);\n menuList.addEventListener('focusout', restoreSearchFocus);\n\n return () => {\n window.cancelAnimationFrame(restoreFrame);\n menuList.removeEventListener('keydown', markKeyboardInteraction, true);\n menuList.removeEventListener('pointermove', markPointerInteraction, true);\n menuList.removeEventListener('focusout', restoreSearchFocus);\n };\n }, [isOpen, searchable]);\n\n const handleClearSearch = useCallback(() => {\n setCurrentSearchText('');\n\n if (infiniteScroll) {\n if (scrollableContainerRef.current) scrollableContainerRef.current.scrollTop = 0;\n void requestPagedOptions({ search: '', reset: true });\n } else {\n setFilteredOptions(options);\n }\n\n if (onClearSearch) {\n onClearSearch();\n }\n }, [infiniteScroll, requestPagedOptions, onClearSearch, options]);\n\n const getSelectedOptionsLabels = useCallback(() => {\n if (!multiple || !selected || (selected as Array<string>).length === 0) {\n return [];\n }\n\n const selectedArray = selected as Array<string>;\n\n return selectedArray.map((value) => {\n const trimmedValue = value.trim();\n const foundOption = options.find((option) => option.value === trimmedValue);\n return {\n value: trimmedValue,\n label: foundOption?.label || trimmedValue,\n swatch: foundOption?.swatch,\n };\n });\n }, [multiple, selected, options]);\n\n const isLateralPosition = selectedItemsPosition === 'left' || selectedItemsPosition === 'right';\n\n const inFieldChipItems = inFieldChips ? getSelectedOptionsLabels() : [];\n const showInFieldChips = inFieldChips && multiple && inFieldChipItems.length > 0;\n const visibleInFieldChips = inFieldChipItems.slice(0, MAX_IN_FIELD_CHIPS);\n const inFieldOverflow = inFieldChipItems.length - visibleInFieldChips.length;\n const inFieldChipsTooltip = inFieldChipItems.map((option) => formatLabel(option.label)).join(', ');\n\n const selectedItemsChips = showSelectedItems && multiple && getSelectedOptionsLabels().length > 1 && (\n <div className=\"flex flex-wrap gap-1\">\n {getSelectedOptionsLabels().map((option) => (\n <OutlinedChip\n key={option.value}\n label={option.label}\n preserveLabelCase={preserveLabelCase}\n dot={Boolean(option.swatch)}\n dotColor={option.swatch}\n className={classNames('bg-muted-active text-lg', { 'px-4 py-1': isLateralPosition })}\n labelClassName={isLateralPosition ? 'text-sm' : undefined}\n onClose={(event: React.MouseEvent) => handleSelect(option.value, event)}\n canClose\n />\n ))}\n </div>\n );\n\n const showClear =\n clearable &&\n !disabled &&\n Boolean(selected) &&\n (multiple ? (selected as Array<string>).length > 0 : selected !== '');\n\n const cursorClassName = disabled ? 'cursor-not-allowed' : 'cursor-pointer';\n\n return (\n <ControlledMenuRoot\n placement=\"bottom-start\"\n open={isOpen && !disabled}\n handler={(next) => {\n if (disabled) return;\n setIsOpen(next);\n if (next) onOpen?.();\n }}\n dismiss={{ itemPress: !multiple }}\n >\n <ControlledMenuHandler skipTriggerAria>\n <div\n className={classNames('flex', {\n 'flex-col gap-y-2': selectedItemsPosition === 'bottom' || selectedItemsPosition === 'top',\n 'flex-row items-start gap-x-2': selectedItemsPosition === 'left' || selectedItemsPosition === 'right',\n })}\n >\n {(selectedItemsPosition === 'top' || selectedItemsPosition === 'left') && selectedItemsChips}\n <div ref={triggerRef} className={classNames('relative', cursorClassName, className)}>\n {selectedSwatch && (\n <span\n className={classNames(\n 'pointer-events-none absolute left-3 top-1/2 z-10 size-3.5 -translate-y-1/2 border border-border',\n swatchShape === 'square' ? 'rounded' : 'rounded-full',\n )}\n style={{ backgroundColor: selectedSwatch }}\n />\n )}\n {showInFieldChips && (\n <ControlledTooltip content={inFieldChipsTooltip} className=\"max-w-72\">\n <div\n className={classNames(\n 'absolute inset-y-0 left-2.5 right-14 z-10 flex items-center gap-1 overflow-hidden',\n cursorClassName,\n )}\n >\n {visibleInFieldChips.map((option) => (\n <OutlinedChip\n key={option.value}\n label={option.label}\n preserveLabelCase={preserveLabelCase}\n dot={Boolean(option.swatch)}\n dotColor={option.swatch}\n className=\"min-w-0 shrink-0 bg-muted-active\"\n labelClassName=\"text-xxs\"\n />\n ))}\n {inFieldOverflow > 0 && (\n <OutlinedChip\n label={t('controlledSelect.moreItems', { count: inFieldOverflow })}\n className=\"min-w-0 shrink-0 bg-muted-active\"\n labelClassName=\"text-xxs\"\n />\n )}\n </div>\n </ControlledTooltip>\n )}\n <ControlledInput\n {...triggerAttributes}\n id={id}\n inputRef={triggerInputRef}\n onBlur={onBlur}\n onFocus={onFocus}\n autoFocus={autoFocus}\n readOnly\n role=\"combobox\"\n // eslint-disable-next-line i18n-check/no-literal-jsx-strings -- ARIA token, not user-facing text\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? scrollableContainerId : undefined}\n onKeyDown={handleTriggerKeyDown}\n className={classNames(\n 'controlled-select-input flex w-full min-w-0 justify-between truncate text-left',\n !compact && 'tablet:min-w-52',\n cursorClassName,\n selectedSwatch && '!pl-9',\n // O \"x\" é absoluto SOBRE o input: sem esta reserva o rótulo passa por baixo dele.\n showClear && '!pr-14',\n inputClassName,\n )}\n error={error}\n autoComplete=\"off\"\n rightSlot={<MaterialSymbol className={cursorClassName} icon=\"keyboard_arrow_down\" size={15} />}\n label={selectedHasValue && !persistentLabel ? undefined : label}\n aria-label={triggerAttributes['aria-label'] ?? ariaLabel ?? label}\n aria-required={ariaRequired || required ? true : triggerAttributes['aria-required']}\n value={showInFieldChips ? ' ' : selectedHasValue || hasEmptyValueOption ? getDisplayLabel() : ''}\n disabled={disabled}\n />\n\n {showClear && (\n <ControlledTooltip content={t('dates.clearSelection')}>\n <div\n onClick={handleClearSelection}\n className=\"absolute inset-y-0 right-8 top-0.5 flex cursor-pointer items-center\"\n >\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n\n {name &&\n (multiple\n ? (selected as Array<string>).map((value) => (\n <input key={value} type=\"hidden\" name={name} value={value} disabled={disabled} />\n ))\n : selected !== '' && <input type=\"hidden\" name={name} value={selected as string} disabled={disabled} />)}\n\n {(selectedItemsPosition === 'bottom' || selectedItemsPosition === 'right') && selectedItemsChips}\n </div>\n </ControlledMenuHandler>\n\n <ControlledMenuList\n ref={menuListRef}\n role=\"presentation\"\n onKeyDown={handleListKeyDown}\n className={classNames('zIndexMax w-80 max-w-[calc(100vw-1.5rem)] overflow-hidden', listClassName)}\n >\n {searchable && (\n <SelectSearchSlot>\n <UnifiedSearchBar\n mode=\"default\"\n label={t('actions.search')}\n debounceTime={SEARCH_DEBOUNCE_MS}\n // Em modo infinito quem filtra é o servidor: sem itens, a barra só reporta o\n // termo via onSearchTermChange e nunca escreve na lista local.\n searchableItems={infiniteScroll ? null : options}\n searchKeys={['label', 'secondaryLabel']}\n setFilteredItems={setFilteredOptions}\n onSearchTermChange={handleServerSearch}\n className={classNames('w-full', SEARCH_INPUT_MARKER_CLASS)}\n onClearFilter={handleClearSearch}\n initialValue={currentSearchText}\n />\n </SelectSearchSlot>\n )}\n\n <div\n ref={scrollableContainerRef}\n className={classNames({\n 'max-h-[250px] overflow-y-auto overscroll-contain': infiniteScroll,\n 'overflow-hidden': !infiniteScroll,\n })}\n id={scrollableContainerId}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n aria-labelledby={triggerAttributes['aria-labelledby']}\n aria-label={triggerAttributes['aria-labelledby'] ? undefined : listboxLabel}\n >\n {infiniteScroll ? (\n <InfiniteScroll\n dataLength={filteredOptions.length}\n // O loader do InfiniteScroll é renderizado sempre que `hasMore` é verdadeiro, ao lado\n // do conteúdo. Com a lista vazia isso empilhava o spinner sobre o estado vazio (ou\n // sobre o próprio spinner de `loading`): sem opções, quem manda é o bloco abaixo.\n hasMore={hasMoreOptions && filteredOptions.length > 0}\n next={handleLoadMoreFromScroll}\n loader={\n <div className=\"flex size-full items-center justify-center py-1\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n }\n scrollableTarget={scrollableContainerId}\n // `overflow-hidden` makes this a scroll port the user cannot scroll, so\n // `overscroll-none` here stopped the wheel from ever reaching the container\n // that does scroll: the list only moved by dragging its bar. Containment\n // belongs to that container, which already declares `overscroll-contain`.\n className=\"!overflow-hidden\"\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option, index) => renderOptionItem(option, index))\n ) : loading ? (\n <div className=\"flex w-full items-center justify-center py-4\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n ) : (\n <ControlledMenuItem disabled role=\"presentation\">\n <NoDataFound compact message={t('controlledSelect.noDataToShow')} />\n </ControlledMenuItem>\n )}\n </InfiniteScroll>\n ) : filteredOptions.length > 0 ? (\n <List\n ref={virtualListRef}\n className=\"virtualized-select-list\"\n height={listHeight}\n // eslint-disable-next-line i18n-check/no-literal-jsx-strings\n width=\"100%\"\n itemCount={filteredOptions.length}\n itemSize={ITEM_HEIGHT}\n overscanCount={OVERSCAN_COUNT}\n >\n {ItemRenderer}\n </List>\n ) : loading ? (\n <div className=\"flex w-full items-center justify-center py-4\">\n <LoadingSpinner size={4} label={t('controlledSelect.loadingOptions')} />\n </div>\n ) : (\n <ControlledMenuItem disabled role=\"presentation\">\n <NoDataFound compact message={t('controlledSelect.noDataToShow')} />\n </ControlledMenuItem>\n )}\n </div>\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n}\n"],"names":["label","List"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- interface IControlledSwitchProps {
1
+ type TSwitchNativeProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange' | 'value' | 'type' | 'role' | 'children' | 'color' | 'className' | 'disabled'>;
2
+ interface IControlledSwitchProps extends TSwitchNativeProps {
2
3
  checked: boolean;
3
4
  onChange: (checked: boolean) => void;
4
5
  label?: string;
@@ -9,6 +10,12 @@ interface IControlledSwitchProps {
9
10
  className?: string;
10
11
  labelClassName?: string;
11
12
  size?: 'sm' | 'md' | 'lg' | 'xl';
13
+ /** Submitted with `name` while checked, like a native checkbox. Default `on`. */
14
+ value?: string;
12
15
  }
13
- declare const ControlledSwitch: ({ checked, onChange, label, ariaLabel, color, disabled, className, labelClassName, size, }: IControlledSwitchProps) => import("react").JSX.Element;
16
+ /**
17
+ * A `button role="switch"`: Space and Enter toggle it natively, `id` makes it the target of an
18
+ * external `<label htmlFor>`, and the focus ring is painted on the visible track.
19
+ */
20
+ declare const ControlledSwitch: import('react').ForwardRefExoticComponent<IControlledSwitchProps & import('react').RefAttributes<HTMLButtonElement>>;
14
21
  export default ControlledSwitch;
@@ -1,87 +1,106 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
2
+ import { forwardRef, useState, useEffect } from "react";
3
3
  import classNames from "classnames";
4
- const ControlledSwitch = ({
5
- checked,
6
- onChange,
7
- label,
8
- ariaLabel,
9
- color = "purple",
10
- disabled = false,
11
- className = "",
12
- labelClassName = "",
13
- size = "md"
14
- }) => {
15
- const [colorClasses, setColorClasses] = useState("");
16
- const sizeConfig = {
17
- sm: {
18
- container: "w-8 h-4",
19
- thumb: "h-3 w-3"
20
- },
21
- md: {
22
- container: "w-10 h-6",
23
- thumb: "h-5 w-5"
24
- },
25
- lg: {
26
- container: "w-12 h-7",
27
- thumb: "h-6 w-6"
28
- },
29
- xl: {
30
- container: "w-14 h-8",
31
- thumb: "h-7 w-7"
32
- }
33
- };
34
- useEffect(() => {
35
- const colorMap = {
36
- blue: "bg-info",
37
- red: "bg-danger",
38
- green: "bg-success",
39
- amber: "bg-warning",
40
- purple: "bg-brand",
41
- indigo: "bg-brand",
42
- pink: "bg-accent",
43
- teal: "bg-secondary"
44
- };
45
- setColorClasses(colorMap[color] || "bg-brand");
46
- }, [color]);
47
- return /* @__PURE__ */ jsxs("div", { className: classNames("flex items-center gap-2", className), children: [
48
- /* @__PURE__ */ jsxs("label", { className: "relative inline-block", children: [
4
+ import ControlledButton from "./ControlledButton.js";
5
+ const SIZE_CONFIG = {
6
+ sm: {
7
+ container: "w-8 h-4",
8
+ thumb: "h-3 w-3"
9
+ },
10
+ md: {
11
+ container: "w-10 h-6",
12
+ thumb: "h-5 w-5"
13
+ },
14
+ lg: {
15
+ container: "w-12 h-7",
16
+ thumb: "h-6 w-6"
17
+ },
18
+ xl: {
19
+ container: "w-14 h-8",
20
+ thumb: "h-7 w-7"
21
+ }
22
+ };
23
+ const ControlledSwitch = forwardRef(
24
+ ({
25
+ checked,
26
+ onChange,
27
+ label,
28
+ ariaLabel,
29
+ color = "purple",
30
+ disabled = false,
31
+ className = "",
32
+ labelClassName = "",
33
+ size = "md",
34
+ name,
35
+ value = "on",
36
+ onClick,
37
+ "aria-label": ariaLabelAttribute,
38
+ ...rest
39
+ }, ref) => {
40
+ const [colorClasses, setColorClasses] = useState("");
41
+ useEffect(() => {
42
+ const colorMap = {
43
+ blue: "bg-info",
44
+ red: "bg-danger",
45
+ green: "bg-success",
46
+ amber: "bg-warning",
47
+ purple: "bg-brand",
48
+ indigo: "bg-brand",
49
+ pink: "bg-accent",
50
+ teal: "bg-secondary"
51
+ };
52
+ setColorClasses(colorMap[color] || "bg-brand");
53
+ }, [color]);
54
+ return /* @__PURE__ */ jsxs("div", { className: classNames("flex items-center gap-2", className), children: [
49
55
  /* @__PURE__ */ jsx(
50
- "input",
56
+ ControlledButton,
51
57
  {
52
- type: "checkbox",
53
- checked,
54
- onChange: () => onChange(!checked),
58
+ defaultHtmlButton: true,
59
+ ref,
60
+ role: "switch",
61
+ "aria-checked": checked,
62
+ "aria-label": ariaLabelAttribute ?? ariaLabel ?? label,
55
63
  disabled,
56
- "aria-label": ariaLabel ?? label,
57
- className: "peer sr-only"
58
- }
59
- ),
60
- /* @__PURE__ */ jsx(
61
- "div",
62
- {
64
+ ...rest,
65
+ onClick: (event) => {
66
+ onClick?.(event);
67
+ if (event.defaultPrevented) return;
68
+ onChange(!checked);
69
+ },
63
70
  className: classNames(
64
- sizeConfig[size].container,
65
- "relative rounded-full transition-colors duration-base ease-standard",
66
- checked ? colorClasses : "bg-border-emphasis",
67
- disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
71
+ "relative inline-block shrink-0 rounded-full outline-none",
72
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
73
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
68
74
  ),
69
75
  children: /* @__PURE__ */ jsx(
70
76
  "span",
71
77
  {
72
78
  className: classNames(
73
- "absolute top-1/2 inline-block -translate-y-1/2 rounded-full bg-surface shadow-md transition-interactive duration-base",
74
- sizeConfig[size].thumb,
75
- checked ? "right-0.5" : "left-0.5"
79
+ SIZE_CONFIG[size].container,
80
+ "relative block rounded-full transition-colors duration-base ease-standard",
81
+ checked ? colorClasses : "bg-border-emphasis",
82
+ disabled && "opacity-50"
83
+ ),
84
+ children: /* @__PURE__ */ jsx(
85
+ "span",
86
+ {
87
+ className: classNames(
88
+ "absolute top-1/2 inline-block -translate-y-1/2 rounded-full bg-surface shadow-md transition-interactive duration-base",
89
+ SIZE_CONFIG[size].thumb,
90
+ checked ? "right-0.5" : "left-0.5"
91
+ )
92
+ }
76
93
  )
77
94
  }
78
95
  )
79
96
  }
80
- )
81
- ] }),
82
- label && /* @__PURE__ */ jsx("span", { className: classNames("text-sm font-medium text-foreground", labelClassName), children: label })
83
- ] });
84
- };
97
+ ),
98
+ name && checked && /* @__PURE__ */ jsx("input", { type: "hidden", name, value, disabled }),
99
+ label && /* @__PURE__ */ jsx("span", { className: classNames("text-sm font-medium text-foreground", labelClassName), children: label })
100
+ ] });
101
+ }
102
+ );
103
+ ControlledSwitch.displayName = "ControlledSwitch";
85
104
  export {
86
105
  ControlledSwitch as default
87
106
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledSwitch.js","sources":["../../../src/components/Form/ControlledSwitch.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport classNames from 'classnames';\n\ninterface IControlledSwitchProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n /** Nome acessível quando o switch não tem `label` visível (ex.: linha com rótulo à esquerda). */\n ariaLabel?: string;\n color?: 'blue' | 'red' | 'green' | 'amber' | 'purple' | 'indigo' | 'pink' | 'teal';\n disabled?: boolean;\n className?: string;\n labelClassName?: string;\n size?: 'sm' | 'md' | 'lg' | 'xl';\n}\n\nconst ControlledSwitch = ({\n checked,\n onChange,\n label,\n ariaLabel,\n color = 'purple',\n disabled = false,\n className = '',\n labelClassName = '',\n size = 'md',\n}: IControlledSwitchProps) => {\n const [colorClasses, setColorClasses] = useState('');\n\n const sizeConfig = {\n sm: {\n container: 'w-8 h-4',\n thumb: 'h-3 w-3',\n },\n md: {\n container: 'w-10 h-6',\n thumb: 'h-5 w-5',\n },\n lg: {\n container: 'w-12 h-7',\n thumb: 'h-6 w-6',\n },\n xl: {\n container: 'w-14 h-8',\n thumb: 'h-7 w-7',\n },\n };\n\n useEffect(() => {\n const colorMap = {\n blue: 'bg-info',\n red: 'bg-danger',\n green: 'bg-success',\n amber: 'bg-warning',\n purple: 'bg-brand',\n indigo: 'bg-brand',\n pink: 'bg-accent',\n teal: 'bg-secondary',\n };\n\n setColorClasses(colorMap[color] || 'bg-brand');\n }, [color]);\n\n return (\n <div className={classNames('flex items-center gap-2', className)}>\n <label className=\"relative inline-block\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => onChange(!checked)}\n disabled={disabled}\n aria-label={ariaLabel ?? label}\n className=\"peer sr-only\"\n />\n <div\n className={classNames(\n sizeConfig[size].container,\n 'relative rounded-full transition-colors duration-base ease-standard',\n checked ? colorClasses : 'bg-border-emphasis',\n disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n )}\n >\n <span\n className={classNames(\n 'absolute top-1/2 inline-block -translate-y-1/2 rounded-full bg-surface shadow-md transition-interactive duration-base',\n sizeConfig[size].thumb,\n checked ? 'right-0.5' : 'left-0.5',\n )}\n ></span>\n </div>\n </label>\n {label && <span className={classNames('text-sm font-medium text-foreground', labelClassName)}>{label}</span>}\n </div>\n );\n};\n\nexport default ControlledSwitch;\n"],"names":[],"mappings":";;;AAgBA,MAAM,mBAAmB,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,OAAO;AACT,MAA8B;AAC5B,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,EAAE;AAEnD,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,MACF,WAAW;AAAA,MACX,OAAO;AAAA,IAAA;AAAA,IAET,IAAI;AAAA,MACF,WAAW;AAAA,MACX,OAAO;AAAA,IAAA;AAAA,IAET,IAAI;AAAA,MACF,WAAW;AAAA,MACX,OAAO;AAAA,IAAA;AAAA,IAET,IAAI;AAAA,MACF,WAAW;AAAA,MACX,OAAO;AAAA,IAAA;AAAA,EACT;AAGF,YAAU,MAAM;AACd,UAAM,WAAW;AAAA,MACf,MAAM;AAAA,MACN,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAGR,oBAAgB,SAAS,KAAK,KAAK,UAAU;AAAA,EAC/C,GAAG,CAAC,KAAK,CAAC;AAEV,8BACG,OAAA,EAAI,WAAW,WAAW,2BAA2B,SAAS,GAC7D,UAAA;AAAA,IAAA,qBAAC,SAAA,EAAM,WAAU,yBACf,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL;AAAA,UACA,UAAU,MAAM,SAAS,CAAC,OAAO;AAAA,UACjC;AAAA,UACA,cAAY,aAAa;AAAA,UACzB,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAEZ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT,WAAW,IAAI,EAAE;AAAA,YACjB;AAAA,YACA,UAAU,eAAe;AAAA,YACzB,WAAW,kCAAkC;AAAA,UAAA;AAAA,UAG/C,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,WAAW,IAAI,EAAE;AAAA,gBACjB,UAAU,cAAc;AAAA,cAAA;AAAA,YAC1B;AAAA,UAAA;AAAA,QACD;AAAA,MAAA;AAAA,IACH,GACF;AAAA,IACC,6BAAU,QAAA,EAAK,WAAW,WAAW,uCAAuC,cAAc,GAAI,UAAA,MAAA,CAAM;AAAA,EAAA,GACvG;AAEJ;"}
1
+ {"version":3,"file":"ControlledSwitch.js","sources":["../../../src/components/Form/ControlledSwitch.tsx"],"sourcesContent":["import { forwardRef, useEffect, useState } from 'react';\nimport classNames from 'classnames';\n\nimport ControlledButton from './ControlledButton';\n\ntype TSwitchNativeProps = Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n 'onChange' | 'value' | 'type' | 'role' | 'children' | 'color' | 'className' | 'disabled'\n>;\n\ninterface IControlledSwitchProps extends TSwitchNativeProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n /** Nome acessível quando o switch não tem `label` visível (ex.: linha com rótulo à esquerda). */\n ariaLabel?: string;\n color?: 'blue' | 'red' | 'green' | 'amber' | 'purple' | 'indigo' | 'pink' | 'teal';\n disabled?: boolean;\n className?: string;\n labelClassName?: string;\n size?: 'sm' | 'md' | 'lg' | 'xl';\n /** Submitted with `name` while checked, like a native checkbox. Default `on`. */\n value?: string;\n}\n\nconst SIZE_CONFIG = {\n sm: {\n container: 'w-8 h-4',\n thumb: 'h-3 w-3',\n },\n md: {\n container: 'w-10 h-6',\n thumb: 'h-5 w-5',\n },\n lg: {\n container: 'w-12 h-7',\n thumb: 'h-6 w-6',\n },\n xl: {\n container: 'w-14 h-8',\n thumb: 'h-7 w-7',\n },\n};\n\n/**\n * A `button role=\"switch\"`: Space and Enter toggle it natively, `id` makes it the target of an\n * external `<label htmlFor>`, and the focus ring is painted on the visible track.\n */\nconst ControlledSwitch = forwardRef<HTMLButtonElement, IControlledSwitchProps>(\n (\n {\n checked,\n onChange,\n label,\n ariaLabel,\n color = 'purple',\n disabled = false,\n className = '',\n labelClassName = '',\n size = 'md',\n name,\n value = 'on',\n onClick,\n 'aria-label': ariaLabelAttribute,\n ...rest\n },\n ref,\n ) => {\n const [colorClasses, setColorClasses] = useState('');\n\n useEffect(() => {\n const colorMap = {\n blue: 'bg-info',\n red: 'bg-danger',\n green: 'bg-success',\n amber: 'bg-warning',\n purple: 'bg-brand',\n indigo: 'bg-brand',\n pink: 'bg-accent',\n teal: 'bg-secondary',\n };\n\n setColorClasses(colorMap[color] || 'bg-brand');\n }, [color]);\n\n return (\n <div className={classNames('flex items-center gap-2', className)}>\n <ControlledButton\n defaultHtmlButton\n ref={ref}\n role=\"switch\"\n aria-checked={checked}\n aria-label={ariaLabelAttribute ?? ariaLabel ?? label}\n disabled={disabled}\n {...rest}\n onClick={(event) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n onChange(!checked);\n }}\n className={classNames(\n 'relative inline-block shrink-0 rounded-full outline-none',\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n )}\n >\n <span\n className={classNames(\n SIZE_CONFIG[size].container,\n 'relative block rounded-full transition-colors duration-base ease-standard',\n checked ? colorClasses : 'bg-border-emphasis',\n disabled && 'opacity-50',\n )}\n >\n <span\n className={classNames(\n 'absolute top-1/2 inline-block -translate-y-1/2 rounded-full bg-surface shadow-md transition-interactive duration-base',\n SIZE_CONFIG[size].thumb,\n checked ? 'right-0.5' : 'left-0.5',\n )}\n ></span>\n </span>\n </ControlledButton>\n {name && checked && <input type=\"hidden\" name={name} value={value} disabled={disabled} />}\n {label && <span className={classNames('text-sm font-medium text-foreground', labelClassName)}>{label}</span>}\n </div>\n );\n },\n);\n\nControlledSwitch.displayName = 'ControlledSwitch';\n\nexport default ControlledSwitch;\n"],"names":[],"mappings":";;;;AAyBA,MAAM,cAAc;AAAA,EAClB,IAAI;AAAA,IACF,WAAW;AAAA,IACX,OAAO;AAAA,EAAA;AAAA,EAET,IAAI;AAAA,IACF,WAAW;AAAA,IACX,OAAO;AAAA,EAAA;AAAA,EAET,IAAI;AAAA,IACF,WAAW;AAAA,IACX,OAAO;AAAA,EAAA;AAAA,EAET,IAAI;AAAA,IACF,WAAW;AAAA,IACX,OAAO;AAAA,EAAA;AAEX;AAMA,MAAM,mBAAmB;AAAA,EACvB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,cAAc;AAAA,IACd,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,CAAC,cAAc,eAAe,IAAI,SAAS,EAAE;AAEnD,cAAU,MAAM;AACd,YAAM,WAAW;AAAA,QACf,MAAM;AAAA,QACN,KAAK;AAAA,QACL,OAAO;AAAA,QACP,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAGR,sBAAgB,SAAS,KAAK,KAAK,UAAU;AAAA,IAC/C,GAAG,CAAC,KAAK,CAAC;AAEV,gCACG,OAAA,EAAI,WAAW,WAAW,2BAA2B,SAAS,GAC7D,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB;AAAA,UACA,MAAK;AAAA,UACL,gBAAc;AAAA,UACd,cAAY,sBAAsB,aAAa;AAAA,UAC/C;AAAA,UACC,GAAG;AAAA,UACJ,SAAS,CAAC,UAAU;AAClB,sBAAU,KAAK;AACf,gBAAI,MAAM,iBAAkB;AAC5B,qBAAS,CAAC,OAAO;AAAA,UACnB;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,WAAW,uBAAuB;AAAA,UAAA;AAAA,UAGpC,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT,YAAY,IAAI,EAAE;AAAA,gBAClB;AAAA,gBACA,UAAU,eAAe;AAAA,gBACzB,YAAY;AAAA,cAAA;AAAA,cAGd,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA,oBACA,YAAY,IAAI,EAAE;AAAA,oBAClB,UAAU,cAAc;AAAA,kBAAA;AAAA,gBAC1B;AAAA,cAAA;AAAA,YACD;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,MAED,QAAQ,WAAW,oBAAC,SAAA,EAAM,MAAK,UAAS,MAAY,OAAc,UAAoB;AAAA,MACtF,6BAAU,QAAA,EAAK,WAAW,WAAW,uCAAuC,cAAc,GAAI,UAAA,MAAA,CAAM;AAAA,IAAA,GACvG;AAAA,EAEJ;AACF;AAEA,iBAAiB,cAAc;"}
@@ -0,0 +1,26 @@
1
+ import { AnchorHTMLAttributes } from 'react';
2
+ import { TButtonSize, TButtonTone, TButtonVariant } from '../Form/ControlledButton';
3
+ export interface ILinkButtonProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
4
+ href: string;
5
+ variant?: TButtonVariant;
6
+ tone?: TButtonTone;
7
+ size?: TButtonSize;
8
+ fullWidth?: boolean;
9
+ /**
10
+ * Forces a plain `<a>` that leaves the app. Auto-detected for hrefs with a scheme
11
+ * (`https:`, `mailto:`, `tel:`) or protocol-relative (`//host`), and for `download` links.
12
+ */
13
+ external?: boolean;
14
+ /** Renders a non-navigable link (`aria-disabled`, out of the Tab order) with the disabled look. */
15
+ disabled?: boolean;
16
+ }
17
+ export declare const isExternalHref: (href: string) => boolean;
18
+ /**
19
+ * A real `<a>` with the `ControlledButton` look (same `variant` / `tone` / `size` / `fullWidth`).
20
+ * Internal hrefs go through the navigation port's `Link`, so the host router does client-side
21
+ * navigation and keeps modifier-click, middle-click and "open in new tab" working; external
22
+ * hrefs render a plain anchor, with `noopener noreferrer` added for `target="_blank"`.
23
+ * Never nest another interactive element inside it.
24
+ */
25
+ declare const LinkButton: import('react').ForwardRefExoticComponent<ILinkButtonProps & import('react').RefAttributes<HTMLAnchorElement>>;
26
+ export default LinkButton;
@@ -0,0 +1,82 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import classNames from "classnames";
4
+ import { getButtonClassName } from "../Form/ControlledButton.js";
5
+ import { useUiNavigation } from "./UiNavigationContext.js";
6
+ const SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i;
7
+ const isExternalHref = (href) => SCHEME_PATTERN.test(href) || href.startsWith("//");
8
+ const DISABLED_CLASS = "pointer-events-none opacity-50";
9
+ const mergeRel = (rel, target) => {
10
+ if (target !== "_blank") return rel;
11
+ const tokens = new Set((rel ?? "").split(/\s+/).filter(Boolean));
12
+ tokens.add("noopener");
13
+ tokens.add("noreferrer");
14
+ return Array.from(tokens).join(" ");
15
+ };
16
+ const LinkButton = forwardRef(
17
+ ({
18
+ href,
19
+ variant,
20
+ tone,
21
+ size,
22
+ fullWidth,
23
+ external,
24
+ disabled = false,
25
+ className,
26
+ target,
27
+ rel,
28
+ download,
29
+ onClick,
30
+ children,
31
+ ...rest
32
+ }, ref) => {
33
+ const { Link } = useUiNavigation();
34
+ const composedClassName = getButtonClassName({
35
+ variant,
36
+ tone,
37
+ size,
38
+ fullWidth,
39
+ className: classNames(className, disabled && DISABLED_CLASS)
40
+ });
41
+ if (disabled) {
42
+ return /* @__PURE__ */ jsx("a", { ref, role: "link", "aria-disabled": true, tabIndex: -1, ...rest, className: composedClassName, children });
43
+ }
44
+ const isExternal = external ?? (isExternalHref(href) || download !== void 0);
45
+ if (isExternal) {
46
+ return /* @__PURE__ */ jsx(
47
+ "a",
48
+ {
49
+ ref,
50
+ href,
51
+ target,
52
+ rel: mergeRel(rel, target),
53
+ download,
54
+ onClick,
55
+ ...rest,
56
+ className: composedClassName,
57
+ children
58
+ }
59
+ );
60
+ }
61
+ const PortLink = Link;
62
+ return /* @__PURE__ */ jsx(
63
+ PortLink,
64
+ {
65
+ ref,
66
+ to: href,
67
+ target,
68
+ rel: mergeRel(rel, target),
69
+ onClick,
70
+ ...rest,
71
+ className: composedClassName,
72
+ children
73
+ }
74
+ );
75
+ }
76
+ );
77
+ LinkButton.displayName = "LinkButton";
78
+ export {
79
+ LinkButton as default,
80
+ isExternalHref
81
+ };
82
+ //# sourceMappingURL=LinkButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinkButton.js","sources":["../../../src/components/Navigation/LinkButton.tsx"],"sourcesContent":["import { AnchorHTMLAttributes, forwardRef } from 'react';\nimport classNames from 'classnames';\n\nimport { getButtonClassName, TButtonSize, TButtonTone, TButtonVariant } from '@/components/Form/ControlledButton';\n\nimport { IUiLinkProps, useUiNavigation } from './UiNavigationContext';\n\nexport interface ILinkButtonProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n href: string;\n variant?: TButtonVariant;\n tone?: TButtonTone;\n size?: TButtonSize;\n fullWidth?: boolean;\n /**\n * Forces a plain `<a>` that leaves the app. Auto-detected for hrefs with a scheme\n * (`https:`, `mailto:`, `tel:`) or protocol-relative (`//host`), and for `download` links.\n */\n external?: boolean;\n /** Renders a non-navigable link (`aria-disabled`, out of the Tab order) with the disabled look. */\n disabled?: boolean;\n}\n\nconst SCHEME_PATTERN = /^[a-z][a-z\\d+.-]*:/i;\n\nexport const isExternalHref = (href: string): boolean => SCHEME_PATTERN.test(href) || href.startsWith('//');\n\nconst DISABLED_CLASS = 'pointer-events-none opacity-50';\n\nconst mergeRel = (rel: string | undefined, target: string | undefined): string | undefined => {\n if (target !== '_blank') return rel;\n const tokens = new Set((rel ?? '').split(/\\s+/).filter(Boolean));\n tokens.add('noopener');\n tokens.add('noreferrer');\n return Array.from(tokens).join(' ');\n};\n\n/* The host Link may or may not forward refs (react-router's and the fallback do). */\ntype TRefLink = React.ComponentType<IUiLinkProps & React.RefAttributes<HTMLAnchorElement>>;\n\n/**\n * A real `<a>` with the `ControlledButton` look (same `variant` / `tone` / `size` / `fullWidth`).\n * Internal hrefs go through the navigation port's `Link`, so the host router does client-side\n * navigation and keeps modifier-click, middle-click and \"open in new tab\" working; external\n * hrefs render a plain anchor, with `noopener noreferrer` added for `target=\"_blank\"`.\n * Never nest another interactive element inside it.\n */\nconst LinkButton = forwardRef<HTMLAnchorElement, ILinkButtonProps>(\n (\n {\n href,\n variant,\n tone,\n size,\n fullWidth,\n external,\n disabled = false,\n className,\n target,\n rel,\n download,\n onClick,\n children,\n ...rest\n },\n ref,\n ) => {\n const { Link } = useUiNavigation();\n const composedClassName = getButtonClassName({\n variant,\n tone,\n size,\n fullWidth,\n className: classNames(className, disabled && DISABLED_CLASS),\n });\n\n if (disabled) {\n return (\n <a ref={ref} role=\"link\" aria-disabled tabIndex={-1} {...rest} className={composedClassName}>\n {children}\n </a>\n );\n }\n\n const isExternal = external ?? (isExternalHref(href) || download !== undefined);\n\n if (isExternal) {\n return (\n <a\n ref={ref}\n href={href}\n target={target}\n rel={mergeRel(rel, target)}\n download={download}\n onClick={onClick}\n {...rest}\n className={composedClassName}\n >\n {children}\n </a>\n );\n }\n\n const PortLink = Link as TRefLink;\n\n return (\n <PortLink\n ref={ref}\n to={href}\n target={target}\n rel={mergeRel(rel, target)}\n onClick={onClick}\n {...rest}\n className={composedClassName}\n >\n {children}\n </PortLink>\n );\n },\n);\n\nLinkButton.displayName = 'LinkButton';\n\nexport default LinkButton;\n"],"names":[],"mappings":";;;;;AAsBA,MAAM,iBAAiB;AAEhB,MAAM,iBAAiB,CAAC,SAA0B,eAAe,KAAK,IAAI,KAAK,KAAK,WAAW,IAAI;AAE1G,MAAM,iBAAiB;AAEvB,MAAM,WAAW,CAAC,KAAyB,WAAmD;AAC5F,MAAI,WAAW,SAAU,QAAO;AAChC,QAAM,SAAS,IAAI,KAAK,OAAO,IAAI,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC;AAC/D,SAAO,IAAI,UAAU;AACrB,SAAO,IAAI,YAAY;AACvB,SAAO,MAAM,KAAK,MAAM,EAAE,KAAK,GAAG;AACpC;AAYA,MAAM,aAAa;AAAA,EACjB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,EAAE,KAAA,IAAS,gBAAA;AACjB,UAAM,oBAAoB,mBAAmB;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,WAAW,WAAW,YAAY,cAAc;AAAA,IAAA,CAC5D;AAED,QAAI,UAAU;AACZ,aACE,oBAAC,KAAA,EAAE,KAAU,MAAK,QAAO,iBAAa,MAAC,UAAU,IAAK,GAAG,MAAM,WAAW,mBACvE,UACH;AAAA,IAEJ;AAEA,UAAM,aAAa,aAAa,eAAe,IAAI,KAAK,aAAa;AAErE,QAAI,YAAY;AACd,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,SAAS,KAAK,MAAM;AAAA,UACzB;AAAA,UACA;AAAA,UACC,GAAG;AAAA,UACJ,WAAW;AAAA,UAEV;AAAA,QAAA;AAAA,MAAA;AAAA,IAGP;AAEA,UAAM,WAAW;AAEjB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,QACA,KAAK,SAAS,KAAK,MAAM;AAAA,QACzB;AAAA,QACC,GAAG;AAAA,QACJ,WAAW;AAAA,QAEV;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,WAAW,cAAc;"}