@umodeapporg/ui 0.27.0 → 0.29.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 (153) hide show
  1. package/dist/assets/umode-ui.css +0 -5
  2. package/dist/components/DateTime/DatePicker.d.ts +2 -0
  3. package/dist/components/DateTime/DatePicker.js +5 -2
  4. package/dist/components/DateTime/DatePicker.js.map +1 -1
  5. package/dist/components/DateTime/DateRangePicker.d.ts +2 -0
  6. package/dist/components/DateTime/DateRangePicker.js +3 -0
  7. package/dist/components/DateTime/DateRangePicker.js.map +1 -1
  8. package/dist/components/Form/ControlledButton.d.ts +1 -1
  9. package/dist/components/Form/ControlledButton.js +2 -2
  10. package/dist/components/Form/ControlledButton.js.map +1 -1
  11. package/dist/components/Form/ControlledCheckbox.d.ts +5 -1
  12. package/dist/components/Form/ControlledCheckbox.js +53 -39
  13. package/dist/components/Form/ControlledCheckbox.js.map +1 -1
  14. package/dist/components/Form/ControlledDialogPrimitives.js +17 -5
  15. package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
  16. package/dist/components/Form/ControlledDrawer.js +1 -0
  17. package/dist/components/Form/ControlledDrawer.js.map +1 -1
  18. package/dist/components/Form/ControlledImage/index.js +2 -0
  19. package/dist/components/Form/ControlledImage/index.js.map +1 -1
  20. package/dist/components/Form/ControlledInput.d.ts +1 -1
  21. package/dist/components/Form/ControlledInput.js +4 -4
  22. package/dist/components/Form/ControlledInput.js.map +1 -1
  23. package/dist/components/Form/ControlledSelect.js +1 -1
  24. package/dist/components/Form/ControlledSelect.js.map +1 -1
  25. package/dist/components/Form/ControlledTabs.js +17 -3
  26. package/dist/components/Form/ControlledTabs.js.map +1 -1
  27. package/dist/components/Form/ControlledTabsPrimitives.js +6 -6
  28. package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
  29. package/dist/components/Form/ControlledTextarea.js +2 -2
  30. package/dist/components/Form/ControlledTextarea.js.map +1 -1
  31. package/dist/components/Form/ControlledTimeline.js +11 -8
  32. package/dist/components/Form/ControlledTimeline.js.map +1 -1
  33. package/dist/components/Form/ControlledVoiceTextarea.d.ts +2 -0
  34. package/dist/components/Form/ControlledVoiceTextarea.js +3 -0
  35. package/dist/components/Form/ControlledVoiceTextarea.js.map +1 -1
  36. package/dist/components/Form/FieldLabel.d.ts +16 -0
  37. package/dist/components/Form/FieldLabel.js +7 -0
  38. package/dist/components/Form/FieldLabel.js.map +1 -0
  39. package/dist/components/Form/RichTextEditor.js +15 -1
  40. package/dist/components/Form/RichTextEditor.js.map +1 -1
  41. package/dist/components/Form/attachments/AttachmentPreviewList.js +3 -0
  42. package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
  43. package/dist/components/Form/attachments/FileDropzone.js +3 -3
  44. package/dist/components/Form/attachments/FileDropzone.js.map +1 -1
  45. package/dist/components/Navigation/PageTitle.d.ts +6 -0
  46. package/dist/components/Navigation/PageTitle.js +6 -4
  47. package/dist/components/Navigation/PageTitle.js.map +1 -1
  48. package/dist/components/Search/filters/FiltersPanel.js +1 -0
  49. package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
  50. package/dist/components/Search/filters/FiltersSummaryTooltip.js +2 -2
  51. package/dist/components/Search/filters/FiltersSummaryTooltip.js.map +1 -1
  52. package/dist/components/Shell/LanguageToggle.js +1 -1
  53. package/dist/components/Shell/LanguageToggle.js.map +1 -1
  54. package/dist/components/Shell/SidebarMenu.js +9 -1
  55. package/dist/components/Shell/SidebarMenu.js.map +1 -1
  56. package/dist/components/Shell/ThemeToggle.js +2 -1
  57. package/dist/components/Shell/ThemeToggle.js.map +1 -1
  58. package/dist/components/UI/BaseDataCard.d.ts +2 -0
  59. package/dist/components/UI/BaseDataCard.js +6 -0
  60. package/dist/components/UI/BaseDataCard.js.map +1 -1
  61. package/dist/components/UI/BulkActionBar.js +2 -0
  62. package/dist/components/UI/BulkActionBar.js.map +1 -1
  63. package/dist/components/UI/Chips/OutlinedChip.js +13 -6
  64. package/dist/components/UI/Chips/OutlinedChip.js.map +1 -1
  65. package/dist/components/UI/ColorSwatch.js +2 -2
  66. package/dist/components/UI/ColorSwatch.js.map +1 -1
  67. package/dist/components/UI/ControlledAccordion.js +10 -4
  68. package/dist/components/UI/ControlledAccordion.js.map +1 -1
  69. package/dist/components/UI/ControlledCollapse.js +2 -2
  70. package/dist/components/UI/ControlledCollapse.js.map +1 -1
  71. package/dist/components/UI/ControlledMenu.js +3 -3
  72. package/dist/components/UI/ControlledMenu.js.map +1 -1
  73. package/dist/components/UI/ControlledPopover.d.ts +12 -0
  74. package/dist/components/UI/ControlledPopover.js +35 -5
  75. package/dist/components/UI/ControlledPopover.js.map +1 -1
  76. package/dist/components/UI/ControlledProgress.js +3 -3
  77. package/dist/components/UI/ControlledProgress.js.map +1 -1
  78. package/dist/components/UI/ControlledSlider.d.ts +2 -0
  79. package/dist/components/UI/ControlledSlider.js +8 -3
  80. package/dist/components/UI/ControlledSlider.js.map +1 -1
  81. package/dist/components/UI/ControlledTooltip.js +7 -3
  82. package/dist/components/UI/ControlledTooltip.js.map +1 -1
  83. package/dist/components/UI/DataTable/DataTable.js +1 -1
  84. package/dist/components/UI/DataTable/DataTableBody.js +4 -0
  85. package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
  86. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +4 -2
  87. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
  88. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
  89. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +5 -1
  90. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +19 -4
  91. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -1
  92. package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
  93. package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
  94. package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
  95. package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
  96. package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
  97. package/dist/components/UI/DataTable/DataTableStates.js +2 -2
  98. package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
  99. package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
  100. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
  101. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +2 -1
  102. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
  103. package/dist/components/UI/DataTable/types.d.ts +4 -0
  104. package/dist/components/UI/DataTable/types.js.map +1 -1
  105. package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
  106. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
  107. package/dist/components/UI/Flow/FlowCanvas.js +2 -2
  108. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  109. package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
  110. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
  111. package/dist/components/UI/Flow/FlowInspector.js +3 -2
  112. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  113. package/dist/components/UI/Flow/FlowIssueList.js +2 -2
  114. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
  115. package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
  116. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
  117. package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
  118. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
  119. package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
  120. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
  121. package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
  122. package/dist/components/UI/HorizontalScrollArea.js +1 -0
  123. package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
  124. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
  125. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
  126. package/dist/components/UI/ImagePlaceholder.js +5 -2
  127. package/dist/components/UI/ImagePlaceholder.js.map +1 -1
  128. package/dist/components/UI/ImageZoomOverlay.js +1 -0
  129. package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
  130. package/dist/components/UI/MaterialSymbol/index.js +5 -0
  131. package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
  132. package/dist/components/UI/NoDataFound/index.js +3 -3
  133. package/dist/components/UI/NoDataFound/index.js.map +1 -1
  134. package/dist/components/UI/RichTextContent.js +3 -3
  135. package/dist/components/UI/RichTextContent.js.map +1 -1
  136. package/dist/components/UI/ThreeDots.js +15 -11
  137. package/dist/components/UI/ThreeDots.js.map +1 -1
  138. package/dist/helpers/mergeClasses.d.ts +7 -3
  139. package/dist/helpers/mergeClasses.js +23 -1
  140. package/dist/helpers/mergeClasses.js.map +1 -1
  141. package/dist/hooks/useVoiceTranscription.js +5 -2
  142. package/dist/hooks/useVoiceTranscription.js.map +1 -1
  143. package/dist/index.d.ts +2 -0
  144. package/dist/index.js +58 -54
  145. package/dist/index.js.map +1 -1
  146. package/dist/locales/en/ui.json.d.ts +8 -3
  147. package/dist/locales/en/ui.json.js +3 -3
  148. package/dist/locales/index.d.ts +10 -0
  149. package/dist/locales/pt/ui.json.d.ts +8 -3
  150. package/dist/locales/pt/ui.json.js +3 -3
  151. package/package.json +1 -1
  152. package/src/assets/styles/base.css +7 -1
  153. package/tailwind-plugins/glass.js +23 -28
@@ -1 +1 @@
1
- {"version":3,"file":"OutlinedChip.js","sources":["../../../../src/components/UI/Chips/OutlinedChip.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { IFilterOption } from '../../../interfaces/IFilterOption';\nimport { MaterialSymbol, SymbolCodepoints } from '../../UI/MaterialSymbol';\n\nimport { resolveChipDotClass, resolveChipToneClass, TChipSize, TChipTone, TChipVariant } from './chipTones';\n\nconst CHIP_PADDING_CLASS: Record<TChipSize, string> = {\n xs: 'px-2.5 py-0.5',\n sm: 'px-2.5 py-0.5',\n md: 'px-3 py-1',\n};\n\nconst CHIP_LABEL_SIZE_CLASS: Record<TChipSize, string> = {\n xs: 'text-xxs',\n sm: 'text-xs',\n md: 'text-sm',\n};\n\nexport interface IOutlinedChipDropdownOption extends IFilterOption {\n /** Cabeçalho de grupo não selecionável, renderizado acima dos filhos recuados. */\n isGroupHeading?: boolean;\n /** Recuo de filho sob o cabeçalho do grupo. */\n isGroupChild?: boolean;\n}\n\ninterface IOutlinedChipProps {\n label?: string;\n count?: number;\n tone?: TChipTone;\n variant?: TChipVariant;\n size?: TChipSize;\n dot?: boolean;\n dotColor?: string;\n className?: string;\n wrapperClassName?: string;\n labelClassName?: string;\n // Por padrão a label passa por capitalizeAllLetters. Use true quando o texto for um\n // identificador literal (nome de tabela, coluna, chave) cujo caixa não pode ser alterado.\n preserveLabelCase?: boolean;\n truncate?: boolean;\n backgroundColor?: string;\n chipIcon?: SymbolCodepoints;\n chipIconSize?: number;\n trailingIcon?: SymbolCodepoints;\n loading?: boolean;\n tooltip?: string;\n dotClassName?: string;\n onClick?: (event: React.MouseEvent) => void;\n disabled?: boolean;\n useAsDropdown?: boolean;\n dropdownOptions?: Array<IOutlinedChipDropdownOption>;\n dropdownCallback?: (option: IFilterOption) => void;\n dropdownSelectedValue?: string;\n dropdownMenuClassName?: string;\n canClose?: boolean;\n onClose?: (event: React.MouseEvent) => void;\n}\n\nconst OutlinedChip: React.FC<IOutlinedChipProps> = ({\n label,\n count,\n tone,\n variant = 'soft',\n size = 'xs',\n dot = false,\n dotColor,\n className,\n wrapperClassName,\n labelClassName,\n preserveLabelCase = false,\n truncate = false,\n backgroundColor,\n chipIcon,\n chipIconSize = 16,\n trailingIcon,\n loading = false,\n tooltip,\n dotClassName,\n onClick,\n disabled = false,\n useAsDropdown = false,\n dropdownOptions = [],\n dropdownCallback,\n dropdownSelectedValue,\n dropdownMenuClassName,\n canClose = false,\n onClose,\n}) => {\n const { t } = useTranslation('ui');\n const [open, setOpen] = useState(false);\n\n const hasCount = count !== undefined && count !== null;\n if (!label && !hasCount) return null;\n\n const isCountOnly = hasCount && !label;\n const displayLabel = isCountOnly ? String(count) : preserveLabelCase ? label : capitalizeAllLetters(label ?? '');\n\n const toneClass = tone ? resolveChipToneClass(tone, variant) : undefined;\n const resolvedDotClass = dotClassName ?? (dot && tone ? resolveChipDotClass(tone) : undefined);\n const inlineStyle = backgroundColor ? { backgroundColor } : undefined;\n\n const leadingDot =\n resolvedDotClass || dotColor ? (\n <span\n className={classNames('size-1.5 shrink-0 rounded-full', resolvedDotClass)}\n style={dotColor ? { backgroundColor: dotColor } : undefined}\n />\n ) : null;\n\n const leadingIcon = loading ? (\n <LoadingSpinner size={4} className=\"shrink-0\" />\n ) : chipIcon ? (\n <MaterialSymbol icon={chipIcon} size={chipIconSize} />\n ) : null;\n\n if (useAsDropdown && !disabled) {\n const handler = (\n <ControlledMenuHandler>\n <div\n onClick={(event: React.MouseEvent) => event.stopPropagation()}\n className={twMerge(\n classNames(\n 'flex cursor-pointer items-center justify-center gap-x-1 truncate rounded-full font-semibold transition-opacity hover:opacity-80',\n CHIP_PADDING_CLASS[size],\n CHIP_LABEL_SIZE_CLASS[size],\n toneClass,\n ),\n className,\n )}\n style={inlineStyle}\n >\n {leadingDot}\n {leadingIcon}\n {displayLabel}\n <MaterialSymbol icon=\"arrow_drop_down\" size={16} />\n </div>\n </ControlledMenuHandler>\n );\n\n return (\n <ControlledMenuRoot open={open} handler={setOpen} placement=\"bottom-start\">\n {tooltip ? <ControlledTooltip content={tooltip}>{handler}</ControlledTooltip> : handler}\n\n <ControlledMenuList\n className={twMerge('zIndexMax max-h-72 min-w-[12rem] overflow-y-auto', dropdownMenuClassName)}\n onClick={(event: React.MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n }}\n >\n {(dropdownOptions ?? []).map((option) => {\n if (option.isGroupHeading) {\n return (\n <div\n key={option.value}\n className=\"select-none px-3 pb-1 pt-2 text-xs font-semibold text-foreground-muted\"\n >\n {preserveLabelCase ? option.label : capitalizeAllLetters(option.label)}\n </div>\n );\n }\n\n const menuItem = (\n <ControlledMenuItem\n disabled={option.disabled}\n onClick={() => {\n dropdownCallback?.(option);\n setOpen(false);\n }}\n className={classNames('text-sm text-foreground', {\n 'bg-muted-active font-semibold hover:bg-muted-active focus:bg-muted-active':\n dropdownSelectedValue === option.value,\n 'pl-7': option.isGroupChild,\n })}\n >\n {preserveLabelCase ? option.label : capitalizeAllLetters(option.label)}\n </ControlledMenuItem>\n );\n\n // A tooltip explains WHY an option is disabled; the wrapper div exists because the\n // menu item does not forward its ref (the tooltip clones its child).\n return option.tooltip ? (\n <ControlledTooltip key={option.value} content={option.tooltip} className=\"z-[9999] max-w-64\">\n <div>{menuItem}</div>\n </ControlledTooltip>\n ) : (\n <React.Fragment key={option.value}>{menuItem}</React.Fragment>\n );\n })}\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n }\n\n const containerClass = twMerge(\n classNames(\n 'flex items-center gap-x-1 rounded-full text-center',\n CHIP_PADDING_CLASS[size],\n isCountOnly ? 'min-w-5 justify-center' : 'min-w-14',\n toneClass,\n {\n 'justify-between': canClose && onClose,\n 'justify-center': !canClose && !onClose && !isCountOnly,\n 'cursor-default opacity-70': disabled,\n 'transition-opacity hover:opacity-80': onClick && !disabled,\n },\n ),\n className,\n );\n\n const chipInner = (\n <>\n {leadingDot}\n {leadingIcon}\n <span className={twMerge(classNames('font-semibold', CHIP_LABEL_SIZE_CLASS[size], { truncate }), labelClassName)}>\n {displayLabel}\n </span>\n {trailingIcon && <MaterialSymbol icon={trailingIcon} size={chipIconSize} />}\n {canClose && onClose && (\n <ControlledTooltip content={t('actions.remove')}>\n <MaterialSymbol\n icon=\"close\"\n size={16}\n onClick={(event: React.MouseEvent) => {\n event.stopPropagation();\n onClose(event);\n }}\n />\n </ControlledTooltip>\n )}\n </>\n );\n\n const chip = onClick ? (\n <ControlledButton\n defaultHtmlButton\n className={containerClass}\n style={inlineStyle}\n onClick={onClick}\n disabled={disabled}\n >\n {chipInner}\n </ControlledButton>\n ) : (\n <div className={containerClass} style={inlineStyle}>\n {chipInner}\n </div>\n );\n\n if (!tooltip) return chip;\n\n return (\n <ControlledTooltip content={tooltip}>\n <div className={twMerge('inline-flex', wrapperClassName)}>{chip}</div>\n </ControlledTooltip>\n );\n};\n\nexport default OutlinedChip;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OutlinedChip.js","sources":["../../../../src/components/UI/Chips/OutlinedChip.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n ControlledMenuHandler,\n ControlledMenuItem,\n ControlledMenuList,\n ControlledMenuRoot,\n} from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { IFilterOption } from '../../../interfaces/IFilterOption';\nimport { MaterialSymbol, SymbolCodepoints } from '../../UI/MaterialSymbol';\n\nimport { resolveChipDotClass, resolveChipToneClass, TChipSize, TChipTone, TChipVariant } from './chipTones';\n\nconst CHIP_PADDING_CLASS: Record<TChipSize, string> = {\n xs: 'px-2.5 py-0.5',\n sm: 'px-2.5 py-0.5',\n md: 'px-3 py-1',\n};\n\nconst CHIP_LABEL_SIZE_CLASS: Record<TChipSize, string> = {\n xs: 'text-xxs',\n sm: 'text-xs',\n md: 'text-sm',\n};\n\nexport interface IOutlinedChipDropdownOption extends IFilterOption {\n /** Cabeçalho de grupo não selecionável, renderizado acima dos filhos recuados. */\n isGroupHeading?: boolean;\n /** Recuo de filho sob o cabeçalho do grupo. */\n isGroupChild?: boolean;\n}\n\ninterface IOutlinedChipProps {\n label?: string;\n count?: number;\n tone?: TChipTone;\n variant?: TChipVariant;\n size?: TChipSize;\n dot?: boolean;\n dotColor?: string;\n className?: string;\n wrapperClassName?: string;\n labelClassName?: string;\n // Por padrão a label passa por capitalizeAllLetters. Use true quando o texto for um\n // identificador literal (nome de tabela, coluna, chave) cujo caixa não pode ser alterado.\n preserveLabelCase?: boolean;\n truncate?: boolean;\n backgroundColor?: string;\n chipIcon?: SymbolCodepoints;\n chipIconSize?: number;\n trailingIcon?: SymbolCodepoints;\n loading?: boolean;\n tooltip?: string;\n dotClassName?: string;\n onClick?: (event: React.MouseEvent) => void;\n disabled?: boolean;\n useAsDropdown?: boolean;\n dropdownOptions?: Array<IOutlinedChipDropdownOption>;\n dropdownCallback?: (option: IFilterOption) => void;\n dropdownSelectedValue?: string;\n dropdownMenuClassName?: string;\n canClose?: boolean;\n onClose?: (event: React.MouseEvent) => void;\n}\n\nconst OutlinedChip: React.FC<IOutlinedChipProps> = ({\n label,\n count,\n tone,\n variant = 'soft',\n size = 'xs',\n dot = false,\n dotColor,\n className,\n wrapperClassName,\n labelClassName,\n preserveLabelCase = false,\n truncate = false,\n backgroundColor,\n chipIcon,\n chipIconSize = 16,\n trailingIcon,\n loading = false,\n tooltip,\n dotClassName,\n onClick,\n disabled = false,\n useAsDropdown = false,\n dropdownOptions = [],\n dropdownCallback,\n dropdownSelectedValue,\n dropdownMenuClassName,\n canClose = false,\n onClose,\n}) => {\n const { t } = useTranslation('ui');\n const [open, setOpen] = useState(false);\n\n const hasCount = count !== undefined && count !== null;\n if (!label && !hasCount) return null;\n\n const isCountOnly = hasCount && !label;\n const displayLabel = isCountOnly ? String(count) : preserveLabelCase ? label : capitalizeAllLetters(label ?? '');\n\n const toneClass = tone ? resolveChipToneClass(tone, variant) : undefined;\n const resolvedDotClass = dotClassName ?? (dot && tone ? resolveChipDotClass(tone) : undefined);\n const inlineStyle = backgroundColor ? { backgroundColor } : undefined;\n\n const leadingDot =\n resolvedDotClass || dotColor ? (\n <span\n className={classNames('size-1.5 shrink-0 rounded-full', resolvedDotClass)}\n style={dotColor ? { backgroundColor: dotColor } : undefined}\n />\n ) : null;\n\n const leadingIcon = loading ? (\n <LoadingSpinner size={4} className=\"shrink-0\" />\n ) : chipIcon ? (\n <MaterialSymbol icon={chipIcon} size={chipIconSize} />\n ) : null;\n\n if (useAsDropdown && !disabled) {\n const handler = (\n <ControlledMenuHandler>\n <div\n onClick={(event: React.MouseEvent) => event.stopPropagation()}\n className={mergeClasses(\n classNames(\n 'flex cursor-pointer items-center justify-center gap-x-1 truncate rounded-full font-semibold transition-opacity hover:opacity-80',\n CHIP_PADDING_CLASS[size],\n CHIP_LABEL_SIZE_CLASS[size],\n toneClass,\n ),\n className,\n )}\n style={inlineStyle}\n >\n {leadingDot}\n {leadingIcon}\n {displayLabel}\n <MaterialSymbol icon=\"arrow_drop_down\" size={16} />\n </div>\n </ControlledMenuHandler>\n );\n\n return (\n <ControlledMenuRoot open={open} handler={setOpen} placement=\"bottom-start\">\n {tooltip ? <ControlledTooltip content={tooltip}>{handler}</ControlledTooltip> : handler}\n\n <ControlledMenuList\n className={mergeClasses('zIndexMax max-h-72 min-w-[12rem] overflow-y-auto', dropdownMenuClassName)}\n onClick={(event: React.MouseEvent<HTMLDivElement>) => {\n event.stopPropagation();\n }}\n >\n {(dropdownOptions ?? []).map((option) => {\n if (option.isGroupHeading) {\n return (\n <div\n key={option.value}\n className=\"select-none px-3 pb-1 pt-2 text-xs font-semibold text-foreground-muted\"\n >\n {preserveLabelCase ? option.label : capitalizeAllLetters(option.label)}\n </div>\n );\n }\n\n const menuItem = (\n <ControlledMenuItem\n disabled={option.disabled}\n onClick={() => {\n dropdownCallback?.(option);\n setOpen(false);\n }}\n className={classNames('text-sm text-foreground', {\n 'bg-muted-active font-semibold hover:bg-muted-active focus:bg-muted-active':\n dropdownSelectedValue === option.value,\n 'pl-7': option.isGroupChild,\n })}\n >\n {preserveLabelCase ? option.label : capitalizeAllLetters(option.label)}\n </ControlledMenuItem>\n );\n\n // A tooltip explains WHY an option is disabled; the wrapper div exists because the\n // menu item does not forward its ref (the tooltip clones its child).\n return option.tooltip ? (\n <ControlledTooltip key={option.value} content={option.tooltip} className=\"z-[9999] max-w-64\">\n <div>{menuItem}</div>\n </ControlledTooltip>\n ) : (\n <React.Fragment key={option.value}>{menuItem}</React.Fragment>\n );\n })}\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n }\n\n const containerClass = mergeClasses(\n classNames(\n 'flex items-center gap-x-1 rounded-full text-center',\n CHIP_PADDING_CLASS[size],\n isCountOnly ? 'min-w-5 justify-center' : 'min-w-14',\n toneClass,\n {\n 'justify-between': canClose && onClose,\n 'justify-center': !canClose && !onClose && !isCountOnly,\n 'cursor-default opacity-70': disabled,\n 'transition-opacity hover:opacity-80': onClick && !disabled,\n },\n ),\n className,\n );\n\n const chipInner = (\n <>\n {leadingDot}\n {leadingIcon}\n <span\n className={mergeClasses(classNames('font-semibold', CHIP_LABEL_SIZE_CLASS[size], { truncate }), labelClassName)}\n >\n {displayLabel}\n </span>\n {trailingIcon && <MaterialSymbol icon={trailingIcon} size={chipIconSize} />}\n {canClose && onClose && (\n <ControlledTooltip content={t('actions.remove')}>\n <MaterialSymbol\n icon=\"close\"\n size={16}\n aria-label={t('actions.remove')}\n onClick={(event: React.MouseEvent) => {\n event.stopPropagation();\n onClose(event);\n }}\n />\n </ControlledTooltip>\n )}\n </>\n );\n\n const chip = onClick ? (\n <ControlledButton\n defaultHtmlButton\n className={containerClass}\n style={inlineStyle}\n onClick={onClick}\n disabled={disabled}\n >\n {chipInner}\n </ControlledButton>\n ) : (\n <div className={containerClass} style={inlineStyle}>\n {chipInner}\n </div>\n );\n\n if (!tooltip) return chip;\n\n return (\n <ControlledTooltip content={tooltip}>\n <div className={mergeClasses('inline-flex', wrapperClassName)}>{chip}</div>\n </ControlledTooltip>\n );\n};\n\nexport default OutlinedChip;\n"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import classNames from "classnames";
3
3
  import { useTranslation } from "react-i18next";
4
- import { twMerge } from "tailwind-merge";
5
4
  import ControlledTooltip from "./ControlledTooltip.js";
5
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
6
6
  const SIZE_CLASS = {
7
7
  xs: "size-2",
8
8
  sm: "size-3",
@@ -26,7 +26,7 @@ const ColorSwatch = ({
26
26
  const dot = /* @__PURE__ */ jsx(
27
27
  "span",
28
28
  {
29
- className: twMerge(
29
+ className: mergeClasses(
30
30
  classNames(
31
31
  "inline-block shrink-0 rounded-full border border-border",
32
32
  sizePx === void 0 && SIZE_CLASS[size],
@@ -1 +1 @@
1
- {"version":3,"file":"ColorSwatch.js","sources":["../../../src/components/UI/ColorSwatch.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\n\nexport type TColorSwatchSize = 'xs' | 'sm' | 'md' | 'lg';\n\nconst SIZE_CLASS: Record<TColorSwatchSize, string> = {\n xs: 'size-2',\n sm: 'size-3',\n md: 'size-4',\n lg: 'size-5',\n};\n\ninterface IColorSwatchProps {\n /** Cor CSS já resolvida. Ausente é estado válido: a biblioteca aceita cor sem hex e sem RGB. */\n cssColor?: string | null;\n name?: string | null;\n size?: TColorSwatchSize;\n /** Substitui o tamanho do preset quando a medida é calculada (zoom do canvas, por exemplo). */\n sizePx?: number;\n /** Mostra o nome ao lado da bolinha, além do tooltip. */\n showName?: boolean;\n /** Área de hover invisível sobre uma bolinha já desenhada em outra camada. */\n hitAreaOnly?: boolean;\n /**\n * Cada tooltip monta um floating-ui próprio. Numa lista longa (satélites do board, fileiras de\n * cores), desligue e deixe UM tooltip no container: o custo cai de N instâncias para uma.\n * Já vem desligado com `showName`, onde o nome está visível e o tooltip só repetiria.\n */\n tooltip?: boolean;\n className?: string;\n nameClassName?: string;\n}\n\n/**\n * A ÚNICA bolinha de cor da plataforma.\n *\n * Cor sem código cadastrado não some nem vira um buraco na interface: vira cinza opaco e o nome\n * carrega a identidade. Foi a divergência entre \"esconde\" e \"pinta de cinza\" que fez uma cor sem hex\n * parecer bug de renderização.\n */\nconst ColorSwatch: React.FC<IColorSwatchProps> = ({\n cssColor,\n name,\n size = 'sm',\n sizePx,\n showName = false,\n hitAreaOnly = false,\n tooltip,\n className,\n nameClassName,\n}) => {\n const { t } = useTranslation('ui');\n\n const resolvedName = name?.trim();\n const tooltipContent = [resolvedName, cssColor ? undefined : t('colorSwatch.noCode')].filter(Boolean).join(' · ');\n\n const dot = (\n <span\n className={twMerge(\n classNames(\n 'inline-block shrink-0 rounded-full border border-border',\n sizePx === undefined && SIZE_CLASS[size],\n hitAreaOnly && 'cursor-help border-transparent bg-transparent',\n ),\n className,\n )}\n style={{\n ...(sizePx === undefined ? undefined : { width: sizePx, height: sizePx }),\n ...(hitAreaOnly ? undefined : { backgroundColor: cssColor ?? 'rgb(var(--color-muted-active))' }),\n }}\n />\n );\n\n const content = showName ? (\n <span className=\"flex min-w-0 items-center gap-1\">\n {dot}\n <span className={classNames('truncate', nameClassName)}>{resolvedName}</span>\n </span>\n ) : (\n dot\n );\n\n if (!tooltipContent || !(tooltip ?? !showName)) return content;\n\n return <ControlledTooltip content={tooltipContent}>{content}</ControlledTooltip>;\n};\n\nexport default ColorSwatch;\n"],"names":[],"mappings":";;;;;AAQA,MAAM,aAA+C;AAAA,EACnD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AA8BA,MAAM,cAA2C,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,eAAe,MAAM,KAAA;AAC3B,QAAM,iBAAiB,CAAC,cAAc,WAAW,SAAY,EAAE,oBAAoB,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;AAEhH,QAAM,MACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA,WAAW,UAAa,WAAW,IAAI;AAAA,UACvC,eAAe;AAAA,QAAA;AAAA,QAEjB;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,WAAW,SAAY,SAAY,EAAE,OAAO,QAAQ,QAAQ,OAAA;AAAA,QAChE,GAAI,cAAc,SAAY,EAAE,iBAAiB,YAAY,iCAAA;AAAA,MAAiC;AAAA,IAChG;AAAA,EAAA;AAIJ,QAAM,UAAU,WACd,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,IAAA;AAAA,wBACA,QAAA,EAAK,WAAW,WAAW,YAAY,aAAa,GAAI,UAAA,aAAA,CAAa;AAAA,EAAA,EAAA,CACxE,IAEA;AAGF,MAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,UAAW,QAAO;AAEvD,SAAO,oBAAC,mBAAA,EAAkB,SAAS,gBAAiB,UAAA,SAAQ;AAC9D;"}
1
+ {"version":3,"file":"ColorSwatch.js","sources":["../../../src/components/UI/ColorSwatch.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nexport type TColorSwatchSize = 'xs' | 'sm' | 'md' | 'lg';\n\nconst SIZE_CLASS: Record<TColorSwatchSize, string> = {\n xs: 'size-2',\n sm: 'size-3',\n md: 'size-4',\n lg: 'size-5',\n};\n\ninterface IColorSwatchProps {\n /** Cor CSS já resolvida. Ausente é estado válido: a biblioteca aceita cor sem hex e sem RGB. */\n cssColor?: string | null;\n name?: string | null;\n size?: TColorSwatchSize;\n /** Substitui o tamanho do preset quando a medida é calculada (zoom do canvas, por exemplo). */\n sizePx?: number;\n /** Mostra o nome ao lado da bolinha, além do tooltip. */\n showName?: boolean;\n /** Área de hover invisível sobre uma bolinha já desenhada em outra camada. */\n hitAreaOnly?: boolean;\n /**\n * Cada tooltip monta um floating-ui próprio. Numa lista longa (satélites do board, fileiras de\n * cores), desligue e deixe UM tooltip no container: o custo cai de N instâncias para uma.\n * Já vem desligado com `showName`, onde o nome está visível e o tooltip só repetiria.\n */\n tooltip?: boolean;\n className?: string;\n nameClassName?: string;\n}\n\n/**\n * A ÚNICA bolinha de cor da plataforma.\n *\n * Cor sem código cadastrado não some nem vira um buraco na interface: vira cinza opaco e o nome\n * carrega a identidade. Foi a divergência entre \"esconde\" e \"pinta de cinza\" que fez uma cor sem hex\n * parecer bug de renderização.\n */\nconst ColorSwatch: React.FC<IColorSwatchProps> = ({\n cssColor,\n name,\n size = 'sm',\n sizePx,\n showName = false,\n hitAreaOnly = false,\n tooltip,\n className,\n nameClassName,\n}) => {\n const { t } = useTranslation('ui');\n\n const resolvedName = name?.trim();\n const tooltipContent = [resolvedName, cssColor ? undefined : t('colorSwatch.noCode')].filter(Boolean).join(' · ');\n\n const dot = (\n <span\n className={mergeClasses(\n classNames(\n 'inline-block shrink-0 rounded-full border border-border',\n sizePx === undefined && SIZE_CLASS[size],\n hitAreaOnly && 'cursor-help border-transparent bg-transparent',\n ),\n className,\n )}\n style={{\n ...(sizePx === undefined ? undefined : { width: sizePx, height: sizePx }),\n ...(hitAreaOnly ? undefined : { backgroundColor: cssColor ?? 'rgb(var(--color-muted-active))' }),\n }}\n />\n );\n\n const content = showName ? (\n <span className=\"flex min-w-0 items-center gap-1\">\n {dot}\n <span className={classNames('truncate', nameClassName)}>{resolvedName}</span>\n </span>\n ) : (\n dot\n );\n\n if (!tooltipContent || !(tooltip ?? !showName)) return content;\n\n return <ControlledTooltip content={tooltipContent}>{content}</ControlledTooltip>;\n};\n\nexport default ColorSwatch;\n"],"names":[],"mappings":";;;;;AAQA,MAAM,aAA+C;AAAA,EACnD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AA8BA,MAAM,cAA2C,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,eAAe,MAAM,KAAA;AAC3B,QAAM,iBAAiB,CAAC,cAAc,WAAW,SAAY,EAAE,oBAAoB,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;AAEhH,QAAM,MACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,UACE;AAAA,UACA,WAAW,UAAa,WAAW,IAAI;AAAA,UACvC,eAAe;AAAA,QAAA;AAAA,QAEjB;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,WAAW,SAAY,SAAY,EAAE,OAAO,QAAQ,QAAQ,OAAA;AAAA,QAChE,GAAI,cAAc,SAAY,EAAE,iBAAiB,YAAY,iCAAA;AAAA,MAAiC;AAAA,IAChG;AAAA,EAAA;AAIJ,QAAM,UAAU,WACd,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,IAAA;AAAA,wBACA,QAAA,EAAK,WAAW,WAAW,YAAY,aAAa,GAAI,UAAA,aAAA,CAAa;AAAA,EAAA,EAAA,CACxE,IAEA;AAGF,MAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,UAAW,QAAO;AAEvD,SAAO,oBAAC,mBAAA,EAAkB,SAAS,gBAAiB,UAAA,SAAQ;AAC9D;"}
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from "react";
3
3
  import classNames from "classnames";
4
- import { twMerge } from "tailwind-merge";
4
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
5
5
  import ControlledCollapse from "./ControlledCollapse.js";
6
6
  import { MaterialSymbol } from "./MaterialSymbol/index.js";
7
7
  const AccordionContext = createContext({ open: false });
@@ -14,7 +14,7 @@ const ControlledAccordionRoot = ({
14
14
  }) => /* @__PURE__ */ jsx(AccordionContext.Provider, { value: { open, icon, disabled }, children: /* @__PURE__ */ jsx(
15
15
  "div",
16
16
  {
17
- className: twMerge(
17
+ className: mergeClasses(
18
18
  classNames("relative block w-full", { "pointer-events-none opacity-50": disabled }),
19
19
  className
20
20
  ),
@@ -43,7 +43,7 @@ const ControlledAccordionHeader = ({
43
43
  "aria-expanded": isInteractive ? open : void 0,
44
44
  onClick,
45
45
  onKeyDown: handleKeyDown,
46
- className: twMerge(
46
+ className: mergeClasses(
47
47
  "flex w-full select-none items-center justify-between border-b border-border-subtle py-4 text-left",
48
48
  "text-xl font-semibold leading-snug text-foreground-muted transition-colors hover:text-foreground",
49
49
  classNames({ "text-foreground": open }),
@@ -65,7 +65,13 @@ const ControlledAccordionHeader = ({
65
65
  };
66
66
  const ControlledAccordionBody = ({ children, className }) => {
67
67
  const { open } = useContext(AccordionContext);
68
- return /* @__PURE__ */ jsx(ControlledCollapse, { open, children: /* @__PURE__ */ jsx("div", { className: twMerge("block w-full py-4 text-sm font-light leading-normal text-foreground-muted", className), children }) });
68
+ return /* @__PURE__ */ jsx(ControlledCollapse, { open, children: /* @__PURE__ */ jsx(
69
+ "div",
70
+ {
71
+ className: mergeClasses("block w-full py-4 text-sm font-light leading-normal text-foreground-muted", className),
72
+ children
73
+ }
74
+ ) });
69
75
  };
70
76
  const ControlledAccordion = ({
71
77
  open,
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledAccordion.js","sources":["../../../src/components/UI/ControlledAccordion.tsx"],"sourcesContent":["import React, { createContext, useContext } from 'react';\nimport classNames from 'classnames';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledCollapse from './ControlledCollapse';\nimport { MaterialSymbol } from './MaterialSymbol';\n\ninterface IAccordionContext {\n open: boolean;\n icon?: React.ReactNode;\n disabled?: boolean;\n}\n\nconst AccordionContext = createContext<IAccordionContext>({ open: false });\n\ninterface IControlledAccordionRootProps {\n open: boolean;\n children: React.ReactNode;\n icon?: React.ReactNode;\n disabled?: boolean;\n className?: string;\n}\n\nexport const ControlledAccordionRoot: React.FC<IControlledAccordionRootProps> = ({\n open,\n children,\n icon,\n disabled = false,\n className,\n}) => (\n <AccordionContext.Provider value={{ open, icon, disabled }}>\n <div\n className={twMerge(\n classNames('relative block w-full', { 'pointer-events-none opacity-50': disabled }),\n className,\n )}\n >\n {children}\n </div>\n </AccordionContext.Provider>\n);\n\ninterface IControlledAccordionHeaderProps {\n onClick?: () => void;\n children: React.ReactNode;\n className?: string;\n tabIndex?: number;\n}\n\n/**\n * Rendered as a div with role=\"button\", not a native button: several headers in this repo\n * carry their own buttons (rename, visibility, drag handle), and a button inside a button\n * is invalid HTML that silently breaks the inner controls.\n */\nexport const ControlledAccordionHeader: React.FC<IControlledAccordionHeaderProps> = ({\n onClick,\n children,\n className,\n tabIndex,\n}) => {\n const { open, icon } = useContext(AccordionContext);\n const isInteractive = Boolean(onClick);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (event.target !== event.currentTarget) return;\n event.preventDefault();\n onClick?.();\n };\n\n return (\n <div\n role={isInteractive ? 'button' : undefined}\n tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}\n aria-expanded={isInteractive ? open : undefined}\n onClick={onClick}\n onKeyDown={handleKeyDown}\n className={twMerge(\n 'flex w-full select-none items-center justify-between border-b border-border-subtle py-4 text-left',\n 'text-xl font-semibold leading-snug text-foreground-muted transition-colors hover:text-foreground',\n classNames({ 'text-foreground': open }),\n className,\n )}\n >\n {children}\n\n {icon ?? (\n <MaterialSymbol\n icon=\"expand_more\"\n size={20}\n className={classNames('ml-4 shrink-0 transition-transform duration-base', { 'rotate-180': open })}\n />\n )}\n </div>\n );\n};\n\ninterface IControlledAccordionBodyProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const ControlledAccordionBody: React.FC<IControlledAccordionBodyProps> = ({ children, className }) => {\n const { open } = useContext(AccordionContext);\n\n return (\n <ControlledCollapse open={open}>\n <div className={twMerge('block w-full py-4 text-sm font-light leading-normal text-foreground-muted', className)}>\n {children}\n </div>\n </ControlledCollapse>\n );\n};\n\ninterface IControlledAccordionProps {\n open: boolean;\n onToggle: () => void;\n header: React.ReactNode;\n children: React.ReactNode;\n icon?: React.ReactNode;\n className?: string;\n headerClassName?: string;\n bodyClassName?: string;\n}\n\nconst ControlledAccordion: React.FC<IControlledAccordionProps> = ({\n open,\n onToggle,\n header,\n children,\n icon,\n className,\n headerClassName,\n bodyClassName,\n}) => (\n <ControlledAccordionRoot open={open} icon={icon} className={className}>\n <ControlledAccordionHeader onClick={onToggle} className={headerClassName}>\n {header}\n </ControlledAccordionHeader>\n <ControlledAccordionBody className={bodyClassName}>{children}</ControlledAccordionBody>\n </ControlledAccordionRoot>\n);\n\nexport default ControlledAccordion;\n"],"names":[],"mappings":";;;;;;AAaA,MAAM,mBAAmB,cAAiC,EAAE,MAAM,OAAO;AAUlE,MAAM,0BAAmE,CAAC;AAAA,EAC/E;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,MACE,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,EAAE,MAAM,MAAM,YAC9C,UAAA;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAW;AAAA,MACT,WAAW,yBAAyB,EAAE,kCAAkC,UAAU;AAAA,MAClF;AAAA,IAAA;AAAA,IAGD;AAAA,EAAA;AACH,EAAA,CACF;AAeK,MAAM,4BAAuE,CAAC;AAAA,EACnF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,MAAM,SAAS,WAAW,gBAAgB;AAClD,QAAM,gBAAgB,QAAQ,OAAO;AAErC,QAAM,gBAAgB,CAAC,UAA+C;AACpE,QAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,IAAK;AAChD,QAAI,MAAM,WAAW,MAAM,cAAe;AAC1C,UAAM,eAAA;AACN,cAAA;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,gBAAgB,WAAW;AAAA,MACjC,UAAU,aAAa,gBAAgB,IAAI;AAAA,MAC3C,iBAAe,gBAAgB,OAAO;AAAA,MACtC;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW,EAAE,mBAAmB,MAAM;AAAA,QACtC;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA;AAAA,QAEA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAM;AAAA,YACN,WAAW,WAAW,oDAAoD,EAAE,cAAc,MAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAClG;AAAA,IAAA;AAAA,EAAA;AAIR;AAOO,MAAM,0BAAmE,CAAC,EAAE,UAAU,gBAAgB;AAC3G,QAAM,EAAE,KAAA,IAAS,WAAW,gBAAgB;AAE5C,SACE,oBAAC,oBAAA,EAAmB,MAClB,UAAA,oBAAC,OAAA,EAAI,WAAW,QAAQ,6EAA6E,SAAS,GAC3G,SAAA,CACH,EAAA,CACF;AAEJ;AAaA,MAAM,sBAA2D,CAAC;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MACE,qBAAC,yBAAA,EAAwB,MAAY,MAAY,WAC/C,UAAA;AAAA,EAAA,oBAAC,2BAAA,EAA0B,SAAS,UAAU,WAAW,iBACtD,UAAA,QACH;AAAA,EACA,oBAAC,yBAAA,EAAwB,WAAW,eAAgB,SAAA,CAAS;AAAA,EAAA,CAC/D;"}
1
+ {"version":3,"file":"ControlledAccordion.js","sources":["../../../src/components/UI/ControlledAccordion.tsx"],"sourcesContent":["import React, { createContext, useContext } from 'react';\nimport classNames from 'classnames';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport ControlledCollapse from './ControlledCollapse';\nimport { MaterialSymbol } from './MaterialSymbol';\n\ninterface IAccordionContext {\n open: boolean;\n icon?: React.ReactNode;\n disabled?: boolean;\n}\n\nconst AccordionContext = createContext<IAccordionContext>({ open: false });\n\ninterface IControlledAccordionRootProps {\n open: boolean;\n children: React.ReactNode;\n icon?: React.ReactNode;\n disabled?: boolean;\n className?: string;\n}\n\nexport const ControlledAccordionRoot: React.FC<IControlledAccordionRootProps> = ({\n open,\n children,\n icon,\n disabled = false,\n className,\n}) => (\n <AccordionContext.Provider value={{ open, icon, disabled }}>\n <div\n className={mergeClasses(\n classNames('relative block w-full', { 'pointer-events-none opacity-50': disabled }),\n className,\n )}\n >\n {children}\n </div>\n </AccordionContext.Provider>\n);\n\ninterface IControlledAccordionHeaderProps {\n onClick?: () => void;\n children: React.ReactNode;\n className?: string;\n tabIndex?: number;\n}\n\n/**\n * Rendered as a div with role=\"button\", not a native button: several headers in this repo\n * carry their own buttons (rename, visibility, drag handle), and a button inside a button\n * is invalid HTML that silently breaks the inner controls.\n */\nexport const ControlledAccordionHeader: React.FC<IControlledAccordionHeaderProps> = ({\n onClick,\n children,\n className,\n tabIndex,\n}) => {\n const { open, icon } = useContext(AccordionContext);\n const isInteractive = Boolean(onClick);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (event.target !== event.currentTarget) return;\n event.preventDefault();\n onClick?.();\n };\n\n return (\n <div\n role={isInteractive ? 'button' : undefined}\n tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}\n aria-expanded={isInteractive ? open : undefined}\n onClick={onClick}\n onKeyDown={handleKeyDown}\n className={mergeClasses(\n 'flex w-full select-none items-center justify-between border-b border-border-subtle py-4 text-left',\n 'text-xl font-semibold leading-snug text-foreground-muted transition-colors hover:text-foreground',\n classNames({ 'text-foreground': open }),\n className,\n )}\n >\n {children}\n\n {icon ?? (\n <MaterialSymbol\n icon=\"expand_more\"\n size={20}\n className={classNames('ml-4 shrink-0 transition-transform duration-base', { 'rotate-180': open })}\n />\n )}\n </div>\n );\n};\n\ninterface IControlledAccordionBodyProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const ControlledAccordionBody: React.FC<IControlledAccordionBodyProps> = ({ children, className }) => {\n const { open } = useContext(AccordionContext);\n\n return (\n <ControlledCollapse open={open}>\n <div\n className={mergeClasses('block w-full py-4 text-sm font-light leading-normal text-foreground-muted', className)}\n >\n {children}\n </div>\n </ControlledCollapse>\n );\n};\n\ninterface IControlledAccordionProps {\n open: boolean;\n onToggle: () => void;\n header: React.ReactNode;\n children: React.ReactNode;\n icon?: React.ReactNode;\n className?: string;\n headerClassName?: string;\n bodyClassName?: string;\n}\n\nconst ControlledAccordion: React.FC<IControlledAccordionProps> = ({\n open,\n onToggle,\n header,\n children,\n icon,\n className,\n headerClassName,\n bodyClassName,\n}) => (\n <ControlledAccordionRoot open={open} icon={icon} className={className}>\n <ControlledAccordionHeader onClick={onToggle} className={headerClassName}>\n {header}\n </ControlledAccordionHeader>\n <ControlledAccordionBody className={bodyClassName}>{children}</ControlledAccordionBody>\n </ControlledAccordionRoot>\n);\n\nexport default ControlledAccordion;\n"],"names":[],"mappings":";;;;;;AAcA,MAAM,mBAAmB,cAAiC,EAAE,MAAM,OAAO;AAUlE,MAAM,0BAAmE,CAAC;AAAA,EAC/E;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AACF,MACE,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,EAAE,MAAM,MAAM,YAC9C,UAAA;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAW;AAAA,MACT,WAAW,yBAAyB,EAAE,kCAAkC,UAAU;AAAA,MAClF;AAAA,IAAA;AAAA,IAGD;AAAA,EAAA;AACH,EAAA,CACF;AAeK,MAAM,4BAAuE,CAAC;AAAA,EACnF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,MAAM,SAAS,WAAW,gBAAgB;AAClD,QAAM,gBAAgB,QAAQ,OAAO;AAErC,QAAM,gBAAgB,CAAC,UAA+C;AACpE,QAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,IAAK;AAChD,QAAI,MAAM,WAAW,MAAM,cAAe;AAC1C,UAAM,eAAA;AACN,cAAA;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,gBAAgB,WAAW;AAAA,MACjC,UAAU,aAAa,gBAAgB,IAAI;AAAA,MAC3C,iBAAe,gBAAgB,OAAO;AAAA,MACtC;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW,EAAE,mBAAmB,MAAM;AAAA,QACtC;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA;AAAA,QAEA,QACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAM;AAAA,YACN,WAAW,WAAW,oDAAoD,EAAE,cAAc,MAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAClG;AAAA,IAAA;AAAA,EAAA;AAIR;AAOO,MAAM,0BAAmE,CAAC,EAAE,UAAU,gBAAgB;AAC3G,QAAM,EAAE,KAAA,IAAS,WAAW,gBAAgB;AAE5C,SACE,oBAAC,sBAAmB,MAClB,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,aAAa,6EAA6E,SAAS;AAAA,MAE7G;AAAA,IAAA;AAAA,EAAA,GAEL;AAEJ;AAaA,MAAM,sBAA2D,CAAC;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MACE,qBAAC,yBAAA,EAAwB,MAAY,MAAY,WAC/C,UAAA;AAAA,EAAA,oBAAC,2BAAA,EAA0B,SAAS,UAAU,WAAW,iBACtD,UAAA,QACH;AAAA,EACA,oBAAC,yBAAA,EAAwB,WAAW,eAAgB,SAAA,CAAS;AAAA,EAAA,CAC/D;"}
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useRef, useState, useEffect } from "react";
3
- import { twMerge } from "tailwind-merge";
3
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
4
4
  const ControlledCollapse = ({ open, children, className }) => {
5
5
  const wrapperRef = useRef(null);
6
6
  const contentRef = useRef(null);
@@ -29,7 +29,7 @@ const ControlledCollapse = ({ open, children, className }) => {
29
29
  ref: wrapperRef,
30
30
  style: { height },
31
31
  "aria-hidden": !open,
32
- className: twMerge("overflow-hidden transition-[height] duration-slow ease-enter", className),
32
+ className: mergeClasses("overflow-hidden transition-[height] duration-slow ease-enter", className),
33
33
  children: /* @__PURE__ */ jsx("div", { ref: contentRef, children })
34
34
  }
35
35
  );
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledCollapse.js","sources":["../../../src/components/UI/ControlledCollapse.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\ninterface IControlledCollapseProps {\n open: boolean;\n children: React.ReactNode;\n className?: string;\n}\n\n/**\n * Height is measured from the content node and written to the wrapper, so the transition\n * always has a numeric start and end. The observer watches the CONTENT, whose height is\n * unconstrained, so updating the wrapper cannot feed back into it.\n *\n * A closed collapse is `inert`: `height: 0` with `overflow: hidden` still leaves whatever is\n * inside reachable by Tab, so without it a keyboard lands on controls nobody can see, and\n * `aria-hidden` around focusable content is itself an ARIA violation. Set through the DOM\n * because React 18 does not type the attribute.\n */\nconst ControlledCollapse: React.FC<IControlledCollapseProps> = ({ open, children, className }) => {\n const wrapperRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const [height, setHeight] = useState(0);\n\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n if (open) wrapper.removeAttribute('inert');\n else wrapper.setAttribute('inert', '');\n }, [open]);\n\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return undefined;\n\n if (!open) {\n setHeight(0);\n return undefined;\n }\n\n setHeight(content.scrollHeight);\n\n const observer = new ResizeObserver(() => setHeight(content.scrollHeight));\n observer.observe(content);\n\n return () => observer.disconnect();\n }, [open]);\n\n return (\n <div\n ref={wrapperRef}\n style={{ height }}\n aria-hidden={!open}\n className={twMerge('overflow-hidden transition-[height] duration-slow ease-enter', className)}\n >\n <div ref={contentRef}>{children}</div>\n </div>\n );\n};\n\nexport default ControlledCollapse;\n"],"names":[],"mappings":";;;AAmBA,MAAM,qBAAyD,CAAC,EAAE,MAAM,UAAU,gBAAgB;AAChG,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,CAAC;AAEtC,YAAU,MAAM;AACd,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS;AACd,QAAI,KAAM,SAAQ,gBAAgB,OAAO;AAAA,QACpC,SAAQ,aAAa,SAAS,EAAE;AAAA,EACvC,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI,CAAC,MAAM;AACT,gBAAU,CAAC;AACX,aAAO;AAAA,IACT;AAEA,cAAU,QAAQ,YAAY;AAE9B,UAAM,WAAW,IAAI,eAAe,MAAM,UAAU,QAAQ,YAAY,CAAC;AACzE,aAAS,QAAQ,OAAO;AAExB,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,IAAI,CAAC;AAET,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,OAAO,EAAE,OAAA;AAAA,MACT,eAAa,CAAC;AAAA,MACd,WAAW,QAAQ,gEAAgE,SAAS;AAAA,MAE5F,UAAA,oBAAC,OAAA,EAAI,KAAK,YAAa,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAGtC;"}
1
+ {"version":3,"file":"ControlledCollapse.js","sources":["../../../src/components/UI/ControlledCollapse.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\ninterface IControlledCollapseProps {\n open: boolean;\n children: React.ReactNode;\n className?: string;\n}\n\n/**\n * Height is measured from the content node and written to the wrapper, so the transition\n * always has a numeric start and end. The observer watches the CONTENT, whose height is\n * unconstrained, so updating the wrapper cannot feed back into it.\n *\n * A closed collapse is `inert`: `height: 0` with `overflow: hidden` still leaves whatever is\n * inside reachable by Tab, so without it a keyboard lands on controls nobody can see, and\n * `aria-hidden` around focusable content is itself an ARIA violation. Set through the DOM\n * because React 18 does not type the attribute.\n */\nconst ControlledCollapse: React.FC<IControlledCollapseProps> = ({ open, children, className }) => {\n const wrapperRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const [height, setHeight] = useState(0);\n\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n if (open) wrapper.removeAttribute('inert');\n else wrapper.setAttribute('inert', '');\n }, [open]);\n\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return undefined;\n\n if (!open) {\n setHeight(0);\n return undefined;\n }\n\n setHeight(content.scrollHeight);\n\n const observer = new ResizeObserver(() => setHeight(content.scrollHeight));\n observer.observe(content);\n\n return () => observer.disconnect();\n }, [open]);\n\n return (\n <div\n ref={wrapperRef}\n style={{ height }}\n aria-hidden={!open}\n className={mergeClasses('overflow-hidden transition-[height] duration-slow ease-enter', className)}\n >\n <div ref={contentRef}>{children}</div>\n </div>\n );\n};\n\nexport default ControlledCollapse;\n"],"names":[],"mappings":";;;AAoBA,MAAM,qBAAyD,CAAC,EAAE,MAAM,UAAU,gBAAgB;AAChG,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,CAAC;AAEtC,YAAU,MAAM;AACd,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS;AACd,QAAI,KAAM,SAAQ,gBAAgB,OAAO;AAAA,QACpC,SAAQ,aAAa,SAAS,EAAE;AAAA,EACvC,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI,CAAC,MAAM;AACT,gBAAU,CAAC;AACX,aAAO;AAAA,IACT;AAEA,cAAU,QAAQ,YAAY;AAE9B,UAAM,WAAW,IAAI,eAAe,MAAM,UAAU,QAAQ,YAAY,CAAC;AACzE,aAAS,QAAQ,OAAO;AAExB,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,IAAI,CAAC;AAET,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,OAAO,EAAE,OAAA;AAAA,MACT,eAAa,CAAC;AAAA,MACd,WAAW,aAAa,gEAAgE,SAAS;AAAA,MAEjG,UAAA,oBAAC,OAAA,EAAI,KAAK,YAAa,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAGtC;"}
@@ -3,11 +3,11 @@ import React, { createContext, isValidElement, cloneElement, useEffect, useConte
3
3
  import classNames from "classnames";
4
4
  import { AnimatePresence, motion } from "motion/react";
5
5
  import { createPortal } from "react-dom";
6
- import { twMerge } from "tailwind-merge";
7
6
  import ControlledTooltip from "./ControlledTooltip.js";
8
7
  import { MaterialSymbol } from "./MaterialSymbol/index.js";
9
8
  import { composeRefs } from "../../helpers/composeRefs.js";
10
9
  import { LAYERED_SURFACE_ATTRIBUTE, isPressOnLayeredSurface } from "../../helpers/layeredSurfaces.js";
10
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
11
11
  import { getPortalHost } from "../../helpers/portalHost.js";
12
12
  import { useAnchoredFloating } from "../../hooks/useAnchoredFloating.js";
13
13
  import { floatingVariants } from "../../motion/motionPresets.js";
@@ -131,7 +131,7 @@ const ControlledMenuList = React.forwardRef(
131
131
  if (target?.closest('[role="menuitem"]')) setOpen(false);
132
132
  },
133
133
  ...rest,
134
- className: twMerge(
134
+ className: mergeClasses(
135
135
  "fixed left-0 top-0 z-[9999] min-w-44 overflow-auto rounded-lg border border-border p-1 text-sm text-foreground glass",
136
136
  className
137
137
  ),
@@ -164,7 +164,7 @@ const ControlledMenuItem = ({
164
164
  event.currentTarget.click();
165
165
  },
166
166
  ...rest,
167
- className: twMerge(
167
+ className: mergeClasses(
168
168
  classNames(
169
169
  "flex w-full cursor-pointer select-none items-center rounded-md px-3 py-2 text-sm outline-none",
170
170
  "transition-colors hover:bg-muted-hover focus:bg-muted-hover",
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledMenu.js","sources":["../../../src/components/UI/ControlledMenu.tsx"],"sourcesContent":["import React, {\n cloneElement,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n} from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { isPressOnLayeredSurface, LAYERED_SURFACE_ATTRIBUTE } from '@/helpers/layeredSurfaces';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { floatingVariants } from '@/motion';\n\nexport type TMenuPlacement = TFloatingPlacement;\nexport type TMenuItemTone = 'default' | 'danger';\n\nexport interface IMenuDismissConfig {\n /** Closes the menu when an item is pressed. Multi-select surfaces pass false. */\n itemPress?: boolean;\n escapeKey?: boolean;\n outsidePress?: boolean;\n}\n\nexport interface IControlledMenuItem {\n key: string;\n label: string;\n icon?: SymbolCodepoints;\n hint?: string;\n onClick?: () => void;\n disabled?: boolean;\n tone?: TMenuItemTone;\n}\n\ninterface IMenuContext {\n open: boolean;\n setOpen: (open: boolean) => void;\n dismiss?: IMenuDismissConfig;\n listId: string;\n setReference: (node: HTMLElement | null) => void;\n setFloating: (node: HTMLElement | null) => void;\n floatingRef: React.MutableRefObject<HTMLElement | null>;\n referenceRef: React.MutableRefObject<HTMLElement | null>;\n}\n\nconst MenuContext = createContext<IMenuContext | null>(null);\n\nconst useMenuContext = (): IMenuContext => {\n const context = useContext(MenuContext);\n if (!context) throw new Error('Menu parts must be rendered inside ControlledMenuRoot');\n return context;\n};\n\ninterface IControlledMenuRootProps {\n children: React.ReactNode;\n open?: boolean;\n handler?: (open: boolean) => void;\n placement?: TMenuPlacement;\n offsetPx?: number;\n dismiss?: IMenuDismissConfig;\n matchTriggerWidth?: boolean;\n}\n\nexport const ControlledMenuRoot: React.FC<IControlledMenuRootProps> = ({\n children,\n open,\n handler,\n placement = 'bottom-start',\n offsetPx = 6,\n dismiss,\n matchTriggerWidth = false,\n}) => {\n const listId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n\n const isControlled = open !== undefined;\n const isOpen = isControlled ? Boolean(open) : uncontrolledOpen;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next);\n handler?.(next);\n },\n [isControlled, handler],\n );\n\n const { setReference, setFloating, floatingRef, referenceRef } = useAnchoredFloating({\n open: isOpen,\n placement,\n offsetPx,\n matchReferenceWidth: matchTriggerWidth,\n });\n\n const value = useMemo(\n () => ({ open: isOpen, setOpen, dismiss, listId, setReference, setFloating, floatingRef, referenceRef }),\n [isOpen, setOpen, dismiss, listId, setReference, setFloating, floatingRef, referenceRef],\n );\n\n return <MenuContext.Provider value={value}>{children}</MenuContext.Provider>;\n};\n\ninterface IHandlerChildProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * Repassa ao filho o que ele recebe de fora, além do que injeta.\n *\n * O gatilho é o alvo natural de um `ControlledTooltip`, e o tooltip se prende clonando o filho e\n * injetando `onPointerEnter`. Aceitando só `children`, este componente engolia essa injeção e o\n * tooltip simplesmente nunca abria, mesmo com a ordem que a documentação manda usar\n * (tooltip POR FORA do handler). Os `onClick` são encadeados: o de fora roda antes de abrir.\n */\nexport const ControlledMenuHandler = React.forwardRef<\n HTMLElement,\n {\n children: React.ReactNode;\n /**\n * Leaves the popup ARIA to the child: set it when the real trigger is an element INSIDE the\n * child that owns its own role (the select's combobox input), so the wrapper does not\n * claim `aria-haspopup=\"menu\"` for a listbox.\n */\n skipTriggerAria?: boolean;\n } & React.HTMLAttributes<HTMLElement>\n>(({ children, onClick: onOuterClick, skipTriggerAria = false, ...rest }, forwardedRef) => {\n const { open, setOpen, setReference, listId } = useMenuContext();\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const child = children as React.ReactElement<IHandlerChildProps>;\n const triggerAria = skipTriggerAria\n ? {}\n : { 'aria-haspopup': 'menu', 'aria-expanded': open, 'aria-controls': open ? listId : undefined };\n\n return cloneElement(child, {\n ...rest,\n ref: composeRefs<HTMLElement>(setReference, forwardedRef, (child as { ref?: React.Ref<HTMLElement> }).ref),\n ...triggerAria,\n onClick: (event: React.MouseEvent<HTMLElement>) => {\n onOuterClick?.(event);\n child.props.onClick?.(event);\n setOpen(!open);\n },\n } as IHandlerChildProps);\n});\n\nControlledMenuHandler.displayName = 'ControlledMenuHandler';\n\ninterface IControlledMenuListProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'className' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'\n> {\n children: React.ReactNode;\n className?: string;\n}\n\nconst FOCUSABLE_ITEM_SELECTOR = '[role=\"menuitem\"]:not([aria-disabled=\"true\"])';\n\nexport const ControlledMenuList = React.forwardRef<HTMLDivElement, IControlledMenuListProps>(\n ({ children, className, onClick, onKeyDown, ...rest }, forwardedRef) => {\n const { open, setOpen, dismiss, listId, setFloating, floatingRef, referenceRef } = useMenuContext();\n\n useEffect(() => {\n if (!open) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || dismiss?.escapeKey === false) return;\n setOpen(false);\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n if (dismiss?.outsidePress === false) return;\n const target = event.target as Node | null;\n if (!target) return;\n if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;\n if (isPressOnLayeredSurface(target, floatingRef.current)) return;\n setOpen(false);\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('pointerdown', handlePointerDown, true);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('pointerdown', handlePointerDown, true);\n };\n }, [open, setOpen, dismiss, floatingRef, referenceRef]);\n\n const handleListKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;\n\n const list = floatingRef.current;\n if (!list) return;\n\n const items = Array.from(list.querySelectorAll<HTMLElement>(FOCUSABLE_ITEM_SELECTOR));\n if (items.length === 0) return;\n\n event.preventDefault();\n const currentIndex = items.indexOf(document.activeElement as HTMLElement);\n const step = event.key === 'ArrowDown' ? 1 : -1;\n const nextIndex = currentIndex < 0 ? 0 : (currentIndex + step + items.length) % items.length;\n items[nextIndex]?.focus();\n };\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence>\n {open && (\n <motion.div\n key=\"menu-list\"\n id={listId}\n variants={floatingVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"menu\"\n {...LAYERED_SURFACE_ATTRIBUTE}\n ref={(node) => {\n setFloating(node);\n if (typeof forwardedRef === 'function') forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n }}\n onKeyDown={handleListKeyDown}\n onClick={(event) => {\n onClick?.(event);\n if (dismiss?.itemPress === false) return;\n const target = event.target as HTMLElement | null;\n if (target?.closest('[role=\"menuitem\"]')) setOpen(false);\n }}\n {...rest}\n className={twMerge(\n 'fixed left-0 top-0 z-[9999] min-w-44 overflow-auto rounded-lg border border-border p-1 text-sm text-foreground glass',\n className,\n )}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n },\n);\n\nControlledMenuList.displayName = 'ControlledMenuList';\n\ninterface IControlledMenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'className'> {\n children: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}\n\nexport const ControlledMenuItem: React.FC<IControlledMenuItemProps> = ({\n children,\n className,\n disabled = false,\n onClick,\n ...rest\n}) => (\n <div\n role=\"menuitem\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : onClick}\n onKeyDown={(event) => {\n if (disabled) return;\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n (event.currentTarget as HTMLElement).click();\n }}\n {...rest}\n className={twMerge(\n classNames(\n 'flex w-full cursor-pointer select-none items-center rounded-md px-3 py-2 text-sm outline-none',\n 'transition-colors hover:bg-muted-hover focus:bg-muted-hover',\n { 'pointer-events-none opacity-50': disabled },\n ),\n className,\n )}\n >\n {children}\n </div>\n);\n\ninterface IControlledMenuProps {\n trigger: React.ReactNode;\n items?: Array<IControlledMenuItem>;\n children?: React.ReactNode;\n triggerTooltip?: string;\n placement?: TMenuPlacement;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n dismissOnItemClick?: boolean;\n listClassName?: string;\n tooltipClassName?: string;\n listRef?: React.Ref<HTMLDivElement>;\n}\n\nconst ITEM_TONE_CLASS: Record<TMenuItemTone, string> = {\n default: 'text-foreground-muted',\n danger: 'text-danger-emphasis',\n};\n\nconst ICON_TONE_CLASS: Record<TMenuItemTone, string> = {\n default: 'text-foreground-subtle',\n danger: 'text-danger-emphasis',\n};\n\n/**\n * A tooltip on the trigger must sit OUTSIDE the handler: the handler clones its child to\n * inject the click handler and the reference ref, and a tooltip in that position swallows\n * both, so the menu stops opening.\n */\nconst ControlledMenu: React.FC<IControlledMenuProps> = ({\n trigger,\n items,\n children,\n triggerTooltip,\n placement = 'bottom-start',\n open,\n onOpenChange,\n disabled = false,\n dismissOnItemClick = true,\n listClassName,\n tooltipClassName,\n listRef,\n}) => {\n const menuItems = useMemo(() => (Array.isArray(items) ? items : []), [items]);\n const dismiss = useMemo(() => ({ itemPress: dismissOnItemClick }), [dismissOnItemClick]);\n const handle = <ControlledMenuHandler>{trigger}</ControlledMenuHandler>;\n\n if (disabled) return <>{trigger}</>;\n\n return (\n <ControlledMenuRoot open={open} handler={onOpenChange} placement={placement} dismiss={dismiss}>\n {triggerTooltip ? (\n <ControlledTooltip content={triggerTooltip} className={tooltipClassName}>\n {handle}\n </ControlledTooltip>\n ) : (\n handle\n )}\n\n <ControlledMenuList ref={listRef} className={listClassName}>\n {children ??\n menuItems.map((item) => {\n const tone = item.tone ?? 'default';\n\n return (\n <ControlledMenuItem\n key={item.key}\n disabled={item.disabled}\n onClick={item.onClick}\n className={classNames('items-start gap-x-2', ITEM_TONE_CLASS[tone])}\n >\n {item.icon && <MaterialSymbol icon={item.icon} size={16} className={ICON_TONE_CLASS[tone]} />}\n\n <span className=\"flex flex-col items-start gap-y-0.5\">\n <span>{item.label}</span>\n {item.hint && <span className=\"text-xs text-foreground-subtle\">{item.hint}</span>}\n </span>\n </ControlledMenuItem>\n );\n })}\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n};\n\nexport default ControlledMenu;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ControlledMenu.js","sources":["../../../src/components/UI/ControlledMenu.tsx"],"sourcesContent":["import React, {\n cloneElement,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n} from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { isPressOnLayeredSurface, LAYERED_SURFACE_ATTRIBUTE } from '@/helpers/layeredSurfaces';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { floatingVariants } from '@/motion';\n\nexport type TMenuPlacement = TFloatingPlacement;\nexport type TMenuItemTone = 'default' | 'danger';\n\nexport interface IMenuDismissConfig {\n /** Closes the menu when an item is pressed. Multi-select surfaces pass false. */\n itemPress?: boolean;\n escapeKey?: boolean;\n outsidePress?: boolean;\n}\n\nexport interface IControlledMenuItem {\n key: string;\n label: string;\n icon?: SymbolCodepoints;\n hint?: string;\n onClick?: () => void;\n disabled?: boolean;\n tone?: TMenuItemTone;\n}\n\ninterface IMenuContext {\n open: boolean;\n setOpen: (open: boolean) => void;\n dismiss?: IMenuDismissConfig;\n listId: string;\n setReference: (node: HTMLElement | null) => void;\n setFloating: (node: HTMLElement | null) => void;\n floatingRef: React.MutableRefObject<HTMLElement | null>;\n referenceRef: React.MutableRefObject<HTMLElement | null>;\n}\n\nconst MenuContext = createContext<IMenuContext | null>(null);\n\nconst useMenuContext = (): IMenuContext => {\n const context = useContext(MenuContext);\n if (!context) throw new Error('Menu parts must be rendered inside ControlledMenuRoot');\n return context;\n};\n\ninterface IControlledMenuRootProps {\n children: React.ReactNode;\n open?: boolean;\n handler?: (open: boolean) => void;\n placement?: TMenuPlacement;\n offsetPx?: number;\n dismiss?: IMenuDismissConfig;\n matchTriggerWidth?: boolean;\n}\n\nexport const ControlledMenuRoot: React.FC<IControlledMenuRootProps> = ({\n children,\n open,\n handler,\n placement = 'bottom-start',\n offsetPx = 6,\n dismiss,\n matchTriggerWidth = false,\n}) => {\n const listId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n\n const isControlled = open !== undefined;\n const isOpen = isControlled ? Boolean(open) : uncontrolledOpen;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next);\n handler?.(next);\n },\n [isControlled, handler],\n );\n\n const { setReference, setFloating, floatingRef, referenceRef } = useAnchoredFloating({\n open: isOpen,\n placement,\n offsetPx,\n matchReferenceWidth: matchTriggerWidth,\n });\n\n const value = useMemo(\n () => ({ open: isOpen, setOpen, dismiss, listId, setReference, setFloating, floatingRef, referenceRef }),\n [isOpen, setOpen, dismiss, listId, setReference, setFloating, floatingRef, referenceRef],\n );\n\n return <MenuContext.Provider value={value}>{children}</MenuContext.Provider>;\n};\n\ninterface IHandlerChildProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * Repassa ao filho o que ele recebe de fora, além do que injeta.\n *\n * O gatilho é o alvo natural de um `ControlledTooltip`, e o tooltip se prende clonando o filho e\n * injetando `onPointerEnter`. Aceitando só `children`, este componente engolia essa injeção e o\n * tooltip simplesmente nunca abria, mesmo com a ordem que a documentação manda usar\n * (tooltip POR FORA do handler). Os `onClick` são encadeados: o de fora roda antes de abrir.\n */\nexport const ControlledMenuHandler = React.forwardRef<\n HTMLElement,\n {\n children: React.ReactNode;\n /**\n * Leaves the popup ARIA to the child: set it when the real trigger is an element INSIDE the\n * child that owns its own role (the select's combobox input), so the wrapper does not\n * claim `aria-haspopup=\"menu\"` for a listbox.\n */\n skipTriggerAria?: boolean;\n } & React.HTMLAttributes<HTMLElement>\n>(({ children, onClick: onOuterClick, skipTriggerAria = false, ...rest }, forwardedRef) => {\n const { open, setOpen, setReference, listId } = useMenuContext();\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const child = children as React.ReactElement<IHandlerChildProps>;\n const triggerAria = skipTriggerAria\n ? {}\n : { 'aria-haspopup': 'menu', 'aria-expanded': open, 'aria-controls': open ? listId : undefined };\n\n return cloneElement(child, {\n ...rest,\n ref: composeRefs<HTMLElement>(setReference, forwardedRef, (child as { ref?: React.Ref<HTMLElement> }).ref),\n ...triggerAria,\n onClick: (event: React.MouseEvent<HTMLElement>) => {\n onOuterClick?.(event);\n child.props.onClick?.(event);\n setOpen(!open);\n },\n } as IHandlerChildProps);\n});\n\nControlledMenuHandler.displayName = 'ControlledMenuHandler';\n\ninterface IControlledMenuListProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'className' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'\n> {\n children: React.ReactNode;\n className?: string;\n}\n\nconst FOCUSABLE_ITEM_SELECTOR = '[role=\"menuitem\"]:not([aria-disabled=\"true\"])';\n\nexport const ControlledMenuList = React.forwardRef<HTMLDivElement, IControlledMenuListProps>(\n ({ children, className, onClick, onKeyDown, ...rest }, forwardedRef) => {\n const { open, setOpen, dismiss, listId, setFloating, floatingRef, referenceRef } = useMenuContext();\n\n useEffect(() => {\n if (!open) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || dismiss?.escapeKey === false) return;\n setOpen(false);\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n if (dismiss?.outsidePress === false) return;\n const target = event.target as Node | null;\n if (!target) return;\n if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;\n if (isPressOnLayeredSurface(target, floatingRef.current)) return;\n setOpen(false);\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('pointerdown', handlePointerDown, true);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('pointerdown', handlePointerDown, true);\n };\n }, [open, setOpen, dismiss, floatingRef, referenceRef]);\n\n const handleListKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;\n\n const list = floatingRef.current;\n if (!list) return;\n\n const items = Array.from(list.querySelectorAll<HTMLElement>(FOCUSABLE_ITEM_SELECTOR));\n if (items.length === 0) return;\n\n event.preventDefault();\n const currentIndex = items.indexOf(document.activeElement as HTMLElement);\n const step = event.key === 'ArrowDown' ? 1 : -1;\n const nextIndex = currentIndex < 0 ? 0 : (currentIndex + step + items.length) % items.length;\n items[nextIndex]?.focus();\n };\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence>\n {open && (\n <motion.div\n key=\"menu-list\"\n id={listId}\n variants={floatingVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"menu\"\n {...LAYERED_SURFACE_ATTRIBUTE}\n ref={(node) => {\n setFloating(node);\n if (typeof forwardedRef === 'function') forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n }}\n onKeyDown={handleListKeyDown}\n onClick={(event) => {\n onClick?.(event);\n if (dismiss?.itemPress === false) return;\n const target = event.target as HTMLElement | null;\n if (target?.closest('[role=\"menuitem\"]')) setOpen(false);\n }}\n {...rest}\n className={mergeClasses(\n 'fixed left-0 top-0 z-[9999] min-w-44 overflow-auto rounded-lg border border-border p-1 text-sm text-foreground glass',\n className,\n )}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n },\n);\n\nControlledMenuList.displayName = 'ControlledMenuList';\n\ninterface IControlledMenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'className'> {\n children: React.ReactNode;\n className?: string;\n disabled?: boolean;\n}\n\nexport const ControlledMenuItem: React.FC<IControlledMenuItemProps> = ({\n children,\n className,\n disabled = false,\n onClick,\n ...rest\n}) => (\n <div\n role=\"menuitem\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : onClick}\n onKeyDown={(event) => {\n if (disabled) return;\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n (event.currentTarget as HTMLElement).click();\n }}\n {...rest}\n className={mergeClasses(\n classNames(\n 'flex w-full cursor-pointer select-none items-center rounded-md px-3 py-2 text-sm outline-none',\n 'transition-colors hover:bg-muted-hover focus:bg-muted-hover',\n { 'pointer-events-none opacity-50': disabled },\n ),\n className,\n )}\n >\n {children}\n </div>\n);\n\ninterface IControlledMenuProps {\n trigger: React.ReactNode;\n items?: Array<IControlledMenuItem>;\n children?: React.ReactNode;\n triggerTooltip?: string;\n placement?: TMenuPlacement;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n dismissOnItemClick?: boolean;\n listClassName?: string;\n tooltipClassName?: string;\n listRef?: React.Ref<HTMLDivElement>;\n}\n\nconst ITEM_TONE_CLASS: Record<TMenuItemTone, string> = {\n default: 'text-foreground-muted',\n danger: 'text-danger-emphasis',\n};\n\nconst ICON_TONE_CLASS: Record<TMenuItemTone, string> = {\n default: 'text-foreground-subtle',\n danger: 'text-danger-emphasis',\n};\n\n/**\n * A tooltip on the trigger must sit OUTSIDE the handler: the handler clones its child to\n * inject the click handler and the reference ref, and a tooltip in that position swallows\n * both, so the menu stops opening.\n */\nconst ControlledMenu: React.FC<IControlledMenuProps> = ({\n trigger,\n items,\n children,\n triggerTooltip,\n placement = 'bottom-start',\n open,\n onOpenChange,\n disabled = false,\n dismissOnItemClick = true,\n listClassName,\n tooltipClassName,\n listRef,\n}) => {\n const menuItems = useMemo(() => (Array.isArray(items) ? items : []), [items]);\n const dismiss = useMemo(() => ({ itemPress: dismissOnItemClick }), [dismissOnItemClick]);\n const handle = <ControlledMenuHandler>{trigger}</ControlledMenuHandler>;\n\n if (disabled) return <>{trigger}</>;\n\n return (\n <ControlledMenuRoot open={open} handler={onOpenChange} placement={placement} dismiss={dismiss}>\n {triggerTooltip ? (\n <ControlledTooltip content={triggerTooltip} className={tooltipClassName}>\n {handle}\n </ControlledTooltip>\n ) : (\n handle\n )}\n\n <ControlledMenuList ref={listRef} className={listClassName}>\n {children ??\n menuItems.map((item) => {\n const tone = item.tone ?? 'default';\n\n return (\n <ControlledMenuItem\n key={item.key}\n disabled={item.disabled}\n onClick={item.onClick}\n className={classNames('items-start gap-x-2', ITEM_TONE_CLASS[tone])}\n >\n {item.icon && <MaterialSymbol icon={item.icon} size={16} className={ICON_TONE_CLASS[tone]} />}\n\n <span className=\"flex flex-col items-start gap-y-0.5\">\n <span>{item.label}</span>\n {item.hint && <span className=\"text-xs text-foreground-subtle\">{item.hint}</span>}\n </span>\n </ControlledMenuItem>\n );\n })}\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n};\n\nexport default ControlledMenu;\n"],"names":[],"mappings":"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"}
@@ -25,6 +25,16 @@ export declare const ControlledPopoverHandler: React.ForwardRefExoticComponent<{
25
25
  interface IControlledPopoverContentProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'className' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'> {
26
26
  children: React.ReactNode;
27
27
  className?: string;
28
+ /**
29
+ * Keyboard support for a panel of controls, which portals to the end of the page and is otherwise
30
+ * only reached after everything else: opening moves the focus inside, closing from inside gives it
31
+ * back to the trigger, and focus going anywhere else closes the panel. Off by default because a
32
+ * popover that opens while someone works in a field (a date input, the colour of a text selection)
33
+ * must not take the focus away from it.
34
+ */
35
+ manageFocus?: boolean;
36
+ /** Where the keyboard lands with `manageFocus`; the first control by default. */
37
+ initialFocus?: (panel: HTMLElement) => HTMLElement | null;
28
38
  }
29
39
  export declare const ControlledPopoverContent: React.FC<IControlledPopoverContentProps>;
30
40
  interface IControlledPopoverProps {
@@ -37,6 +47,8 @@ interface IControlledPopoverProps {
37
47
  contentClassName?: string;
38
48
  tooltipClassName?: string;
39
49
  matchTriggerWidth?: boolean;
50
+ manageFocus?: boolean;
51
+ initialFocus?: (panel: HTMLElement) => HTMLElement | null;
40
52
  }
41
53
  /**
42
54
  * A tooltip on the trigger must sit OUTSIDE the handler: the handler clones its child to
@@ -1,12 +1,13 @@
1
1
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
- import React, { createContext, isValidElement, cloneElement, useContext, useId, useState, useEffect } from "react";
2
+ import React, { createContext, isValidElement, cloneElement, useContext, useId, useState, useRef, useEffect } from "react";
3
3
  import classNames from "classnames";
4
4
  import { AnimatePresence, motion } from "motion/react";
5
5
  import { createPortal } from "react-dom";
6
- import { twMerge } from "tailwind-merge";
6
+ import { getTabbableElements } from "../Form/ControlledDialogPrimitives.js";
7
7
  import ControlledTooltip from "./ControlledTooltip.js";
8
8
  import { composeRefs } from "../../helpers/composeRefs.js";
9
9
  import { isPressOnLayeredSurface } from "../../helpers/layeredSurfaces.js";
10
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
10
11
  import { getPortalHost } from "../../helpers/portalHost.js";
11
12
  import { useAnchoredFloating } from "../../hooks/useAnchoredFloating.js";
12
13
  import { floatingVariants } from "../../motion/motionPresets.js";
@@ -77,9 +78,36 @@ ControlledPopoverHandler.displayName = "ControlledPopoverHandler";
77
78
  const ControlledPopoverContent = ({
78
79
  children,
79
80
  className,
81
+ manageFocus = false,
82
+ initialFocus,
80
83
  ...rest
81
84
  }) => {
82
85
  const { open, setOpen, contentId, setFloating, floatingRef, referenceRef } = usePopoverContext();
86
+ const setOpenRef = useRef(setOpen);
87
+ setOpenRef.current = setOpen;
88
+ const initialFocusRef = useRef(initialFocus);
89
+ initialFocusRef.current = initialFocus;
90
+ useEffect(() => {
91
+ if (!open || !manageFocus) return void 0;
92
+ const frame = requestAnimationFrame(() => {
93
+ const panel = floatingRef.current;
94
+ if (!panel || panel.contains(document.activeElement)) return;
95
+ (initialFocusRef.current?.(panel) ?? getTabbableElements(panel)[0])?.focus();
96
+ });
97
+ const closeWhenFocusLeaves = (event) => {
98
+ const { target } = event;
99
+ if (!(target instanceof Node)) return;
100
+ if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;
101
+ if (isPressOnLayeredSurface(target, floatingRef.current)) return;
102
+ setOpenRef.current(false);
103
+ };
104
+ document.addEventListener("focusin", closeWhenFocusLeaves);
105
+ return () => {
106
+ cancelAnimationFrame(frame);
107
+ document.removeEventListener("focusin", closeWhenFocusLeaves);
108
+ if (floatingRef.current?.contains(document.activeElement)) referenceRef.current?.focus();
109
+ };
110
+ }, [open, manageFocus, floatingRef, referenceRef]);
83
111
  useEffect(() => {
84
112
  if (!open) return void 0;
85
113
  const handleKeyDown = (event) => {
@@ -113,7 +141,7 @@ const ControlledPopoverContent = ({
113
141
  exit: "exit",
114
142
  role: "dialog",
115
143
  ...rest,
116
- className: twMerge(
144
+ className: mergeClasses(
117
145
  "fixed left-0 top-0 z-[9999] overflow-auto rounded-lg border border-border p-2 text-sm text-foreground glass",
118
146
  className
119
147
  ),
@@ -133,7 +161,9 @@ const ControlledPopover = ({
133
161
  onOpenChange,
134
162
  contentClassName,
135
163
  tooltipClassName,
136
- matchTriggerWidth
164
+ matchTriggerWidth,
165
+ manageFocus,
166
+ initialFocus
137
167
  }) => {
138
168
  const handle = /* @__PURE__ */ jsx(ControlledPopoverHandler, { children: trigger });
139
169
  return /* @__PURE__ */ jsxs(
@@ -145,7 +175,7 @@ const ControlledPopover = ({
145
175
  matchTriggerWidth,
146
176
  children: [
147
177
  triggerTooltip ? /* @__PURE__ */ jsx(ControlledTooltip, { content: triggerTooltip, className: classNames(tooltipClassName), children: handle }) : handle,
148
- /* @__PURE__ */ jsx(ControlledPopoverContent, { className: contentClassName, children })
178
+ /* @__PURE__ */ jsx(ControlledPopoverContent, { className: contentClassName, manageFocus, initialFocus, children })
149
179
  ]
150
180
  }
151
181
  );
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledPopover.js","sources":["../../../src/components/UI/ControlledPopover.tsx"],"sourcesContent":["import React, { cloneElement, createContext, isValidElement, useContext, useEffect, useId, useState } from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { isPressOnLayeredSurface } from '@/helpers/layeredSurfaces';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { floatingVariants } from '@/motion';\n\nexport type TPopoverPlacement = TFloatingPlacement;\n\ninterface IPopoverContext {\n open: boolean;\n setOpen: (open: boolean) => void;\n placement: TFloatingPlacement;\n offsetPx: number;\n contentId: string;\n setReference: (node: HTMLElement | null) => void;\n setFloating: (node: HTMLElement | null) => void;\n floatingRef: React.MutableRefObject<HTMLElement | null>;\n referenceRef: React.MutableRefObject<HTMLElement | null>;\n}\n\nconst PopoverContext = createContext<IPopoverContext | null>(null);\n\nconst usePopoverContext = (): IPopoverContext => {\n const context = useContext(PopoverContext);\n if (!context) throw new Error('Popover parts must be rendered inside ControlledPopoverRoot');\n return context;\n};\n\ninterface IControlledPopoverRootProps {\n children: React.ReactNode;\n open?: boolean;\n handler?: (open: boolean) => void;\n placement?: TPopoverPlacement;\n offsetPx?: number;\n /** Sizes the content to the trigger width. Use it when the popover acts as a select dropdown. */\n matchTriggerWidth?: boolean;\n}\n\nexport const ControlledPopoverRoot: React.FC<IControlledPopoverRootProps> = ({\n children,\n open,\n handler,\n placement = 'bottom-start',\n offsetPx = 6,\n matchTriggerWidth = false,\n}) => {\n const contentId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n\n const isControlled = open !== undefined;\n const isOpen = isControlled ? Boolean(open) : uncontrolledOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next);\n handler?.(next);\n };\n\n const { setReference, setFloating, floatingRef, referenceRef } = useAnchoredFloating({\n open: isOpen,\n placement,\n offsetPx,\n matchReferenceWidth: matchTriggerWidth,\n });\n\n return (\n <PopoverContext.Provider\n value={{\n open: isOpen,\n setOpen,\n placement,\n offsetPx,\n contentId,\n setReference,\n setFloating,\n floatingRef,\n referenceRef,\n }}\n >\n {children}\n </PopoverContext.Provider>\n );\n};\n\ninterface IHandlerChildProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * Repassa ao filho o que ele recebe de fora, além do que injeta.\n *\n * O gatilho é o alvo natural de um `ControlledTooltip`, e o tooltip se prende clonando o filho e\n * injetando `onPointerEnter`. Aceitando só `children`, este componente engolia essa injeção e o\n * tooltip simplesmente nunca abria, mesmo com a ordem que a documentação manda usar\n * (tooltip POR FORA do handler). Os `onClick` são encadeados: o de fora roda antes de abrir.\n */\nexport const ControlledPopoverHandler = React.forwardRef<\n HTMLElement,\n { children: React.ReactNode } & React.HTMLAttributes<HTMLElement>\n>(({ children, onClick: onOuterClick, ...rest }, forwardedRef) => {\n const { open, setOpen, setReference, contentId } = usePopoverContext();\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const child = children as React.ReactElement<IHandlerChildProps>;\n\n return cloneElement(child, {\n ...rest,\n ref: composeRefs<HTMLElement>(setReference, forwardedRef, (child as { ref?: React.Ref<HTMLElement> }).ref),\n 'aria-expanded': open,\n 'aria-controls': open ? contentId : undefined,\n onClick: (event: React.MouseEvent<HTMLElement>) => {\n onOuterClick?.(event);\n child.props.onClick?.(event);\n setOpen(!open);\n },\n } as IHandlerChildProps);\n});\n\nControlledPopoverHandler.displayName = 'ControlledPopoverHandler';\n\ninterface IControlledPopoverContentProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'className' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'\n> {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const ControlledPopoverContent: React.FC<IControlledPopoverContentProps> = ({\n children,\n className,\n ...rest\n}) => {\n const { open, setOpen, contentId, setFloating, floatingRef, referenceRef } = usePopoverContext();\n\n useEffect(() => {\n if (!open) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setOpen(false);\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target as Node | null;\n if (!target) return;\n if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;\n if (isPressOnLayeredSurface(target, floatingRef.current)) return;\n setOpen(false);\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('pointerdown', handlePointerDown, true);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('pointerdown', handlePointerDown, true);\n };\n }, [open, setOpen, floatingRef, referenceRef]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence>\n {open && (\n <motion.div\n key=\"popover-content\"\n id={contentId}\n ref={setFloating}\n variants={floatingVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"dialog\"\n {...rest}\n className={twMerge(\n 'fixed left-0 top-0 z-[9999] overflow-auto rounded-lg border border-border p-2 text-sm text-foreground glass',\n className,\n )}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n};\n\ninterface IControlledPopoverProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n triggerTooltip?: string;\n placement?: TPopoverPlacement;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n contentClassName?: string;\n tooltipClassName?: string;\n matchTriggerWidth?: boolean;\n}\n\n/**\n * A tooltip on the trigger must sit OUTSIDE the handler: the handler clones its child to\n * inject the click handler and the reference ref, and a tooltip in that position swallows\n * both, so the popover stops opening.\n */\nconst ControlledPopover: React.FC<IControlledPopoverProps> = ({\n trigger,\n children,\n triggerTooltip,\n placement = 'bottom-start',\n open,\n onOpenChange,\n contentClassName,\n tooltipClassName,\n matchTriggerWidth,\n}) => {\n const handle = <ControlledPopoverHandler>{trigger}</ControlledPopoverHandler>;\n\n return (\n <ControlledPopoverRoot\n open={open}\n handler={onOpenChange}\n placement={placement}\n matchTriggerWidth={matchTriggerWidth}\n >\n {triggerTooltip ? (\n <ControlledTooltip content={triggerTooltip} className={classNames(tooltipClassName)}>\n {handle}\n </ControlledTooltip>\n ) : (\n handle\n )}\n\n <ControlledPopoverContent className={contentClassName}>{children}</ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default ControlledPopover;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ControlledPopover.js","sources":["../../../src/components/UI/ControlledPopover.tsx"],"sourcesContent":["import React, {\n cloneElement,\n createContext,\n isValidElement,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\n\nimport { getTabbableElements } from '@/components/Form/ControlledDialogPrimitives';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { isPressOnLayeredSurface } from '@/helpers/layeredSurfaces';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { floatingVariants } from '@/motion';\n\nexport type TPopoverPlacement = TFloatingPlacement;\n\ninterface IPopoverContext {\n open: boolean;\n setOpen: (open: boolean) => void;\n placement: TFloatingPlacement;\n offsetPx: number;\n contentId: string;\n setReference: (node: HTMLElement | null) => void;\n setFloating: (node: HTMLElement | null) => void;\n floatingRef: React.MutableRefObject<HTMLElement | null>;\n referenceRef: React.MutableRefObject<HTMLElement | null>;\n}\n\nconst PopoverContext = createContext<IPopoverContext | null>(null);\n\nconst usePopoverContext = (): IPopoverContext => {\n const context = useContext(PopoverContext);\n if (!context) throw new Error('Popover parts must be rendered inside ControlledPopoverRoot');\n return context;\n};\n\ninterface IControlledPopoverRootProps {\n children: React.ReactNode;\n open?: boolean;\n handler?: (open: boolean) => void;\n placement?: TPopoverPlacement;\n offsetPx?: number;\n /** Sizes the content to the trigger width. Use it when the popover acts as a select dropdown. */\n matchTriggerWidth?: boolean;\n}\n\nexport const ControlledPopoverRoot: React.FC<IControlledPopoverRootProps> = ({\n children,\n open,\n handler,\n placement = 'bottom-start',\n offsetPx = 6,\n matchTriggerWidth = false,\n}) => {\n const contentId = useId();\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n\n const isControlled = open !== undefined;\n const isOpen = isControlled ? Boolean(open) : uncontrolledOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next);\n handler?.(next);\n };\n\n const { setReference, setFloating, floatingRef, referenceRef } = useAnchoredFloating({\n open: isOpen,\n placement,\n offsetPx,\n matchReferenceWidth: matchTriggerWidth,\n });\n\n return (\n <PopoverContext.Provider\n value={{\n open: isOpen,\n setOpen,\n placement,\n offsetPx,\n contentId,\n setReference,\n setFloating,\n floatingRef,\n referenceRef,\n }}\n >\n {children}\n </PopoverContext.Provider>\n );\n};\n\ninterface IHandlerChildProps {\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * Repassa ao filho o que ele recebe de fora, além do que injeta.\n *\n * O gatilho é o alvo natural de um `ControlledTooltip`, e o tooltip se prende clonando o filho e\n * injetando `onPointerEnter`. Aceitando só `children`, este componente engolia essa injeção e o\n * tooltip simplesmente nunca abria, mesmo com a ordem que a documentação manda usar\n * (tooltip POR FORA do handler). Os `onClick` são encadeados: o de fora roda antes de abrir.\n */\nexport const ControlledPopoverHandler = React.forwardRef<\n HTMLElement,\n { children: React.ReactNode } & React.HTMLAttributes<HTMLElement>\n>(({ children, onClick: onOuterClick, ...rest }, forwardedRef) => {\n const { open, setOpen, setReference, contentId } = usePopoverContext();\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const child = children as React.ReactElement<IHandlerChildProps>;\n\n return cloneElement(child, {\n ...rest,\n ref: composeRefs<HTMLElement>(setReference, forwardedRef, (child as { ref?: React.Ref<HTMLElement> }).ref),\n 'aria-expanded': open,\n 'aria-controls': open ? contentId : undefined,\n onClick: (event: React.MouseEvent<HTMLElement>) => {\n onOuterClick?.(event);\n child.props.onClick?.(event);\n setOpen(!open);\n },\n } as IHandlerChildProps);\n});\n\nControlledPopoverHandler.displayName = 'ControlledPopoverHandler';\n\ninterface IControlledPopoverContentProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'className' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'\n> {\n children: React.ReactNode;\n className?: string;\n /**\n * Keyboard support for a panel of controls, which portals to the end of the page and is otherwise\n * only reached after everything else: opening moves the focus inside, closing from inside gives it\n * back to the trigger, and focus going anywhere else closes the panel. Off by default because a\n * popover that opens while someone works in a field (a date input, the colour of a text selection)\n * must not take the focus away from it.\n */\n manageFocus?: boolean;\n /** Where the keyboard lands with `manageFocus`; the first control by default. */\n initialFocus?: (panel: HTMLElement) => HTMLElement | null;\n}\n\nexport const ControlledPopoverContent: React.FC<IControlledPopoverContentProps> = ({\n children,\n className,\n manageFocus = false,\n initialFocus,\n ...rest\n}) => {\n const { open, setOpen, contentId, setFloating, floatingRef, referenceRef } = usePopoverContext();\n\n // Both change on every render of the root, and leaving the effect below re-runs would hand the\n // focus back to the trigger each time.\n const setOpenRef = useRef(setOpen);\n setOpenRef.current = setOpen;\n const initialFocusRef = useRef(initialFocus);\n initialFocusRef.current = initialFocus;\n\n useEffect(() => {\n if (!open || !manageFocus) return undefined;\n\n // The panel mounts with this update: wait for the frame that paints it.\n const frame = requestAnimationFrame(() => {\n const panel = floatingRef.current;\n if (!panel || panel.contains(document.activeElement)) return;\n (initialFocusRef.current?.(panel) ?? getTabbableElements(panel)[0])?.focus();\n });\n\n const closeWhenFocusLeaves = (event: FocusEvent) => {\n const { target } = event;\n if (!(target instanceof Node)) return;\n if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;\n if (isPressOnLayeredSurface(target, floatingRef.current)) return;\n setOpenRef.current(false);\n };\n\n document.addEventListener('focusin', closeWhenFocusLeaves);\n\n return () => {\n cancelAnimationFrame(frame);\n document.removeEventListener('focusin', closeWhenFocusLeaves);\n if (floatingRef.current?.contains(document.activeElement)) referenceRef.current?.focus();\n };\n }, [open, manageFocus, floatingRef, referenceRef]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setOpen(false);\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target as Node | null;\n if (!target) return;\n if (floatingRef.current?.contains(target) || referenceRef.current?.contains(target)) return;\n if (isPressOnLayeredSurface(target, floatingRef.current)) return;\n setOpen(false);\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('pointerdown', handlePointerDown, true);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('pointerdown', handlePointerDown, true);\n };\n }, [open, setOpen, floatingRef, referenceRef]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence>\n {open && (\n <motion.div\n key=\"popover-content\"\n id={contentId}\n ref={setFloating}\n variants={floatingVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"dialog\"\n {...rest}\n className={mergeClasses(\n 'fixed left-0 top-0 z-[9999] overflow-auto rounded-lg border border-border p-2 text-sm text-foreground glass',\n className,\n )}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n};\n\ninterface IControlledPopoverProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n triggerTooltip?: string;\n placement?: TPopoverPlacement;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n contentClassName?: string;\n tooltipClassName?: string;\n matchTriggerWidth?: boolean;\n manageFocus?: boolean;\n initialFocus?: (panel: HTMLElement) => HTMLElement | null;\n}\n\n/**\n * A tooltip on the trigger must sit OUTSIDE the handler: the handler clones its child to\n * inject the click handler and the reference ref, and a tooltip in that position swallows\n * both, so the popover stops opening.\n */\nconst ControlledPopover: React.FC<IControlledPopoverProps> = ({\n trigger,\n children,\n triggerTooltip,\n placement = 'bottom-start',\n open,\n onOpenChange,\n contentClassName,\n tooltipClassName,\n matchTriggerWidth,\n manageFocus,\n initialFocus,\n}) => {\n const handle = <ControlledPopoverHandler>{trigger}</ControlledPopoverHandler>;\n\n return (\n <ControlledPopoverRoot\n open={open}\n handler={onOpenChange}\n placement={placement}\n matchTriggerWidth={matchTriggerWidth}\n >\n {triggerTooltip ? (\n <ControlledTooltip content={triggerTooltip} className={classNames(tooltipClassName)}>\n {handle}\n </ControlledTooltip>\n ) : (\n handle\n )}\n\n <ControlledPopoverContent className={contentClassName} manageFocus={manageFocus} initialFocus={initialFocus}>\n {children}\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default ControlledPopover;\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import classNames from "classnames";
3
- import { twMerge } from "tailwind-merge";
3
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
4
4
  const TONE_BAR_CLASS = {
5
5
  brand: "bg-brand",
6
6
  success: "bg-success",
@@ -26,11 +26,11 @@ const ControlledProgress = ({
26
26
  "aria-valuenow": Math.round(clamped),
27
27
  "aria-valuemin": 0,
28
28
  "aria-valuemax": 100,
29
- className: twMerge("h-1.5 w-full overflow-hidden rounded-full bg-muted", className),
29
+ className: mergeClasses("h-1.5 w-full overflow-hidden rounded-full bg-muted", className),
30
30
  children: /* @__PURE__ */ jsx(
31
31
  "div",
32
32
  {
33
- className: twMerge(
33
+ className: mergeClasses(
34
34
  classNames("h-full rounded-full transition-interactive duration-slow", !barColor && TONE_BAR_CLASS[tone]),
35
35
  barClassName
36
36
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledProgress.js","sources":["../../../src/components/UI/ControlledProgress.tsx"],"sourcesContent":["import React from 'react';\nimport classNames from 'classnames';\nimport { twMerge } from 'tailwind-merge';\n\nexport type TProgressTone = 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\ninterface IControlledProgressProps {\n value: number;\n tone?: TProgressTone;\n className?: string;\n barClassName?: string;\n /** A colour that is DATA (a stage colour the user picked), not a theme token. */\n barColor?: string;\n label?: string;\n}\n\nconst TONE_BAR_CLASS: Record<TProgressTone, string> = {\n brand: 'bg-brand',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-danger',\n info: 'bg-info',\n neutral: 'bg-foreground-faint',\n};\n\nconst ControlledProgress: React.FC<IControlledProgressProps> = ({\n value,\n tone = 'brand',\n className,\n barClassName,\n barColor,\n label,\n}) => {\n const clamped = Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;\n\n return (\n <div\n role=\"progressbar\"\n aria-label={label}\n aria-valuenow={Math.round(clamped)}\n aria-valuemin={0}\n aria-valuemax={100}\n className={twMerge('h-1.5 w-full overflow-hidden rounded-full bg-muted', className)}\n >\n <div\n className={twMerge(\n classNames('h-full rounded-full transition-interactive duration-slow', !barColor && TONE_BAR_CLASS[tone]),\n barClassName,\n )}\n style={{ width: `${clamped}%`, backgroundColor: barColor }}\n />\n </div>\n );\n};\n\nexport default ControlledProgress;\n"],"names":[],"mappings":";;;AAgBA,MAAM,iBAAgD;AAAA,EACpD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAEA,MAAM,qBAAyD,CAAC;AAAA,EAC9D;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,UAAU,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC,IAAI;AAE7E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,iBAAe,KAAK,MAAM,OAAO;AAAA,MACjC,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,WAAW,QAAQ,sDAAsD,SAAS;AAAA,MAElF,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT,WAAW,4DAA4D,CAAC,YAAY,eAAe,IAAI,CAAC;AAAA,YACxG;AAAA,UAAA;AAAA,UAEF,OAAO,EAAE,OAAO,GAAG,OAAO,KAAK,iBAAiB,SAAA;AAAA,QAAS;AAAA,MAAA;AAAA,IAC3D;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"ControlledProgress.js","sources":["../../../src/components/UI/ControlledProgress.tsx"],"sourcesContent":["import React from 'react';\nimport classNames from 'classnames';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nexport type TProgressTone = 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\ninterface IControlledProgressProps {\n value: number;\n tone?: TProgressTone;\n className?: string;\n barClassName?: string;\n /** A colour that is DATA (a stage colour the user picked), not a theme token. */\n barColor?: string;\n label?: string;\n}\n\nconst TONE_BAR_CLASS: Record<TProgressTone, string> = {\n brand: 'bg-brand',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-danger',\n info: 'bg-info',\n neutral: 'bg-foreground-faint',\n};\n\nconst ControlledProgress: React.FC<IControlledProgressProps> = ({\n value,\n tone = 'brand',\n className,\n barClassName,\n barColor,\n label,\n}) => {\n const clamped = Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;\n\n return (\n <div\n role=\"progressbar\"\n aria-label={label}\n aria-valuenow={Math.round(clamped)}\n aria-valuemin={0}\n aria-valuemax={100}\n className={mergeClasses('h-1.5 w-full overflow-hidden rounded-full bg-muted', className)}\n >\n <div\n className={mergeClasses(\n classNames('h-full rounded-full transition-interactive duration-slow', !barColor && TONE_BAR_CLASS[tone]),\n barClassName,\n )}\n style={{ width: `${clamped}%`, backgroundColor: barColor }}\n />\n </div>\n );\n};\n\nexport default ControlledProgress;\n"],"names":[],"mappings":";;;AAiBA,MAAM,iBAAgD;AAAA,EACpD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAEA,MAAM,qBAAyD,CAAC;AAAA,EAC9D;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,UAAU,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC,IAAI;AAE7E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,iBAAe,KAAK,MAAM,OAAO;AAAA,MACjC,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,WAAW,aAAa,sDAAsD,SAAS;AAAA,MAEvF,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW;AAAA,YACT,WAAW,4DAA4D,CAAC,YAAY,eAAe,IAAI,CAAC;AAAA,YACxG;AAAA,UAAA;AAAA,UAEF,OAAO,EAAE,OAAO,GAAG,OAAO,KAAK,iBAAiB,SAAA;AAAA,QAAS;AAAA,MAAA;AAAA,IAC3D;AAAA,EAAA;AAGN;"}
@@ -8,6 +8,8 @@ interface IControlledSliderProps {
8
8
  disabled?: boolean;
9
9
  className?: string;
10
10
  label?: string;
11
+ /** Reaches the field itself, so a `FieldLabel` can point at it. */
12
+ id?: string;
11
13
  }
12
14
  declare const ControlledSlider: React.FC<IControlledSliderProps>;
13
15
  export default ControlledSlider;
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { twMerge } from "tailwind-merge";
2
+ import { mergeClasses } from "../../helpers/mergeClasses.js";
3
3
  const ControlledSlider = ({
4
4
  value,
5
5
  onChange,
@@ -8,10 +8,12 @@ const ControlledSlider = ({
8
8
  step = 1,
9
9
  disabled = false,
10
10
  className,
11
- label
11
+ label,
12
+ id
12
13
  }) => /* @__PURE__ */ jsx(
13
14
  "input",
14
15
  {
16
+ id,
15
17
  type: "range",
16
18
  min,
17
19
  max,
@@ -20,7 +22,10 @@ const ControlledSlider = ({
20
22
  disabled,
21
23
  "aria-label": label,
22
24
  onChange: (event) => onChange(Number(event.target.value)),
23
- className: twMerge("h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-brand", className)
25
+ className: mergeClasses(
26
+ "h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-brand",
27
+ className
28
+ )
24
29
  }
25
30
  );
26
31
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledSlider.js","sources":["../../../src/components/UI/ControlledSlider.tsx"],"sourcesContent":["import React from 'react';\nimport { twMerge } from 'tailwind-merge';\n\ninterface IControlledSliderProps {\n value: number;\n onChange: (value: number) => void;\n min?: number;\n max?: number;\n step?: number;\n disabled?: boolean;\n className?: string;\n label?: string;\n}\n\nconst ControlledSlider: React.FC<IControlledSliderProps> = ({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n className,\n label,\n}) => (\n <input\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={Number.isFinite(value) ? value : 0}\n disabled={disabled}\n aria-label={label}\n onChange={(event) => onChange(Number(event.target.value))}\n className={twMerge('h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-brand', className)}\n />\n);\n\nexport default ControlledSlider;\n"],"names":[],"mappings":";;AAcA,MAAM,mBAAqD,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,MAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,IACxC;AAAA,IACA,cAAY;AAAA,IACZ,UAAU,CAAC,UAAU,SAAS,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD,WAAW,QAAQ,kFAAkF,SAAS;AAAA,EAAA;AAChH;"}
1
+ {"version":3,"file":"ControlledSlider.js","sources":["../../../src/components/UI/ControlledSlider.tsx"],"sourcesContent":["import React from 'react';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\ninterface IControlledSliderProps {\n value: number;\n onChange: (value: number) => void;\n min?: number;\n max?: number;\n step?: number;\n disabled?: boolean;\n className?: string;\n label?: string;\n /** Reaches the field itself, so a `FieldLabel` can point at it. */\n id?: string;\n}\n\nconst ControlledSlider: React.FC<IControlledSliderProps> = ({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n className,\n label,\n id,\n}) => (\n <input\n id={id}\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={Number.isFinite(value) ? value : 0}\n disabled={disabled}\n aria-label={label}\n onChange={(event) => onChange(Number(event.target.value))}\n className={mergeClasses(\n 'h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-brand',\n className,\n )}\n />\n);\n\nexport default ControlledSlider;\n"],"names":[],"mappings":";;AAiBA,MAAM,mBAAqD,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA,EAAA;AAAA,IACC;AAAA,IACA,MAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,IACxC;AAAA,IACA,cAAY;AAAA,IACZ,UAAU,CAAC,UAAU,SAAS,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,EACF;AACF;"}