@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.
- package/dist/assets/umode-ui.css +0 -5
- package/dist/components/DateTime/DatePicker.d.ts +2 -0
- package/dist/components/DateTime/DatePicker.js +5 -2
- package/dist/components/DateTime/DatePicker.js.map +1 -1
- package/dist/components/DateTime/DateRangePicker.d.ts +2 -0
- package/dist/components/DateTime/DateRangePicker.js +3 -0
- package/dist/components/DateTime/DateRangePicker.js.map +1 -1
- package/dist/components/Form/ControlledButton.d.ts +1 -1
- package/dist/components/Form/ControlledButton.js +2 -2
- package/dist/components/Form/ControlledButton.js.map +1 -1
- package/dist/components/Form/ControlledCheckbox.d.ts +5 -1
- package/dist/components/Form/ControlledCheckbox.js +53 -39
- package/dist/components/Form/ControlledCheckbox.js.map +1 -1
- package/dist/components/Form/ControlledDialogPrimitives.js +17 -5
- package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
- package/dist/components/Form/ControlledDrawer.js +1 -0
- package/dist/components/Form/ControlledDrawer.js.map +1 -1
- package/dist/components/Form/ControlledImage/index.js +2 -0
- package/dist/components/Form/ControlledImage/index.js.map +1 -1
- package/dist/components/Form/ControlledInput.d.ts +1 -1
- package/dist/components/Form/ControlledInput.js +4 -4
- package/dist/components/Form/ControlledInput.js.map +1 -1
- package/dist/components/Form/ControlledSelect.js +1 -1
- package/dist/components/Form/ControlledSelect.js.map +1 -1
- package/dist/components/Form/ControlledTabs.js +17 -3
- package/dist/components/Form/ControlledTabs.js.map +1 -1
- package/dist/components/Form/ControlledTabsPrimitives.js +6 -6
- package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
- package/dist/components/Form/ControlledTextarea.js +2 -2
- package/dist/components/Form/ControlledTextarea.js.map +1 -1
- package/dist/components/Form/ControlledTimeline.js +11 -8
- package/dist/components/Form/ControlledTimeline.js.map +1 -1
- package/dist/components/Form/ControlledVoiceTextarea.d.ts +2 -0
- package/dist/components/Form/ControlledVoiceTextarea.js +3 -0
- package/dist/components/Form/ControlledVoiceTextarea.js.map +1 -1
- package/dist/components/Form/FieldLabel.d.ts +16 -0
- package/dist/components/Form/FieldLabel.js +7 -0
- package/dist/components/Form/FieldLabel.js.map +1 -0
- package/dist/components/Form/RichTextEditor.js +15 -1
- package/dist/components/Form/RichTextEditor.js.map +1 -1
- package/dist/components/Form/attachments/AttachmentPreviewList.js +3 -0
- package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
- package/dist/components/Form/attachments/FileDropzone.js +3 -3
- package/dist/components/Form/attachments/FileDropzone.js.map +1 -1
- package/dist/components/Navigation/PageTitle.d.ts +6 -0
- package/dist/components/Navigation/PageTitle.js +6 -4
- package/dist/components/Navigation/PageTitle.js.map +1 -1
- package/dist/components/Search/filters/FiltersPanel.js +1 -0
- package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
- package/dist/components/Search/filters/FiltersSummaryTooltip.js +2 -2
- package/dist/components/Search/filters/FiltersSummaryTooltip.js.map +1 -1
- package/dist/components/Shell/LanguageToggle.js +1 -1
- package/dist/components/Shell/LanguageToggle.js.map +1 -1
- package/dist/components/Shell/SidebarMenu.js +9 -1
- package/dist/components/Shell/SidebarMenu.js.map +1 -1
- package/dist/components/Shell/ThemeToggle.js +2 -1
- package/dist/components/Shell/ThemeToggle.js.map +1 -1
- package/dist/components/UI/BaseDataCard.d.ts +2 -0
- package/dist/components/UI/BaseDataCard.js +6 -0
- package/dist/components/UI/BaseDataCard.js.map +1 -1
- package/dist/components/UI/BulkActionBar.js +2 -0
- package/dist/components/UI/BulkActionBar.js.map +1 -1
- package/dist/components/UI/Chips/OutlinedChip.js +13 -6
- package/dist/components/UI/Chips/OutlinedChip.js.map +1 -1
- package/dist/components/UI/ColorSwatch.js +2 -2
- package/dist/components/UI/ColorSwatch.js.map +1 -1
- package/dist/components/UI/ControlledAccordion.js +10 -4
- package/dist/components/UI/ControlledAccordion.js.map +1 -1
- package/dist/components/UI/ControlledCollapse.js +2 -2
- package/dist/components/UI/ControlledCollapse.js.map +1 -1
- package/dist/components/UI/ControlledMenu.js +3 -3
- package/dist/components/UI/ControlledMenu.js.map +1 -1
- package/dist/components/UI/ControlledPopover.d.ts +12 -0
- package/dist/components/UI/ControlledPopover.js +35 -5
- package/dist/components/UI/ControlledPopover.js.map +1 -1
- package/dist/components/UI/ControlledProgress.js +3 -3
- package/dist/components/UI/ControlledProgress.js.map +1 -1
- package/dist/components/UI/ControlledSlider.d.ts +2 -0
- package/dist/components/UI/ControlledSlider.js +8 -3
- package/dist/components/UI/ControlledSlider.js.map +1 -1
- package/dist/components/UI/ControlledTooltip.js +7 -3
- package/dist/components/UI/ControlledTooltip.js.map +1 -1
- package/dist/components/UI/DataTable/DataTable.js +1 -1
- package/dist/components/UI/DataTable/DataTableBody.js +4 -0
- package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +4 -2
- package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +5 -1
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +19 -4
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
- package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
- package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
- package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableStates.js +2 -2
- package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
- package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +2 -1
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
- package/dist/components/UI/DataTable/types.d.ts +4 -0
- package/dist/components/UI/DataTable/types.js.map +1 -1
- package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
- package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
- package/dist/components/UI/Flow/FlowCanvas.js +2 -2
- package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
- package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
- package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
- package/dist/components/UI/Flow/FlowInspector.js +3 -2
- package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
- package/dist/components/UI/Flow/FlowIssueList.js +2 -2
- package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
- package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
- package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
- package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
- package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
- package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
- package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
- package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
- package/dist/components/UI/HorizontalScrollArea.js +1 -0
- package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
- package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
- package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
- package/dist/components/UI/ImagePlaceholder.js +5 -2
- package/dist/components/UI/ImagePlaceholder.js.map +1 -1
- package/dist/components/UI/ImageZoomOverlay.js +1 -0
- package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
- package/dist/components/UI/MaterialSymbol/index.js +5 -0
- package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
- package/dist/components/UI/NoDataFound/index.js +3 -3
- package/dist/components/UI/NoDataFound/index.js.map +1 -1
- package/dist/components/UI/RichTextContent.js +3 -3
- package/dist/components/UI/RichTextContent.js.map +1 -1
- package/dist/components/UI/ThreeDots.js +15 -11
- package/dist/components/UI/ThreeDots.js.map +1 -1
- package/dist/helpers/mergeClasses.d.ts +7 -3
- package/dist/helpers/mergeClasses.js +23 -1
- package/dist/helpers/mergeClasses.js.map +1 -1
- package/dist/hooks/useVoiceTranscription.js +5 -2
- package/dist/hooks/useVoiceTranscription.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +58 -54
- package/dist/index.js.map +1 -1
- package/dist/locales/en/ui.json.d.ts +8 -3
- package/dist/locales/en/ui.json.js +3 -3
- package/dist/locales/index.d.ts +10 -0
- package/dist/locales/pt/ui.json.d.ts +8 -3
- package/dist/locales/pt/ui.json.js +3 -3
- package/package.json +1 -1
- package/src/assets/styles/base.css +7 -1
- package/tailwind-plugins/glass.js +23 -28
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FiltersPanel.js","sources":["../../../../src/components/Search/filters/FiltersPanel.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledDialog from '@/components/Form/ControlledDialog';\nimport ControlledDrawer from '@/components/Form/ControlledDrawer';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport FilterChipsRow from './FilterChipsRow';\nimport FiltersButton from './FiltersButton';\nimport FiltersForm from './FiltersForm';\nimport { FILTERS_POPOVER_WIDTH_REM, getFiltersPanelWidth, TFiltersSize, TFiltersVariant } from './filtersLayout';\nimport { IFiltersSchema, TFilterValues } from './filterTypes';\nimport { formatFilterValue } from './filterValues';\nimport { useFilters } from './useFilters';\n\nexport interface IFiltersPanelProps {\n schema: IFiltersSchema;\n values?: TFilterValues;\n onChange?: (values: TFilterValues) => void;\n initialValues?: TFilterValues;\n urlKey?: string;\n\n variant?: TFiltersVariant;\n size?: TFiltersSize;\n\n /** Controlled open state. Omit to let the panel own its trigger and state. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Hides the built-in trigger, for hosts that render their own. */\n hideTrigger?: boolean;\n triggerLabel?: string;\n /** Match the page actions standing next to the trigger. */\n triggerSize?: 'sm' | 'md';\n\n loading?: boolean;\n disabled?: boolean;\n onFirstOpen?: () => void;\n emptyMessage?: string;\n className?: string;\n}\n\n/**\n * The filter surface. `variant` picks the shell (side drawer, anchored popover\n * or inline block); the schema, state and field rendering are identical in all\n * three, so a screen never reimplements filtering to get a different shape.\n */\nconst FiltersPanel: React.FC<IFiltersPanelProps> = ({\n schema,\n values,\n onChange,\n initialValues,\n urlKey,\n variant = 'drawer',\n size = 'md',\n open: controlledOpen,\n onOpenChange,\n hideTrigger,\n triggerLabel,\n triggerSize,\n loading,\n disabled,\n onFirstOpen,\n emptyMessage,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n\n const fields = useMemo(() => schema?.fields ?? [], [schema]);\n const filters = useFilters({ fields, value: values, onChange, initialValues, urlKey });\n\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const [isDiscardOpen, setIsDiscardOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const hasOpenedOnceRef = useRef(false);\n const prevOpenRef = useRef(open);\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange],\n );\n\n const filtersRef = useRef(filters);\n filtersRef.current = filters;\n\n useEffect(() => {\n if (open === prevOpenRef.current) return;\n prevOpenRef.current = open;\n if (open && !hasOpenedOnceRef.current) {\n hasOpenedOnceRef.current = true;\n onFirstOpen?.();\n }\n if (open) {\n // Reopening always shows what is actually applied, never a stale draft.\n filtersRef.current.resetDraft();\n window.requestAnimationFrame(() => {\n bodyRef.current?.querySelector<HTMLElement>('input, button, [tabindex]')?.focus();\n });\n } else {\n triggerRef.current?.focus();\n }\n }, [open, onFirstOpen]);\n\n const closeRequest = useCallback(() => {\n if (filters.isDirty) {\n setIsDiscardOpen(true);\n return;\n }\n setOpen(false);\n }, [filters.isDirty, setOpen]);\n\n const discardAndClose = useCallback(() => {\n filters.resetDraft();\n setIsDiscardOpen(false);\n setOpen(false);\n }, [filters, setOpen]);\n\n const handleApply = useCallback(() => {\n filters.apply();\n setOpen(false);\n }, [filters, setOpen]);\n\n /** Clearing does not close the panel: the user usually clears to start over. */\n const handleClear = useCallback(() => {\n filters.clear();\n }, [filters]);\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n // React bubbles a select list's keydown through its portal; an ESC it consumed is not ours.\n if (event.defaultPrevented) return;\n event.stopPropagation();\n closeRequest();\n return;\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n handleApply();\n }\n };\n\n const form = (\n <FiltersForm\n fields={fields}\n groups={schema?.groups}\n values={filters.draft}\n onValueChange={filters.setDraftValue}\n disabled={disabled}\n loading={loading}\n emptyMessage={emptyMessage}\n />\n );\n\n if (variant === 'chips') {\n // A chip row applies on pick, so the draft is committed immediately.\n return (\n <FilterChipsRow\n fields={fields}\n values={filters.applied}\n disabled={disabled}\n className={className}\n onValueChange={(key, next) => {\n filters.setDraftValue(key, next);\n window.requestAnimationFrame(() => filters.apply());\n }}\n onClearAll={filters.clear}\n />\n );\n }\n\n if (variant === 'inline') {\n return (\n <div className={classNames('flex flex-col gap-y-4', className)}>\n {form}\n {filters.isDirty && (\n <div className=\"flex items-center justify-end gap-x-2\">\n <ControlledButton variant=\"text\" tone=\"neutral\" size=\"sm\" onClick={filters.resetDraft}>\n {t('actions.cancel')}\n </ControlledButton>\n <ControlledButton size=\"sm\" onClick={() => filters.apply()}>\n {t('actions.apply')}\n </ControlledButton>\n </div>\n )}\n </div>\n );\n }\n\n const activeSummary = filters.activeFields.map((field) => ({\n key: field.key,\n label: field.title,\n value: formatFilterValue(field, filters.applied?.[field.key]),\n }));\n\n const trigger = hideTrigger ? null : (\n <FiltersButton\n ref={triggerRef}\n label={triggerLabel}\n size={triggerSize}\n activeCount={filters.activeCount}\n activeFilters={activeSummary}\n loading={loading}\n disabled={disabled}\n onClick={() => setOpen(!open)}\n />\n );\n\n const header = (\n <div className=\"flex shrink-0 items-start justify-between gap-x-3 border-b border-border p-4\">\n <div className=\"flex items-start gap-x-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-xl bg-brand-soft text-brand-emphasis\">\n <MaterialSymbol icon=\"filter_alt\" size={20} />\n </div>\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"truncate text-base font-semibold text-foreground\">\n {schema?.title ?? t('actions.filter')}\n </h2>\n {schema?.subtitle && <p className=\"text-xs text-foreground-subtle\">{schema.subtitle}</p>}\n </div>\n </div>\n <ControlledTooltip className=\"z-[9999]\" content={t('actions.close')}>\n <ControlledButton\n defaultHtmlButton\n onClick={closeRequest}\n className=\"rounded-full p-1 text-foreground-subtle transition-colors hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border\"\n >\n <MaterialSymbol icon=\"close\" size={20} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n\n const footer = (\n <div className=\"flex shrink-0 items-center justify-between gap-x-3 border-t border-border p-4\">\n <ControlledButton\n variant=\"text\"\n tone=\"neutral\"\n disabled={filters.activeCount === 0}\n className=\"flex items-center gap-x-1\"\n onClick={handleClear}\n >\n <MaterialSymbol icon=\"filter_list_off\" size={18} />\n {t('actions.clear')}\n </ControlledButton>\n\n <ControlledButton disabled={!filters.isDirty} onClick={handleApply}>\n {t('actions.apply')}\n </ControlledButton>\n </div>\n );\n\n const discardDialog = (\n <ControlledDialog\n size=\"sm\"\n open={isDiscardOpen}\n title={t('filtersDrawer.discardChangesTitle')}\n subtitle={t('filtersDrawer.discardChangesMessage')}\n cancelLabel={t('filtersDrawer.keepEditing')}\n confirmLabel={t('filtersDrawer.discard')}\n onCancel={() => setIsDiscardOpen(false)}\n onConfirm={discardAndClose}\n >\n {null}\n </ControlledDialog>\n );\n\n if (variant === 'popover') {\n return (\n <>\n <ControlledPopoverRoot open={open} handler={setOpen} placement=\"bottom-end\">\n <ControlledPopoverHandler>{trigger ?? <span />}</ControlledPopoverHandler>\n <ControlledPopoverContent\n className={classNames('z-[9998] flex max-h-[70vh] flex-col overflow-hidden rounded-xl p-0', className)}\n style={{ width: `${FILTERS_POPOVER_WIDTH_REM}rem` }}\n role=\"dialog\"\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n >\n {header}\n <div ref={bodyRef} className=\"min-h-0 flex-1 overflow-y-auto px-4 py-3\">\n {form}\n </div>\n {footer}\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n {discardDialog}\n </>\n );\n }\n\n return (\n <>\n {trigger}\n\n <ControlledDrawer\n open={open}\n onClose={closeRequest}\n placement=\"right\"\n size={getFiltersPanelWidth(size)}\n lazy\n className={className}\n >\n <div\n className=\"flex h-full flex-col\"\n role=\"dialog\"\n aria-modal\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n >\n {header}\n <div ref={bodyRef} className=\"min-h-0 flex-1 overflow-y-auto px-4 py-4\">\n {form}\n </div>\n {footer}\n </div>\n </ControlledDrawer>\n\n {discardDialog}\n </>\n );\n};\n\nexport default FiltersPanel;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FiltersPanel.js","sources":["../../../../src/components/Search/filters/FiltersPanel.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledDialog from '@/components/Form/ControlledDialog';\nimport ControlledDrawer from '@/components/Form/ControlledDrawer';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport FilterChipsRow from './FilterChipsRow';\nimport FiltersButton from './FiltersButton';\nimport FiltersForm from './FiltersForm';\nimport { FILTERS_POPOVER_WIDTH_REM, getFiltersPanelWidth, TFiltersSize, TFiltersVariant } from './filtersLayout';\nimport { IFiltersSchema, TFilterValues } from './filterTypes';\nimport { formatFilterValue } from './filterValues';\nimport { useFilters } from './useFilters';\n\nexport interface IFiltersPanelProps {\n schema: IFiltersSchema;\n values?: TFilterValues;\n onChange?: (values: TFilterValues) => void;\n initialValues?: TFilterValues;\n urlKey?: string;\n\n variant?: TFiltersVariant;\n size?: TFiltersSize;\n\n /** Controlled open state. Omit to let the panel own its trigger and state. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Hides the built-in trigger, for hosts that render their own. */\n hideTrigger?: boolean;\n triggerLabel?: string;\n /** Match the page actions standing next to the trigger. */\n triggerSize?: 'sm' | 'md';\n\n loading?: boolean;\n disabled?: boolean;\n onFirstOpen?: () => void;\n emptyMessage?: string;\n className?: string;\n}\n\n/**\n * The filter surface. `variant` picks the shell (side drawer, anchored popover\n * or inline block); the schema, state and field rendering are identical in all\n * three, so a screen never reimplements filtering to get a different shape.\n */\nconst FiltersPanel: React.FC<IFiltersPanelProps> = ({\n schema,\n values,\n onChange,\n initialValues,\n urlKey,\n variant = 'drawer',\n size = 'md',\n open: controlledOpen,\n onOpenChange,\n hideTrigger,\n triggerLabel,\n triggerSize,\n loading,\n disabled,\n onFirstOpen,\n emptyMessage,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n\n const fields = useMemo(() => schema?.fields ?? [], [schema]);\n const filters = useFilters({ fields, value: values, onChange, initialValues, urlKey });\n\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const [isDiscardOpen, setIsDiscardOpen] = useState(false);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const hasOpenedOnceRef = useRef(false);\n const prevOpenRef = useRef(open);\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange],\n );\n\n const filtersRef = useRef(filters);\n filtersRef.current = filters;\n\n useEffect(() => {\n if (open === prevOpenRef.current) return;\n prevOpenRef.current = open;\n if (open && !hasOpenedOnceRef.current) {\n hasOpenedOnceRef.current = true;\n onFirstOpen?.();\n }\n if (open) {\n // Reopening always shows what is actually applied, never a stale draft.\n filtersRef.current.resetDraft();\n window.requestAnimationFrame(() => {\n bodyRef.current?.querySelector<HTMLElement>('input, button, [tabindex]')?.focus();\n });\n } else {\n triggerRef.current?.focus();\n }\n }, [open, onFirstOpen]);\n\n const closeRequest = useCallback(() => {\n if (filters.isDirty) {\n setIsDiscardOpen(true);\n return;\n }\n setOpen(false);\n }, [filters.isDirty, setOpen]);\n\n const discardAndClose = useCallback(() => {\n filters.resetDraft();\n setIsDiscardOpen(false);\n setOpen(false);\n }, [filters, setOpen]);\n\n const handleApply = useCallback(() => {\n filters.apply();\n setOpen(false);\n }, [filters, setOpen]);\n\n /** Clearing does not close the panel: the user usually clears to start over. */\n const handleClear = useCallback(() => {\n filters.clear();\n }, [filters]);\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n // React bubbles a select list's keydown through its portal; an ESC it consumed is not ours.\n if (event.defaultPrevented) return;\n event.stopPropagation();\n closeRequest();\n return;\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n handleApply();\n }\n };\n\n const form = (\n <FiltersForm\n fields={fields}\n groups={schema?.groups}\n values={filters.draft}\n onValueChange={filters.setDraftValue}\n disabled={disabled}\n loading={loading}\n emptyMessage={emptyMessage}\n />\n );\n\n if (variant === 'chips') {\n // A chip row applies on pick, so the draft is committed immediately.\n return (\n <FilterChipsRow\n fields={fields}\n values={filters.applied}\n disabled={disabled}\n className={className}\n onValueChange={(key, next) => {\n filters.setDraftValue(key, next);\n window.requestAnimationFrame(() => filters.apply());\n }}\n onClearAll={filters.clear}\n />\n );\n }\n\n if (variant === 'inline') {\n return (\n <div className={classNames('flex flex-col gap-y-4', className)}>\n {form}\n {filters.isDirty && (\n <div className=\"flex items-center justify-end gap-x-2\">\n <ControlledButton variant=\"text\" tone=\"neutral\" size=\"sm\" onClick={filters.resetDraft}>\n {t('actions.cancel')}\n </ControlledButton>\n <ControlledButton size=\"sm\" onClick={() => filters.apply()}>\n {t('actions.apply')}\n </ControlledButton>\n </div>\n )}\n </div>\n );\n }\n\n const activeSummary = filters.activeFields.map((field) => ({\n key: field.key,\n label: field.title,\n value: formatFilterValue(field, filters.applied?.[field.key]),\n }));\n\n const trigger = hideTrigger ? null : (\n <FiltersButton\n ref={triggerRef}\n label={triggerLabel}\n size={triggerSize}\n activeCount={filters.activeCount}\n activeFilters={activeSummary}\n loading={loading}\n disabled={disabled}\n onClick={() => setOpen(!open)}\n />\n );\n\n const header = (\n <div className=\"flex shrink-0 items-start justify-between gap-x-3 border-b border-border p-4\">\n <div className=\"flex items-start gap-x-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-xl bg-brand-soft text-brand-emphasis\">\n <MaterialSymbol icon=\"filter_alt\" size={20} />\n </div>\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"truncate text-base font-semibold text-foreground\">\n {schema?.title ?? t('actions.filter')}\n </h2>\n {schema?.subtitle && <p className=\"text-xs text-foreground-subtle\">{schema.subtitle}</p>}\n </div>\n </div>\n <ControlledTooltip className=\"z-[9999]\" content={t('actions.close')}>\n <ControlledButton\n aria-label={t('actions.close')}\n defaultHtmlButton\n onClick={closeRequest}\n className=\"rounded-full p-1 text-foreground-subtle transition-colors hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border\"\n >\n <MaterialSymbol icon=\"close\" size={20} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n\n const footer = (\n <div className=\"flex shrink-0 items-center justify-between gap-x-3 border-t border-border p-4\">\n <ControlledButton\n variant=\"text\"\n tone=\"neutral\"\n disabled={filters.activeCount === 0}\n className=\"flex items-center gap-x-1\"\n onClick={handleClear}\n >\n <MaterialSymbol icon=\"filter_list_off\" size={18} />\n {t('actions.clear')}\n </ControlledButton>\n\n <ControlledButton disabled={!filters.isDirty} onClick={handleApply}>\n {t('actions.apply')}\n </ControlledButton>\n </div>\n );\n\n const discardDialog = (\n <ControlledDialog\n size=\"sm\"\n open={isDiscardOpen}\n title={t('filtersDrawer.discardChangesTitle')}\n subtitle={t('filtersDrawer.discardChangesMessage')}\n cancelLabel={t('filtersDrawer.keepEditing')}\n confirmLabel={t('filtersDrawer.discard')}\n onCancel={() => setIsDiscardOpen(false)}\n onConfirm={discardAndClose}\n >\n {null}\n </ControlledDialog>\n );\n\n if (variant === 'popover') {\n return (\n <>\n <ControlledPopoverRoot open={open} handler={setOpen} placement=\"bottom-end\">\n <ControlledPopoverHandler>{trigger ?? <span />}</ControlledPopoverHandler>\n <ControlledPopoverContent\n className={classNames('z-[9998] flex max-h-[70vh] flex-col overflow-hidden rounded-xl p-0', className)}\n style={{ width: `${FILTERS_POPOVER_WIDTH_REM}rem` }}\n role=\"dialog\"\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n >\n {header}\n <div ref={bodyRef} className=\"min-h-0 flex-1 overflow-y-auto px-4 py-3\">\n {form}\n </div>\n {footer}\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n {discardDialog}\n </>\n );\n }\n\n return (\n <>\n {trigger}\n\n <ControlledDrawer\n open={open}\n onClose={closeRequest}\n placement=\"right\"\n size={getFiltersPanelWidth(size)}\n lazy\n className={className}\n >\n <div\n className=\"flex h-full flex-col\"\n role=\"dialog\"\n aria-modal\n aria-labelledby={titleId}\n onKeyDown={handleKeyDown}\n >\n {header}\n <div ref={bodyRef} className=\"min-h-0 flex-1 overflow-y-auto px-4 py-4\">\n {form}\n </div>\n {footer}\n </div>\n </ControlledDrawer>\n\n {discardDialog}\n </>\n );\n};\n\nexport default FiltersPanel;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
2
|
import ControlledTooltip from "../../UI/ControlledTooltip.js";
|
|
3
|
+
import { mergeClasses } from "../../../helpers/mergeClasses.js";
|
|
4
4
|
const SUMMARY_TOOLTIP_CLASS = "z-[9999] max-w-[26rem] overflow-hidden rounded-xl border border-brand-border bg-surface p-0 shadow-xl";
|
|
5
5
|
const FiltersSummaryTooltip = ({ items, fallback, className, children }) => {
|
|
6
6
|
const summary = items.filter((item) => item.value);
|
|
@@ -11,7 +11,7 @@ const FiltersSummaryTooltip = ({ items, fallback, className, children }) => {
|
|
|
11
11
|
return /* @__PURE__ */ jsx(
|
|
12
12
|
ControlledTooltip,
|
|
13
13
|
{
|
|
14
|
-
className:
|
|
14
|
+
className: mergeClasses(SUMMARY_TOOLTIP_CLASS, className),
|
|
15
15
|
content: /* @__PURE__ */ jsx("div", { className: "flex w-max min-w-[17rem] max-w-[calc(100vw-2rem)] flex-col gap-3 p-4", children: summary.map((item) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 border-l-2 border-brand-border pl-3", children: [
|
|
16
16
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-brand-emphasis", children: item.label }),
|
|
17
17
|
/* @__PURE__ */ jsx("span", { className: "text-xs leading-relaxed text-foreground-muted", children: item.value })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FiltersSummaryTooltip.js","sources":["../../../../src/components/Search/filters/FiltersSummaryTooltip.tsx"],"sourcesContent":["import { ReactElement, ReactNode } from 'react';\nimport
|
|
1
|
+
{"version":3,"file":"FiltersSummaryTooltip.js","sources":["../../../../src/components/Search/filters/FiltersSummaryTooltip.tsx"],"sourcesContent":["import { ReactElement, ReactNode } from 'react';\n\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport { IFiltersButtonSummaryItem } from './filterTypes';\n\nconst SUMMARY_TOOLTIP_CLASS =\n 'z-[9999] max-w-[26rem] overflow-hidden rounded-xl border border-brand-border bg-surface p-0 shadow-xl';\n\ninterface IFiltersSummaryTooltipProps {\n items: Array<IFiltersButtonSummaryItem>;\n /** Mostrado quando nada está aplicado. Sem ele, o gatilho fica sem tooltip nesse estado. */\n fallback?: ReactNode;\n className?: string;\n children: ReactElement;\n}\n\n/**\n * O que está filtrado, campo a campo, no hover do gatilho.\n *\n * Existe como componente próprio porque o mapa e o board têm gatilhos visuais diferentes (um botão\n * de página e um item da barra do canvas) e o resumo precisa ser o MESMO nos dois: um segundo\n * desenho do mesmo conteúdo é onde os dois começam a divergir.\n *\n * Um item sem `value` é descartado: tooltip com conteúdo vazio ainda abre, como uma caixa preta.\n */\nconst FiltersSummaryTooltip: React.FC<IFiltersSummaryTooltipProps> = ({ items, fallback, className, children }) => {\n const summary = items.filter((item) => item.value);\n\n if (summary.length === 0) {\n if (!fallback) return children;\n return (\n <ControlledTooltip content={fallback} className={className}>\n {children}\n </ControlledTooltip>\n );\n }\n\n return (\n <ControlledTooltip\n className={mergeClasses(SUMMARY_TOOLTIP_CLASS, className)}\n content={\n <div className=\"flex w-max min-w-[17rem] max-w-[calc(100vw-2rem)] flex-col gap-3 p-4\">\n {summary.map((item) => (\n <div key={item.key} className=\"flex flex-col gap-1 border-l-2 border-brand-border pl-3\">\n <span className=\"text-xs font-bold text-brand-emphasis\">{item.label}</span>\n <span className=\"text-xs leading-relaxed text-foreground-muted\">{item.value}</span>\n </div>\n ))}\n </div>\n }\n >\n {children}\n </ControlledTooltip>\n );\n};\n\nexport default FiltersSummaryTooltip;\n"],"names":[],"mappings":";;;AAOA,MAAM,wBACJ;AAmBF,MAAM,wBAA+D,CAAC,EAAE,OAAO,UAAU,WAAW,eAAe;AACjH,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS,KAAK,KAAK;AAEjD,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAI,CAAC,SAAU,QAAO;AACtB,WACE,oBAAC,mBAAA,EAAkB,SAAS,UAAU,WACnC,UACH;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,aAAa,uBAAuB,SAAS;AAAA,MACxD,SACE,oBAAC,OAAA,EAAI,WAAU,wEACZ,UAAA,QAAQ,IAAI,CAAC,SACZ,qBAAC,OAAA,EAAmB,WAAU,2DAC5B,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,KAAK,OAAM;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,iDAAiD,eAAK,MAAA,CAAM;AAAA,MAAA,EAAA,GAFpE,KAAK,GAGf,CACD,GACH;AAAA,MAGD;AAAA,IAAA;AAAA,EAAA;AAGP;"}
|
|
@@ -11,7 +11,7 @@ const LanguageToggle = ({ language, onChange }) => {
|
|
|
11
11
|
const isPT = language === "pt";
|
|
12
12
|
const optionClass = (isActive) => classNames(
|
|
13
13
|
"relative z-10 flex shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-semibold leading-none tracking-wide transition-colors duration-base",
|
|
14
|
-
isActive ? "text-brand-emphasis" : "text-foreground-
|
|
14
|
+
isActive ? "text-brand-emphasis" : "text-foreground-subtle hover:text-foreground-muted"
|
|
15
15
|
);
|
|
16
16
|
return /* @__PURE__ */ jsx(ControlledTooltip, { content: t("language.toggle"), children: /* @__PURE__ */ jsxs("div", { className: classNames(HEADER_CONTROL_SHELL_CLASS, "gap-x-1.5 pl-2.5"), children: [
|
|
17
17
|
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "language", size: 15, className: "shrink-0 text-foreground-faint" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageToggle.js","sources":["../../../src/components/Shell/LanguageToggle.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\n\nconst OPTION_WIDTH_REM = 2.25;\n\nexport type TUiLanguage = 'pt' | 'en';\n\ninterface ILanguageToggleProps {\n language: TUiLanguage;\n onChange: (language: TUiLanguage) => void;\n}\n\n/** PT/EN knob; the host owns the i18n instance and persistence. */\nconst LanguageToggle: React.FC<ILanguageToggleProps> = ({ language, onChange }) => {\n const { t } = useTranslation('ui');\n\n const isPT = language === 'pt';\n\n const optionClass = (isActive: boolean) =>\n classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-semibold leading-none tracking-wide transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-
|
|
1
|
+
{"version":3,"file":"LanguageToggle.js","sources":["../../../src/components/Shell/LanguageToggle.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\n\nconst OPTION_WIDTH_REM = 2.25;\n\nexport type TUiLanguage = 'pt' | 'en';\n\ninterface ILanguageToggleProps {\n language: TUiLanguage;\n onChange: (language: TUiLanguage) => void;\n}\n\n/** PT/EN knob; the host owns the i18n instance and persistence. */\nconst LanguageToggle: React.FC<ILanguageToggleProps> = ({ language, onChange }) => {\n const { t } = useTranslation('ui');\n\n const isPT = language === 'pt';\n\n const optionClass = (isActive: boolean) =>\n classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-semibold leading-none tracking-wide transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-subtle hover:text-foreground-muted',\n );\n\n return (\n <ControlledTooltip content={t('language.toggle')}>\n <div className={classNames(HEADER_CONTROL_SHELL_CLASS, 'gap-x-1.5 pl-2.5')}>\n <MaterialSymbol icon=\"language\" size={15} className=\"shrink-0 text-foreground-faint\" />\n\n <div className=\"relative flex items-center\">\n <div\n aria-hidden\n className=\"pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter\"\n style={{\n width: `${OPTION_WIDTH_REM}rem`,\n height: `${HEADER_CONTROL_SIZE_REM}rem`,\n transform: `translateX(${isPT ? 0 : OPTION_WIDTH_REM}rem)`,\n }}\n />\n\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isPT}\n onClick={() => onChange('pt')}\n className={optionClass(isPT)}\n style={{ width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` }}\n >\n {t('language.pt_short')}\n </ControlledButton>\n\n <ControlledButton\n defaultHtmlButton\n aria-pressed={!isPT}\n onClick={() => onChange('en')}\n className={optionClass(!isPT)}\n style={{ width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` }}\n >\n {t('language.en_short')}\n </ControlledButton>\n </div>\n </div>\n </ControlledTooltip>\n );\n};\n\nexport default LanguageToggle;\n"],"names":[],"mappings":";;;;;;;AASA,MAAM,mBAAmB;AAUzB,MAAM,iBAAiD,CAAC,EAAE,UAAU,eAAe;AACjF,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,OAAO,aAAa;AAE1B,QAAM,cAAc,CAAC,aACnB;AAAA,IACE;AAAA,IACA,WAAW,wBAAwB;AAAA,EAAA;AAGvC,SACE,oBAAC,mBAAA,EAAkB,SAAS,EAAE,iBAAiB,GAC7C,UAAA,qBAAC,OAAA,EAAI,WAAW,WAAW,4BAA4B,kBAAkB,GACvE,UAAA;AAAA,IAAA,oBAAC,kBAAe,MAAK,YAAW,MAAM,IAAI,WAAU,kCAAiC;AAAA,IAErF,qBAAC,OAAA,EAAI,WAAU,8BACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAU;AAAA,UACV,OAAO;AAAA,YACL,OAAO,GAAG,gBAAgB;AAAA,YAC1B,QAAQ,GAAG,uBAAuB;AAAA,YAClC,WAAW,cAAc,OAAO,IAAI,gBAAgB;AAAA,UAAA;AAAA,QACtD;AAAA,MAAA;AAAA,MAGF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,gBAAc;AAAA,UACd,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,WAAW,YAAY,IAAI;AAAA,UAC3B,OAAO,EAAE,OAAO,GAAG,gBAAgB,OAAO,QAAQ,GAAG,uBAAuB,MAAA;AAAA,UAE3E,YAAE,mBAAmB;AAAA,QAAA;AAAA,MAAA;AAAA,MAGxB;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,gBAAc,CAAC;AAAA,UACf,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,WAAW,YAAY,CAAC,IAAI;AAAA,UAC5B,OAAO,EAAE,OAAO,GAAG,gBAAgB,OAAO,QAAQ,GAAG,uBAAuB,MAAA;AAAA,UAE3E,YAAE,mBAAmB;AAAA,QAAA;AAAA,MAAA;AAAA,IACxB,EAAA,CACF;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
|
|
@@ -28,6 +28,7 @@ const SidebarFavoriteToggle = ({ favoriteKey }) => {
|
|
|
28
28
|
return /* @__PURE__ */ jsx(ControlledTooltip, { content: t(favorite ? "sidebar.favorites.remove" : "sidebar.favorites.add"), placement: "right", children: /* @__PURE__ */ jsx(
|
|
29
29
|
ControlledButton,
|
|
30
30
|
{
|
|
31
|
+
"aria-label": t(favorite ? "sidebar.favorites.remove" : "sidebar.favorites.add"),
|
|
31
32
|
defaultHtmlButton: true,
|
|
32
33
|
onClick: () => toggleFavorite(favoriteKey),
|
|
33
34
|
"aria-pressed": favorite,
|
|
@@ -52,10 +53,17 @@ const SidebarMenuRow = ({
|
|
|
52
53
|
const { location, Link: NavigationLink } = useUiNavigation();
|
|
53
54
|
const { canFavorite } = useSidebarFavorites();
|
|
54
55
|
const contentRef = useRef(null);
|
|
56
|
+
const submenuRef = useRef(null);
|
|
55
57
|
const [maxHeight, setMaxHeight] = useState("0px");
|
|
56
58
|
const hasChildren = Boolean(item.children?.length);
|
|
57
59
|
const isActive = hasChildren ? containsActiveRoute(item, location) : matchesRoute(location, item.to, item.end);
|
|
58
60
|
const submenuOpen = isOpen && !collapsed;
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const submenu = submenuRef.current;
|
|
63
|
+
if (!submenu) return;
|
|
64
|
+
if (submenuOpen) submenu.removeAttribute("inert");
|
|
65
|
+
else submenu.setAttribute("inert", "");
|
|
66
|
+
}, [submenuOpen]);
|
|
59
67
|
useEffect(() => {
|
|
60
68
|
const content = contentRef.current;
|
|
61
69
|
if (!content) return;
|
|
@@ -115,7 +123,7 @@ const SidebarMenuRow = ({
|
|
|
115
123
|
}
|
|
116
124
|
)
|
|
117
125
|
] }),
|
|
118
|
-
/* @__PURE__ */ jsx("div", { style: { maxHeight }, className: classNames("sidebar-submenu", { "mt-1": submenuOpen }), children: /* @__PURE__ */ jsx("div", { ref: contentRef, className: "pl-5", children: /* @__PURE__ */ jsx(
|
|
126
|
+
/* @__PURE__ */ jsx("div", { ref: submenuRef, style: { maxHeight }, className: classNames("sidebar-submenu", { "mt-1": submenuOpen }), children: /* @__PURE__ */ jsx("div", { ref: contentRef, className: "pl-5", children: /* @__PURE__ */ jsx(
|
|
119
127
|
SidebarMenu,
|
|
120
128
|
{
|
|
121
129
|
items: item.children ?? [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarMenu.js","sources":["../../../src/components/Shell/SidebarMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, MaterialSymbolWeight, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nimport { resolveSidebarFavoriteKey, useSidebarFavorites } from './sidebarFavorites';\n\nexport type TUiSidebarSection = 'favorites' | 'management' | 'registration';\n\nexport interface ISidebarMenuItem {\n id?: string;\n weight?: MaterialSymbolWeight;\n icon: SymbolCodepoints;\n text: string;\n to: string;\n externalUrl?: string;\n end?: boolean;\n section?: TUiSidebarSection;\n children?: Array<ISidebarMenuItem>;\n}\n\nexport const resolveSidebarItemId = (item: ISidebarMenuItem): string => item.id ?? `${item.to}:${item.text}`;\n\ninterface ISidebarLocation {\n pathname: string;\n search: string;\n}\n\ninterface ISidebarMenuProps {\n items: Array<ISidebarMenuItem>;\n activeClass: string;\n collapsed: boolean;\n activeIconClass?: string;\n onNavigate?: () => void;\n level?: number;\n}\n\nconst matchesRoute = (location: ISidebarLocation, target: string, end = false): boolean => {\n if (!target || target === '#') return false;\n\n if (target.includes('?')) {\n const [pathPart, queryPart] = target.split('?');\n const samePath = end\n ? location.pathname === pathPart\n : location.pathname === pathPart || location.pathname.startsWith(`${pathPart}/`);\n if (!samePath) return false;\n\n const currentParams = new URLSearchParams(location.search);\n const expectedParams = new URLSearchParams(queryPart);\n\n return Array.from(expectedParams.entries()).every(([key, value]) => currentParams.get(key) === value);\n }\n\n return end\n ? location.pathname === target\n : location.pathname === target || location.pathname.startsWith(`${target}/`);\n};\n\nconst containsActiveRoute = (item: ISidebarMenuItem, location: ISidebarLocation): boolean =>\n item.children?.length\n ? item.children.some((child) => containsActiveRoute(child, location))\n : matchesRoute(location, item.to, item.end);\n\ninterface ISidebarMenuRowProps {\n item: ISidebarMenuItem;\n isOpen: boolean;\n collapsed: boolean;\n onToggle: () => void;\n activeClass: string;\n activeIconClass?: string;\n onNavigate?: () => void;\n level: number;\n}\n\ninterface ISidebarFavoriteToggleProps {\n favoriteKey: string;\n}\n\nconst SidebarFavoriteToggle: React.FC<ISidebarFavoriteToggleProps> = ({ favoriteKey }) => {\n const { t } = useTranslation('ui');\n const { isFavorite, toggleFavorite } = useSidebarFavorites();\n const favorite = isFavorite(favoriteKey);\n\n return (\n <ControlledTooltip content={t(favorite ? 'sidebar.favorites.remove' : 'sidebar.favorites.add')} placement=\"right\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => toggleFavorite(favoriteKey)}\n aria-pressed={favorite}\n className={classNames(\n 'absolute right-1 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md transition-interactive duration-fast hover:bg-muted-active focus-visible:opacity-100 group-hover/row:opacity-100 [@media(hover:none)]:opacity-100',\n favorite ? 'text-warning-emphasis opacity-0' : 'text-foreground-subtle opacity-0 hover:text-foreground',\n )}\n >\n <MaterialSymbol icon=\"star\" size={16} fill={favorite} />\n </ControlledButton>\n </ControlledTooltip>\n );\n};\n\nconst SidebarMenuRow: React.FC<ISidebarMenuRowProps> = ({\n item,\n isOpen,\n collapsed,\n onToggle,\n activeClass,\n activeIconClass,\n onNavigate,\n level,\n}) => {\n const { location, Link: NavigationLink } = useUiNavigation();\n const { canFavorite } = useSidebarFavorites();\n const contentRef = useRef<HTMLDivElement>(null);\n const [maxHeight, setMaxHeight] = useState('0px');\n\n const hasChildren = Boolean(item.children?.length);\n const isActive = hasChildren ? containsActiveRoute(item, location) : matchesRoute(location, item.to, item.end);\n const submenuOpen = isOpen && !collapsed;\n\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return;\n\n if (!submenuOpen) {\n setMaxHeight('0px');\n return;\n }\n\n const updateHeight = () => setMaxHeight(`${content.scrollHeight}px`);\n updateHeight();\n\n const observer = new ResizeObserver(updateHeight);\n observer.observe(content);\n\n return () => observer.disconnect();\n }, [submenuOpen]);\n\n const rowClassName = classNames(\n 'relative flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left transition-colors duration-base',\n isActive ? activeClass : 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n );\n\n const iconClassName = classNames(\n 'shrink-0',\n isActive ? (activeIconClass ?? 'text-brand-emphasis') : 'text-foreground-muted',\n );\n\n const activeBar = isActive ? (\n <span aria-hidden className=\"absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 rounded-full bg-brand\" />\n ) : null;\n\n const icon = <MaterialSymbol icon={item.icon} size={20} weight={item.weight || 400} className={iconClassName} />;\n const label = <span className={classNames('sidebar-label text-sm', { 'is-collapsed': collapsed })}>{item.text}</span>;\n\n if (item.externalUrl) {\n return (\n <li className=\"flex flex-col\">\n <a\n href={item.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={rowClassName}\n onClick={onNavigate}\n >\n {activeBar}\n {icon}\n {label}\n </a>\n </li>\n );\n }\n\n if (hasChildren) {\n return (\n <li className=\"flex flex-col\">\n <ControlledButton defaultHtmlButton onClick={onToggle} aria-expanded={submenuOpen} className={rowClassName}>\n {activeBar}\n {icon}\n {label}\n <MaterialSymbol\n icon=\"chevron_right\"\n size={18}\n className={classNames('sidebar-chevron ml-auto shrink-0 text-foreground-subtle', {\n 'is-collapsed': collapsed,\n 'is-open': submenuOpen,\n })}\n />\n </ControlledButton>\n\n <div style={{ maxHeight }} className={classNames('sidebar-submenu', { 'mt-1': submenuOpen })}>\n <div ref={contentRef} className=\"pl-5\">\n <SidebarMenu\n items={item.children ?? []}\n collapsed={collapsed}\n activeClass={activeClass}\n activeIconClass={activeIconClass}\n onNavigate={onNavigate}\n level={level + 1}\n />\n </div>\n </div>\n </li>\n );\n }\n\n const favoriteKey = canFavorite && !collapsed ? resolveSidebarFavoriteKey(item) : null;\n\n return (\n <li className=\"group/row relative flex flex-col\">\n <NavigationLink to={item.to} className={classNames(rowClassName, { 'pr-9': favoriteKey })} onClick={onNavigate}>\n {activeBar}\n {icon}\n {label}\n </NavigationLink>\n {favoriteKey && <SidebarFavoriteToggle favoriteKey={favoriteKey} />}\n </li>\n );\n};\n\nconst SECTION_LABEL_KEYS: Record<TUiSidebarSection, string> = {\n favorites: 'sidebar.sections.favorites',\n management: 'sidebar.sections.management',\n registration: 'sidebar.sections.registration',\n};\n\ninterface ISidebarSectionHeaderProps {\n section: TUiSidebarSection;\n collapsed: boolean;\n withDivider: boolean;\n}\n\nconst SidebarSectionHeader: React.FC<ISidebarSectionHeaderProps> = ({ section, collapsed, withDivider }) => {\n const { t } = useTranslation('ui');\n\n if (collapsed) {\n if (!withDivider) return null;\n return (\n <li aria-hidden className=\"flex flex-col px-3\">\n <span className=\"border-t border-border/70\" />\n </li>\n );\n }\n\n return (\n <li className={classNames('flex flex-col', withDivider ? 'mt-2' : 'mt-1')}>\n {withDivider && <span aria-hidden className=\"mx-3 mb-2 border-t border-border/70\" />}\n <span className=\"px-3 pb-0.5 text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {t(SECTION_LABEL_KEYS[section])}\n </span>\n </li>\n );\n};\n\nconst SECTION_ORDER: Array<TUiSidebarSection> = ['favorites', 'management', 'registration'];\n\ninterface ISidebarMenuGroup {\n section: TUiSidebarSection | null;\n items: Array<ISidebarMenuItem>;\n}\n\nconst groupItemsBySection = (items: Array<ISidebarMenuItem>): Array<ISidebarMenuGroup> => {\n const itemsWithoutSection = (items ?? []).filter((item) => !item.section);\n const sectionGroups = SECTION_ORDER.map((section) => ({\n section,\n items: (items ?? []).filter((item) => item.section === section),\n })).filter((group) => group.items.length > 0);\n\n return itemsWithoutSection.length ? [{ section: null, items: itemsWithoutSection }, ...sectionGroups] : sectionGroups;\n};\n\nconst SidebarMenu: React.FC<ISidebarMenuProps> = ({\n items,\n activeClass,\n collapsed,\n activeIconClass,\n onNavigate,\n level = 0,\n}) => {\n const { location } = useUiNavigation();\n\n const activeKey =\n items\n .filter((item) => Boolean(item.children?.length) && containsActiveRoute(item, location))\n .map(resolveSidebarItemId)[0] ?? null;\n\n const [openKey, setOpenKey] = useState<string | null>(activeKey);\n const previousActiveKey = useRef(activeKey);\n\n useEffect(() => {\n if (activeKey && activeKey !== previousActiveKey.current) {\n setOpenKey(activeKey);\n }\n previousActiveKey.current = activeKey;\n }, [activeKey]);\n\n const handleToggle = (key: string) => {\n setOpenKey((current) => (current === key ? null : key));\n };\n\n const showSections = level === 0;\n const groups = showSections ? groupItemsBySection(items) : [{ section: null, items: items ?? [] }];\n\n return (\n <ul className={classNames('flex flex-col', level === 0 ? 'gap-y-2' : 'gap-y-1')}>\n {groups.flatMap((group, groupIndex) => {\n const rows: Array<React.ReactElement> = [];\n\n if (group.section) {\n rows.push(\n <SidebarSectionHeader\n key={`section-${group.section}`}\n section={group.section}\n collapsed={collapsed}\n withDivider={groupIndex > 0}\n />,\n );\n }\n\n group.items.forEach((item) => {\n const itemId = resolveSidebarItemId(item);\n\n rows.push(\n <SidebarMenuRow\n key={itemId}\n item={item}\n isOpen={openKey === itemId}\n collapsed={collapsed}\n onToggle={() => handleToggle(itemId)}\n activeClass={activeClass}\n activeIconClass={activeIconClass}\n onNavigate={onNavigate}\n level={level}\n />,\n );\n });\n\n return rows;\n })}\n </ul>\n );\n};\n\nexport default SidebarMenu;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SidebarMenu.js","sources":["../../../src/components/Shell/SidebarMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, MaterialSymbolWeight, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nimport { resolveSidebarFavoriteKey, useSidebarFavorites } from './sidebarFavorites';\n\nexport type TUiSidebarSection = 'favorites' | 'management' | 'registration';\n\nexport interface ISidebarMenuItem {\n id?: string;\n weight?: MaterialSymbolWeight;\n icon: SymbolCodepoints;\n text: string;\n to: string;\n externalUrl?: string;\n end?: boolean;\n section?: TUiSidebarSection;\n children?: Array<ISidebarMenuItem>;\n}\n\nexport const resolveSidebarItemId = (item: ISidebarMenuItem): string => item.id ?? `${item.to}:${item.text}`;\n\ninterface ISidebarLocation {\n pathname: string;\n search: string;\n}\n\ninterface ISidebarMenuProps {\n items: Array<ISidebarMenuItem>;\n activeClass: string;\n collapsed: boolean;\n activeIconClass?: string;\n onNavigate?: () => void;\n level?: number;\n}\n\nconst matchesRoute = (location: ISidebarLocation, target: string, end = false): boolean => {\n if (!target || target === '#') return false;\n\n if (target.includes('?')) {\n const [pathPart, queryPart] = target.split('?');\n const samePath = end\n ? location.pathname === pathPart\n : location.pathname === pathPart || location.pathname.startsWith(`${pathPart}/`);\n if (!samePath) return false;\n\n const currentParams = new URLSearchParams(location.search);\n const expectedParams = new URLSearchParams(queryPart);\n\n return Array.from(expectedParams.entries()).every(([key, value]) => currentParams.get(key) === value);\n }\n\n return end\n ? location.pathname === target\n : location.pathname === target || location.pathname.startsWith(`${target}/`);\n};\n\nconst containsActiveRoute = (item: ISidebarMenuItem, location: ISidebarLocation): boolean =>\n item.children?.length\n ? item.children.some((child) => containsActiveRoute(child, location))\n : matchesRoute(location, item.to, item.end);\n\ninterface ISidebarMenuRowProps {\n item: ISidebarMenuItem;\n isOpen: boolean;\n collapsed: boolean;\n onToggle: () => void;\n activeClass: string;\n activeIconClass?: string;\n onNavigate?: () => void;\n level: number;\n}\n\ninterface ISidebarFavoriteToggleProps {\n favoriteKey: string;\n}\n\nconst SidebarFavoriteToggle: React.FC<ISidebarFavoriteToggleProps> = ({ favoriteKey }) => {\n const { t } = useTranslation('ui');\n const { isFavorite, toggleFavorite } = useSidebarFavorites();\n const favorite = isFavorite(favoriteKey);\n\n return (\n <ControlledTooltip content={t(favorite ? 'sidebar.favorites.remove' : 'sidebar.favorites.add')} placement=\"right\">\n <ControlledButton\n aria-label={t(favorite ? 'sidebar.favorites.remove' : 'sidebar.favorites.add')}\n defaultHtmlButton\n onClick={() => toggleFavorite(favoriteKey)}\n aria-pressed={favorite}\n className={classNames(\n 'absolute right-1 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md transition-interactive duration-fast hover:bg-muted-active focus-visible:opacity-100 group-hover/row:opacity-100 [@media(hover:none)]:opacity-100',\n favorite ? 'text-warning-emphasis opacity-0' : 'text-foreground-subtle opacity-0 hover:text-foreground',\n )}\n >\n <MaterialSymbol icon=\"star\" size={16} fill={favorite} />\n </ControlledButton>\n </ControlledTooltip>\n );\n};\n\nconst SidebarMenuRow: React.FC<ISidebarMenuRowProps> = ({\n item,\n isOpen,\n collapsed,\n onToggle,\n activeClass,\n activeIconClass,\n onNavigate,\n level,\n}) => {\n const { location, Link: NavigationLink } = useUiNavigation();\n const { canFavorite } = useSidebarFavorites();\n const contentRef = useRef<HTMLDivElement>(null);\n const submenuRef = useRef<HTMLDivElement>(null);\n const [maxHeight, setMaxHeight] = useState('0px');\n\n const hasChildren = Boolean(item.children?.length);\n const isActive = hasChildren ? containsActiveRoute(item, location) : matchesRoute(location, item.to, item.end);\n const submenuOpen = isOpen && !collapsed;\n\n /* `max-height: 0` hides the links but leaves them in the Tab order, so the keyboard lands on rows\n nobody can see. Set through the DOM because React 18 does not type the attribute. */\n useEffect(() => {\n const submenu = submenuRef.current;\n if (!submenu) return;\n if (submenuOpen) submenu.removeAttribute('inert');\n else submenu.setAttribute('inert', '');\n }, [submenuOpen]);\n\n useEffect(() => {\n const content = contentRef.current;\n if (!content) return;\n\n if (!submenuOpen) {\n setMaxHeight('0px');\n return;\n }\n\n const updateHeight = () => setMaxHeight(`${content.scrollHeight}px`);\n updateHeight();\n\n const observer = new ResizeObserver(updateHeight);\n observer.observe(content);\n\n return () => observer.disconnect();\n }, [submenuOpen]);\n\n const rowClassName = classNames(\n 'relative flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left transition-colors duration-base',\n isActive ? activeClass : 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n );\n\n const iconClassName = classNames(\n 'shrink-0',\n isActive ? (activeIconClass ?? 'text-brand-emphasis') : 'text-foreground-muted',\n );\n\n const activeBar = isActive ? (\n <span aria-hidden className=\"absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 rounded-full bg-brand\" />\n ) : null;\n\n const icon = <MaterialSymbol icon={item.icon} size={20} weight={item.weight || 400} className={iconClassName} />;\n const label = <span className={classNames('sidebar-label text-sm', { 'is-collapsed': collapsed })}>{item.text}</span>;\n\n if (item.externalUrl) {\n return (\n <li className=\"flex flex-col\">\n <a\n href={item.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={rowClassName}\n onClick={onNavigate}\n >\n {activeBar}\n {icon}\n {label}\n </a>\n </li>\n );\n }\n\n if (hasChildren) {\n return (\n <li className=\"flex flex-col\">\n <ControlledButton defaultHtmlButton onClick={onToggle} aria-expanded={submenuOpen} className={rowClassName}>\n {activeBar}\n {icon}\n {label}\n <MaterialSymbol\n icon=\"chevron_right\"\n size={18}\n className={classNames('sidebar-chevron ml-auto shrink-0 text-foreground-subtle', {\n 'is-collapsed': collapsed,\n 'is-open': submenuOpen,\n })}\n />\n </ControlledButton>\n\n <div ref={submenuRef} style={{ maxHeight }} className={classNames('sidebar-submenu', { 'mt-1': submenuOpen })}>\n <div ref={contentRef} className=\"pl-5\">\n <SidebarMenu\n items={item.children ?? []}\n collapsed={collapsed}\n activeClass={activeClass}\n activeIconClass={activeIconClass}\n onNavigate={onNavigate}\n level={level + 1}\n />\n </div>\n </div>\n </li>\n );\n }\n\n const favoriteKey = canFavorite && !collapsed ? resolveSidebarFavoriteKey(item) : null;\n\n return (\n <li className=\"group/row relative flex flex-col\">\n <NavigationLink to={item.to} className={classNames(rowClassName, { 'pr-9': favoriteKey })} onClick={onNavigate}>\n {activeBar}\n {icon}\n {label}\n </NavigationLink>\n {favoriteKey && <SidebarFavoriteToggle favoriteKey={favoriteKey} />}\n </li>\n );\n};\n\nconst SECTION_LABEL_KEYS: Record<TUiSidebarSection, string> = {\n favorites: 'sidebar.sections.favorites',\n management: 'sidebar.sections.management',\n registration: 'sidebar.sections.registration',\n};\n\ninterface ISidebarSectionHeaderProps {\n section: TUiSidebarSection;\n collapsed: boolean;\n withDivider: boolean;\n}\n\nconst SidebarSectionHeader: React.FC<ISidebarSectionHeaderProps> = ({ section, collapsed, withDivider }) => {\n const { t } = useTranslation('ui');\n\n if (collapsed) {\n if (!withDivider) return null;\n return (\n <li aria-hidden className=\"flex flex-col px-3\">\n <span className=\"border-t border-border/70\" />\n </li>\n );\n }\n\n return (\n <li className={classNames('flex flex-col', withDivider ? 'mt-2' : 'mt-1')}>\n {withDivider && <span aria-hidden className=\"mx-3 mb-2 border-t border-border/70\" />}\n <span className=\"px-3 pb-0.5 text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {t(SECTION_LABEL_KEYS[section])}\n </span>\n </li>\n );\n};\n\nconst SECTION_ORDER: Array<TUiSidebarSection> = ['favorites', 'management', 'registration'];\n\ninterface ISidebarMenuGroup {\n section: TUiSidebarSection | null;\n items: Array<ISidebarMenuItem>;\n}\n\nconst groupItemsBySection = (items: Array<ISidebarMenuItem>): Array<ISidebarMenuGroup> => {\n const itemsWithoutSection = (items ?? []).filter((item) => !item.section);\n const sectionGroups = SECTION_ORDER.map((section) => ({\n section,\n items: (items ?? []).filter((item) => item.section === section),\n })).filter((group) => group.items.length > 0);\n\n return itemsWithoutSection.length ? [{ section: null, items: itemsWithoutSection }, ...sectionGroups] : sectionGroups;\n};\n\nconst SidebarMenu: React.FC<ISidebarMenuProps> = ({\n items,\n activeClass,\n collapsed,\n activeIconClass,\n onNavigate,\n level = 0,\n}) => {\n const { location } = useUiNavigation();\n\n const activeKey =\n items\n .filter((item) => Boolean(item.children?.length) && containsActiveRoute(item, location))\n .map(resolveSidebarItemId)[0] ?? null;\n\n const [openKey, setOpenKey] = useState<string | null>(activeKey);\n const previousActiveKey = useRef(activeKey);\n\n useEffect(() => {\n if (activeKey && activeKey !== previousActiveKey.current) {\n setOpenKey(activeKey);\n }\n previousActiveKey.current = activeKey;\n }, [activeKey]);\n\n const handleToggle = (key: string) => {\n setOpenKey((current) => (current === key ? null : key));\n };\n\n const showSections = level === 0;\n const groups = showSections ? groupItemsBySection(items) : [{ section: null, items: items ?? [] }];\n\n return (\n <ul className={classNames('flex flex-col', level === 0 ? 'gap-y-2' : 'gap-y-1')}>\n {groups.flatMap((group, groupIndex) => {\n const rows: Array<React.ReactElement> = [];\n\n if (group.section) {\n rows.push(\n <SidebarSectionHeader\n key={`section-${group.section}`}\n section={group.section}\n collapsed={collapsed}\n withDivider={groupIndex > 0}\n />,\n );\n }\n\n group.items.forEach((item) => {\n const itemId = resolveSidebarItemId(item);\n\n rows.push(\n <SidebarMenuRow\n key={itemId}\n item={item}\n isOpen={openKey === itemId}\n collapsed={collapsed}\n onToggle={() => handleToggle(itemId)}\n activeClass={activeClass}\n activeIconClass={activeIconClass}\n onNavigate={onNavigate}\n level={level}\n />,\n );\n });\n\n return rows;\n })}\n </ul>\n );\n};\n\nexport default SidebarMenu;\n"],"names":[],"mappings":"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"}
|
|
@@ -47,12 +47,13 @@ const ThemeToggle = ({
|
|
|
47
47
|
return /* @__PURE__ */ jsx(ControlledTooltip, { content: t(presentation.labelKey), children: /* @__PURE__ */ jsx(
|
|
48
48
|
ControlledButton,
|
|
49
49
|
{
|
|
50
|
+
"aria-label": t(presentation.labelKey),
|
|
50
51
|
defaultHtmlButton: true,
|
|
51
52
|
"aria-pressed": isActive,
|
|
52
53
|
onClick: () => onChange(option),
|
|
53
54
|
className: classNames(
|
|
54
55
|
"relative z-10 flex shrink-0 items-center justify-center rounded-full leading-none transition-colors duration-base",
|
|
55
|
-
isActive ? "text-brand-emphasis" : "text-foreground-
|
|
56
|
+
isActive ? "text-brand-emphasis" : "text-foreground-subtle hover:text-foreground-muted"
|
|
56
57
|
),
|
|
57
58
|
style: { width: `${OPTION_SIZE_REM}rem`, height: `${OPTION_SIZE_REM}rem` },
|
|
58
59
|
children: presentation.renderIcon(isActive)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeToggle.js","sources":["../../../src/components/Shell/ThemeToggle.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { TUiThemeSkin } from '@/helpers/themeSkins';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\nimport PumpkinGlyph from './PumpkinGlyph';\n\nconst OPTION_SIZE_REM = HEADER_CONTROL_SIZE_REM;\nconst OPTION_ICON_SIZE = 15;\n\nexport type TUiThemePreference = 'light' | 'system' | 'dark';\n\n/** A skin is one more position of the knob: choosing it is choosing its scheme, re-coloured. */\nexport type TUiThemeToggleOption = TUiThemePreference | TUiThemeSkin;\n\ninterface IOptionPresentation {\n labelKey: string;\n renderIcon: (isActive: boolean) => ReactNode;\n}\n\nconst renderSymbol = (icon: SymbolCodepoints) => (isActive: boolean) => (\n <MaterialSymbol icon={icon} size={OPTION_ICON_SIZE} fill={isActive} />\n);\n\nconst OPTION_PRESENTATION: Record<TUiThemeToggleOption, IOptionPresentation> = {\n light: { labelKey: 'theme.light', renderIcon: renderSymbol('light_mode') },\n system: { labelKey: 'theme.system', renderIcon: renderSymbol('computer') },\n dark: { labelKey: 'theme.dark', renderIcon: renderSymbol('dark_mode') },\n halloween: {\n labelKey: 'theme.halloween',\n renderIcon: (isActive) => <PumpkinGlyph size={OPTION_ICON_SIZE} filled={isActive} />,\n },\n};\n\nconst DEFAULT_OPTIONS: ReadonlyArray<TUiThemePreference> = ['light', 'system', 'dark'];\n\ninterface IThemeToggleProps<TOption extends TUiThemeToggleOption = TUiThemePreference> {\n preference: TOption;\n onChange: (preference: TOption) => void;\n /** Positions of the knob, in order. Defaults to light, system and dark; the host adds a\n skin for as long as it is on offer and drops it again when the season ends. */\n options?: ReadonlyArray<TOption>;\n}\n\n/**\n * The theme control: an explicit light/dark choice, `system` to follow the OS, and any skin\n * the host is offering. The host owns the preference (persistence + writing `data-theme` and\n * `data-theme-skin` on <html>); this renders the knob.\n */\nconst ThemeToggle = <TOption extends TUiThemeToggleOption = TUiThemePreference>({\n preference,\n onChange,\n options,\n}: IThemeToggleProps<TOption>) => {\n const { t } = useTranslation('ui');\n\n const requestedOptions = (\n Array.isArray(options) && options.length > 0 ? options : DEFAULT_OPTIONS\n ) as ReadonlyArray<TOption>;\n const visibleOptions = requestedOptions.filter((option) => Boolean(OPTION_PRESENTATION[option]));\n const activeIndex = visibleOptions.indexOf(preference);\n\n return (\n <div role=\"group\" aria-label={t('theme.toggle')} className={HEADER_CONTROL_SHELL_CLASS}>\n <div className=\"relative flex items-center\">\n {activeIndex >= 0 && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter\"\n style={{\n width: `${OPTION_SIZE_REM}rem`,\n height: `${OPTION_SIZE_REM}rem`,\n transform: `translateX(${activeIndex * OPTION_SIZE_REM}rem)`,\n }}\n />\n )}\n\n {visibleOptions.map((option) => {\n const isActive = option === preference;\n const presentation = OPTION_PRESENTATION[option];\n\n return (\n <ControlledTooltip key={option} content={t(presentation.labelKey)}>\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isActive}\n onClick={() => onChange(option)}\n className={classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full leading-none transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-
|
|
1
|
+
{"version":3,"file":"ThemeToggle.js","sources":["../../../src/components/Shell/ThemeToggle.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { TUiThemeSkin } from '@/helpers/themeSkins';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\nimport PumpkinGlyph from './PumpkinGlyph';\n\nconst OPTION_SIZE_REM = HEADER_CONTROL_SIZE_REM;\nconst OPTION_ICON_SIZE = 15;\n\nexport type TUiThemePreference = 'light' | 'system' | 'dark';\n\n/** A skin is one more position of the knob: choosing it is choosing its scheme, re-coloured. */\nexport type TUiThemeToggleOption = TUiThemePreference | TUiThemeSkin;\n\ninterface IOptionPresentation {\n labelKey: string;\n renderIcon: (isActive: boolean) => ReactNode;\n}\n\nconst renderSymbol = (icon: SymbolCodepoints) => (isActive: boolean) => (\n <MaterialSymbol icon={icon} size={OPTION_ICON_SIZE} fill={isActive} />\n);\n\nconst OPTION_PRESENTATION: Record<TUiThemeToggleOption, IOptionPresentation> = {\n light: { labelKey: 'theme.light', renderIcon: renderSymbol('light_mode') },\n system: { labelKey: 'theme.system', renderIcon: renderSymbol('computer') },\n dark: { labelKey: 'theme.dark', renderIcon: renderSymbol('dark_mode') },\n halloween: {\n labelKey: 'theme.halloween',\n renderIcon: (isActive) => <PumpkinGlyph size={OPTION_ICON_SIZE} filled={isActive} />,\n },\n};\n\nconst DEFAULT_OPTIONS: ReadonlyArray<TUiThemePreference> = ['light', 'system', 'dark'];\n\ninterface IThemeToggleProps<TOption extends TUiThemeToggleOption = TUiThemePreference> {\n preference: TOption;\n onChange: (preference: TOption) => void;\n /** Positions of the knob, in order. Defaults to light, system and dark; the host adds a\n skin for as long as it is on offer and drops it again when the season ends. */\n options?: ReadonlyArray<TOption>;\n}\n\n/**\n * The theme control: an explicit light/dark choice, `system` to follow the OS, and any skin\n * the host is offering. The host owns the preference (persistence + writing `data-theme` and\n * `data-theme-skin` on <html>); this renders the knob.\n */\nconst ThemeToggle = <TOption extends TUiThemeToggleOption = TUiThemePreference>({\n preference,\n onChange,\n options,\n}: IThemeToggleProps<TOption>) => {\n const { t } = useTranslation('ui');\n\n const requestedOptions = (\n Array.isArray(options) && options.length > 0 ? options : DEFAULT_OPTIONS\n ) as ReadonlyArray<TOption>;\n const visibleOptions = requestedOptions.filter((option) => Boolean(OPTION_PRESENTATION[option]));\n const activeIndex = visibleOptions.indexOf(preference);\n\n return (\n <div role=\"group\" aria-label={t('theme.toggle')} className={HEADER_CONTROL_SHELL_CLASS}>\n <div className=\"relative flex items-center\">\n {activeIndex >= 0 && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter\"\n style={{\n width: `${OPTION_SIZE_REM}rem`,\n height: `${OPTION_SIZE_REM}rem`,\n transform: `translateX(${activeIndex * OPTION_SIZE_REM}rem)`,\n }}\n />\n )}\n\n {visibleOptions.map((option) => {\n const isActive = option === preference;\n const presentation = OPTION_PRESENTATION[option];\n\n return (\n <ControlledTooltip key={option} content={t(presentation.labelKey)}>\n <ControlledButton\n aria-label={t(presentation.labelKey)}\n defaultHtmlButton\n aria-pressed={isActive}\n onClick={() => onChange(option)}\n className={classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full leading-none transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-subtle hover:text-foreground-muted',\n )}\n style={{ width: `${OPTION_SIZE_REM}rem`, height: `${OPTION_SIZE_REM}rem` }}\n >\n {presentation.renderIcon(isActive)}\n </ControlledButton>\n </ControlledTooltip>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default ThemeToggle;\n"],"names":[],"mappings":";;;;;;;;AAYA,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAYzB,MAAM,eAAe,CAAC,SAA2B,CAAC,aAChD,oBAAC,gBAAA,EAAe,MAAY,MAAM,kBAAkB,MAAM,SAAA,CAAU;AAGtE,MAAM,sBAAyE;AAAA,EAC7E,OAAO,EAAE,UAAU,eAAe,YAAY,aAAa,YAAY,EAAA;AAAA,EACvE,QAAQ,EAAE,UAAU,gBAAgB,YAAY,aAAa,UAAU,EAAA;AAAA,EACvE,MAAM,EAAE,UAAU,cAAc,YAAY,aAAa,WAAW,EAAA;AAAA,EACpE,WAAW;AAAA,IACT,UAAU;AAAA,IACV,YAAY,CAAC,aAAa,oBAAC,gBAAa,MAAM,kBAAkB,QAAQ,SAAA,CAAU;AAAA,EAAA;AAEtF;AAEA,MAAM,kBAAqD,CAAC,SAAS,UAAU,MAAM;AAerF,MAAM,cAAc,CAA4D;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AACF,MAAkC;AAChC,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,mBACJ,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,IAAI,UAAU;AAE3D,QAAM,iBAAiB,iBAAiB,OAAO,CAAC,WAAW,QAAQ,oBAAoB,MAAM,CAAC,CAAC;AAC/F,QAAM,cAAc,eAAe,QAAQ,UAAU;AAErD,SACE,oBAAC,OAAA,EAAI,MAAK,SAAQ,cAAY,EAAE,cAAc,GAAG,WAAW,4BAC1D,UAAA,qBAAC,OAAA,EAAI,WAAU,8BACZ,UAAA;AAAA,IAAA,eAAe,KACd;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA,QACV,OAAO;AAAA,UACL,OAAO,GAAG,eAAe;AAAA,UACzB,QAAQ,GAAG,eAAe;AAAA,UAC1B,WAAW,cAAc,cAAc,eAAe;AAAA,QAAA;AAAA,MACxD;AAAA,IAAA;AAAA,IAIH,eAAe,IAAI,CAAC,WAAW;AAC9B,YAAM,WAAW,WAAW;AAC5B,YAAM,eAAe,oBAAoB,MAAM;AAE/C,iCACG,mBAAA,EAA+B,SAAS,EAAE,aAAa,QAAQ,GAC9D,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,cAAY,EAAE,aAAa,QAAQ;AAAA,UACnC,mBAAiB;AAAA,UACjB,gBAAc;AAAA,UACd,SAAS,MAAM,SAAS,MAAM;AAAA,UAC9B,WAAW;AAAA,YACT;AAAA,YACA,WAAW,wBAAwB;AAAA,UAAA;AAAA,UAErC,OAAO,EAAE,OAAO,GAAG,eAAe,OAAO,QAAQ,GAAG,eAAe,MAAA;AAAA,UAElE,UAAA,aAAa,WAAW,QAAQ;AAAA,QAAA;AAAA,MAAA,KAZb,MAcxB;AAAA,IAEJ,CAAC;AAAA,EAAA,EAAA,CACH,EAAA,CACF;AAEJ;"}
|
|
@@ -11,6 +11,8 @@ interface IBaseDataCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
11
11
|
iconSize?: number;
|
|
12
12
|
itsClickable?: boolean;
|
|
13
13
|
actionIcon?: SymbolCodepoints;
|
|
14
|
+
/** Accessible name of the icon-only action; defaults to edit or view details by the icon. */
|
|
15
|
+
actionLabel?: string;
|
|
14
16
|
actionHref?: string;
|
|
15
17
|
actionButton?: () => void;
|
|
16
18
|
onAction?: () => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
4
5
|
import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
|
|
5
6
|
import ControlledTooltip from "./ControlledTooltip.js";
|
|
6
7
|
import { MaterialSymbol } from "./MaterialSymbol/index.js";
|
|
@@ -17,6 +18,7 @@ const BaseDataCard = ({
|
|
|
17
18
|
iconTooltip,
|
|
18
19
|
itsClickable = false,
|
|
19
20
|
actionIcon = "edit",
|
|
21
|
+
actionLabel,
|
|
20
22
|
actionHref,
|
|
21
23
|
className,
|
|
22
24
|
actionButton,
|
|
@@ -29,7 +31,9 @@ const BaseDataCard = ({
|
|
|
29
31
|
onSecondarySlot,
|
|
30
32
|
infoTooltip
|
|
31
33
|
}) => {
|
|
34
|
+
const { t } = useTranslation("ui");
|
|
32
35
|
const { Link: NavigationLink } = useUiNavigation();
|
|
36
|
+
const resolvedActionLabel = actionLabel ?? t(actionIcon === "edit" ? "actions.edit" : "actions.viewDetails");
|
|
33
37
|
const [isArrowUpward, setIsArrowUpward] = useState(false);
|
|
34
38
|
const cardRef = useRef(null);
|
|
35
39
|
const handleActionClick = () => {
|
|
@@ -103,6 +107,7 @@ const BaseDataCard = ({
|
|
|
103
107
|
actionHref && /* @__PURE__ */ jsx(NavigationLink, { to: actionHref, children: /* @__PURE__ */ jsx(
|
|
104
108
|
ControlledButton,
|
|
105
109
|
{
|
|
110
|
+
"aria-label": resolvedActionLabel,
|
|
106
111
|
defaultHtmlButton: true,
|
|
107
112
|
onClick: onAction,
|
|
108
113
|
className: "flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted",
|
|
@@ -112,6 +117,7 @@ const BaseDataCard = ({
|
|
|
112
117
|
onAction && /* @__PURE__ */ jsx(
|
|
113
118
|
ControlledButton,
|
|
114
119
|
{
|
|
120
|
+
"aria-label": resolvedActionLabel,
|
|
115
121
|
defaultHtmlButton: true,
|
|
116
122
|
onClick: onAction,
|
|
117
123
|
className: "flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseDataCard.js","sources":["../../../src/components/UI/BaseDataCard.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { uiLogger } from '@/helpers/uiLogger';\n\nimport ControlledButton from '../Form/ControlledButton';\nimport SkeletonLoading from '../UI/SkeletonLoading';\n\nimport CustomCard from './CustomCard';\ninterface IBaseDataCardProps extends React.HTMLAttributes<HTMLDivElement> {\n title?: string;\n smallTitle?: boolean;\n titleIcon?: SymbolCodepoints | null;\n titleClass?: string;\n cardIcon?: SymbolCodepoints;\n iconTooltip?: string;\n iconColor?: string;\n iconSize?: number;\n itsClickable?: boolean;\n actionIcon?: SymbolCodepoints;\n actionHref?: string;\n actionButton?: () => void;\n onAction?: () => void;\n actionButtonLabel?: string;\n showActionButton?: boolean;\n useScroll?: boolean;\n loading?: boolean;\n onSecondarySlot?: React.ReactNode;\n infoTooltip?: string;\n}\n\nconst BaseDataCard: React.FC<IBaseDataCardProps> = ({\n title,\n smallTitle,\n titleIcon,\n titleClass,\n cardIcon,\n iconTooltip,\n itsClickable = false,\n actionIcon = 'edit',\n actionHref,\n className,\n actionButton,\n actionButtonLabel,\n showActionButton = false,\n onAction,\n useScroll = false,\n loading = false,\n children,\n onSecondarySlot,\n infoTooltip,\n}) => {\n const { Link: NavigationLink } = useUiNavigation();\n const [isArrowUpward, setIsArrowUpward] = useState(false);\n const cardRef = useRef<HTMLDivElement>(null);\n\n const handleActionClick = () => {\n const newArrowUpwardState = !isArrowUpward;\n setIsArrowUpward(newArrowUpwardState);\n if (actionButton) {\n actionButton();\n }\n if (!newArrowUpwardState && cardRef.current && useScroll) {\n setTimeout(() => {\n uiLogger.debug('Scrolling into view');\n cardRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }, 1);\n }\n };\n\n return (\n <CustomCard\n className={classNames('overflow-y-hidden p-5 transition-colors', {\n [`${className}`]: !!className,\n 'h-[30rem]': useScroll,\n ['hover:transition-custom hover:ease-custom cursor-pointer hover:bg-success-soft hover:duration-slow']:\n itsClickable,\n })}\n customRef={cardRef}\n >\n <div className=\"flex h-full flex-col justify-between\">\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex w-full items-center justify-between\">\n {loading ? (\n <SkeletonLoading className=\"!h-4\" />\n ) : (\n <div className=\"flex items-center gap-x-1.5\">\n {titleIcon &&\n (iconTooltip ? (\n <ControlledTooltip content={iconTooltip} className=\"max-w-80\">\n <MaterialSymbol\n icon={titleIcon}\n size={smallTitle ? 18 : 20}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"!hidden desktop:!block\"\n />\n </ControlledTooltip>\n ) : (\n <MaterialSymbol\n icon={titleIcon}\n size={smallTitle ? 18 : 20}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"!hidden desktop:!block\"\n />\n ))}\n\n {title && (\n <span\n className={classNames(\n 'font-semibold text-foreground',\n {\n 'text-xs': smallTitle,\n 'text-lg': !smallTitle,\n },\n titleClass,\n )}\n >\n {title}\n </span>\n )}\n\n {infoTooltip && (\n <ControlledTooltip content={infoTooltip} className=\"max-w-80\">\n <MaterialSymbol\n icon=\"info\"\n size={smallTitle ? 16 : 18}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"ml-1 cursor-help\"\n />\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n\n <div>\n {loading ? (\n <SkeletonLoading type=\"circle\" className=\"ml-3\" />\n ) : (\n <div className=\"flex items-center space-x-2\">\n {actionHref && (\n <NavigationLink to={actionHref}>\n <ControlledButton\n defaultHtmlButton\n onClick={onAction}\n className=\"flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted\"\n >\n <MaterialSymbol color=\"rgb(var(--color-primary))\" icon={actionIcon} />\n </ControlledButton>\n </NavigationLink>\n )}\n\n {onAction && (\n <ControlledButton\n defaultHtmlButton\n onClick={onAction}\n className=\"flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted\"\n >\n <MaterialSymbol color=\"rgb(var(--color-primary))\" icon={actionIcon} />\n </ControlledButton>\n )}\n\n {onSecondarySlot && <>{onSecondarySlot}</>}\n\n {cardIcon && <MaterialSymbol icon={cardIcon} size={25} color=\"rgb(var(--color-primary))\" />}\n </div>\n )}\n </div>\n </div>\n\n <div\n className={classNames('m-auto', {\n 'w-[90%]': useScroll,\n 'w-full': !useScroll,\n })}\n >\n {children}\n </div>\n </div>\n\n {showActionButton && actionButtonLabel && (\n <div className=\"flex h-20 w-full items-center justify-center rounded-md bg-gradient-to-t from-surface to-transparent\">\n <ControlledButton\n className=\"flex w-full max-w-full items-center justify-center gap-x-3 rounded-full px-4 py-2 text-sm font-semibold capitalize text-white tablet:w-60\"\n onClick={handleActionClick}\n >\n <MaterialSymbol\n icon=\"arrow_downward\"\n size={20}\n color=\"white\"\n className={classNames('transition-transform duration-slow', {\n 'rotate-180': isArrowUpward,\n })}\n />\n\n {actionButtonLabel}\n </ControlledButton>\n </div>\n )}\n </CustomCard>\n );\n};\n\nexport default BaseDataCard;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BaseDataCard.js","sources":["../../../src/components/UI/BaseDataCard.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { uiLogger } from '@/helpers/uiLogger';\n\nimport ControlledButton from '../Form/ControlledButton';\nimport SkeletonLoading from '../UI/SkeletonLoading';\n\nimport CustomCard from './CustomCard';\ninterface IBaseDataCardProps extends React.HTMLAttributes<HTMLDivElement> {\n title?: string;\n smallTitle?: boolean;\n titleIcon?: SymbolCodepoints | null;\n titleClass?: string;\n cardIcon?: SymbolCodepoints;\n iconTooltip?: string;\n iconColor?: string;\n iconSize?: number;\n itsClickable?: boolean;\n actionIcon?: SymbolCodepoints;\n /** Accessible name of the icon-only action; defaults to edit or view details by the icon. */\n actionLabel?: string;\n actionHref?: string;\n actionButton?: () => void;\n onAction?: () => void;\n actionButtonLabel?: string;\n showActionButton?: boolean;\n useScroll?: boolean;\n loading?: boolean;\n onSecondarySlot?: React.ReactNode;\n infoTooltip?: string;\n}\n\nconst BaseDataCard: React.FC<IBaseDataCardProps> = ({\n title,\n smallTitle,\n titleIcon,\n titleClass,\n cardIcon,\n iconTooltip,\n itsClickable = false,\n actionIcon = 'edit',\n actionLabel,\n actionHref,\n className,\n actionButton,\n actionButtonLabel,\n showActionButton = false,\n onAction,\n useScroll = false,\n loading = false,\n children,\n onSecondarySlot,\n infoTooltip,\n}) => {\n const { t } = useTranslation('ui');\n const { Link: NavigationLink } = useUiNavigation();\n const resolvedActionLabel = actionLabel ?? t(actionIcon === 'edit' ? 'actions.edit' : 'actions.viewDetails');\n const [isArrowUpward, setIsArrowUpward] = useState(false);\n const cardRef = useRef<HTMLDivElement>(null);\n\n const handleActionClick = () => {\n const newArrowUpwardState = !isArrowUpward;\n setIsArrowUpward(newArrowUpwardState);\n if (actionButton) {\n actionButton();\n }\n if (!newArrowUpwardState && cardRef.current && useScroll) {\n setTimeout(() => {\n uiLogger.debug('Scrolling into view');\n cardRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }, 1);\n }\n };\n\n return (\n <CustomCard\n className={classNames('overflow-y-hidden p-5 transition-colors', {\n [`${className}`]: !!className,\n 'h-[30rem]': useScroll,\n ['hover:transition-custom hover:ease-custom cursor-pointer hover:bg-success-soft hover:duration-slow']:\n itsClickable,\n })}\n customRef={cardRef}\n >\n <div className=\"flex h-full flex-col justify-between\">\n <div className=\"mb-3 flex items-center justify-between\">\n <div className=\"flex w-full items-center justify-between\">\n {loading ? (\n <SkeletonLoading className=\"!h-4\" />\n ) : (\n <div className=\"flex items-center gap-x-1.5\">\n {titleIcon &&\n (iconTooltip ? (\n <ControlledTooltip content={iconTooltip} className=\"max-w-80\">\n <MaterialSymbol\n icon={titleIcon}\n size={smallTitle ? 18 : 20}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"!hidden desktop:!block\"\n />\n </ControlledTooltip>\n ) : (\n <MaterialSymbol\n icon={titleIcon}\n size={smallTitle ? 18 : 20}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"!hidden desktop:!block\"\n />\n ))}\n\n {title && (\n <span\n className={classNames(\n 'font-semibold text-foreground',\n {\n 'text-xs': smallTitle,\n 'text-lg': !smallTitle,\n },\n titleClass,\n )}\n >\n {title}\n </span>\n )}\n\n {infoTooltip && (\n <ControlledTooltip content={infoTooltip} className=\"max-w-80\">\n <MaterialSymbol\n icon=\"info\"\n size={smallTitle ? 16 : 18}\n color=\"rgb(var(--color-foreground-subtle))\"\n className=\"ml-1 cursor-help\"\n />\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n\n <div>\n {loading ? (\n <SkeletonLoading type=\"circle\" className=\"ml-3\" />\n ) : (\n <div className=\"flex items-center space-x-2\">\n {actionHref && (\n <NavigationLink to={actionHref}>\n <ControlledButton\n aria-label={resolvedActionLabel}\n defaultHtmlButton\n onClick={onAction}\n className=\"flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted\"\n >\n <MaterialSymbol color=\"rgb(var(--color-primary))\" icon={actionIcon} />\n </ControlledButton>\n </NavigationLink>\n )}\n\n {onAction && (\n <ControlledButton\n aria-label={resolvedActionLabel}\n defaultHtmlButton\n onClick={onAction}\n className=\"flex size-9 items-center justify-center rounded-full transition-colors hover:bg-muted\"\n >\n <MaterialSymbol color=\"rgb(var(--color-primary))\" icon={actionIcon} />\n </ControlledButton>\n )}\n\n {onSecondarySlot && <>{onSecondarySlot}</>}\n\n {cardIcon && <MaterialSymbol icon={cardIcon} size={25} color=\"rgb(var(--color-primary))\" />}\n </div>\n )}\n </div>\n </div>\n\n <div\n className={classNames('m-auto', {\n 'w-[90%]': useScroll,\n 'w-full': !useScroll,\n })}\n >\n {children}\n </div>\n </div>\n\n {showActionButton && actionButtonLabel && (\n <div className=\"flex h-20 w-full items-center justify-center rounded-md bg-gradient-to-t from-surface to-transparent\">\n <ControlledButton\n className=\"flex w-full max-w-full items-center justify-center gap-x-3 rounded-full px-4 py-2 text-sm font-semibold capitalize text-white tablet:w-60\"\n onClick={handleActionClick}\n >\n <MaterialSymbol\n icon=\"arrow_downward\"\n size={20}\n color=\"white\"\n className={classNames('transition-transform duration-slow', {\n 'rotate-180': isArrowUpward,\n })}\n />\n\n {actionButtonLabel}\n </ControlledButton>\n </div>\n )}\n </CustomCard>\n );\n};\n\nexport default BaseDataCard;\n"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,7 @@ const BulkActionBar = ({
|
|
|
68
68
|
selectAll && /* @__PURE__ */ jsx(ControlledTooltip, { content: selectAll.tooltip, children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
|
|
69
69
|
ControlledButton,
|
|
70
70
|
{
|
|
71
|
+
"aria-label": selectAll.tooltip,
|
|
71
72
|
defaultHtmlButton: true,
|
|
72
73
|
disabled: selectAll.disabled,
|
|
73
74
|
onClick: selectAll.onToggle,
|
|
@@ -108,6 +109,7 @@ const BulkActionBar = ({
|
|
|
108
109
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: closeTooltip, children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
|
|
109
110
|
ControlledButton,
|
|
110
111
|
{
|
|
112
|
+
"aria-label": closeTooltip,
|
|
111
113
|
defaultHtmlButton: true,
|
|
112
114
|
onClick: onClose,
|
|
113
115
|
className: "flex size-9 items-center justify-center rounded-lg text-foreground-subtle transition-colors hover:bg-muted-hover",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkActionBar.js","sources":["../../../src/components/UI/BulkActionBar.tsx"],"sourcesContent":["import classNames from 'classnames';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport OutlinedChip from '@/components/UI/Chips/OutlinedChip';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport HorizontalScrollArea from '@/components/UI/HorizontalScrollArea';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nexport type TBulkActionTone = 'primary' | 'neutral' | 'danger';\n\nexport interface IBulkAction {\n key: string;\n label: string;\n icon: SymbolCodepoints;\n onClick: () => void;\n tone?: TBulkActionTone;\n disabled?: boolean;\n /** Explica o bloqueio quando a ação está desabilitada. */\n tooltip?: string;\n}\n\nexport interface IBulkActionBarSelectAll {\n allSelected: boolean;\n tooltip: string;\n onToggle: () => void;\n disabled?: boolean;\n}\n\ninterface IBulkActionBarProps {\n count: number;\n countLabel: string;\n /** Texto exibido quando nada está selecionado (a barra continua visível guiando o usuário). */\n hintLabel?: string;\n selectAll?: IBulkActionBarSelectAll;\n actions: Array<IBulkAction>;\n closeTooltip: string;\n onClose: () => void;\n className?: string;\n}\n\nconst TONE_CLASSES: Record<TBulkActionTone, { enabled: string; disabled: string }> = {\n primary: {\n enabled: 'bg-brand text-brand-fg hover:bg-brand-hover',\n disabled: 'cursor-not-allowed bg-muted-hover text-foreground-faint',\n },\n neutral: {\n enabled: 'border border-border text-foreground-muted hover:bg-muted-hover',\n disabled: 'cursor-not-allowed border border-border text-foreground-faint',\n },\n danger: {\n enabled: 'border border-danger-border text-danger-emphasis hover:bg-danger-soft',\n disabled: 'cursor-not-allowed border border-border text-foreground-faint',\n },\n};\n\n/**\n * Barra de ações em lote compartilhada (seleção do canvas do criAI, seleção do mapa de produtos).\n * As ações são declarativas para que cada superfície acrescente as suas sem forkar o visual.\n */\nconst BulkActionBar: React.FC<IBulkActionBarProps> = ({\n count,\n countLabel,\n hintLabel,\n selectAll,\n actions,\n closeTooltip,\n onClose,\n className,\n}) => {\n const renderAction = (action: IBulkAction) => {\n const tone = TONE_CLASSES[action.tone ?? 'primary'];\n\n const button = (\n <ControlledButton\n defaultHtmlButton\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex h-9 shrink-0 items-center gap-x-1.5 rounded-lg px-3 text-sm font-medium transition-colors',\n action.disabled ? tone.disabled : tone.enabled,\n )}\n >\n <MaterialSymbol icon={action.icon} size={17} />\n {action.label}\n </ControlledButton>\n );\n\n if (action.disabled && action.tooltip) {\n return (\n <ControlledTooltip key={action.key} content={action.tooltip} className=\"w-52 text-center\">\n <span className=\"inline-flex\">{button}</span>\n </ControlledTooltip>\n );\n }\n\n return (\n <span key={action.key} className=\"inline-flex\">\n {button}\n </span>\n );\n };\n\n return (\n // The bar is capped to the viewport, so on a narrow screen its actions used to\n // be clipped past the cap with no way to reach them. It now scrolls inside that\n // cap: every action stays reachable and the bar keeps its single-line shape.\n <HorizontalScrollArea\n containerClassName={classNames(\n 'pointer-events-auto max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-surface shadow-lg',\n className,\n )}\n className=\"py-1.5 pl-2 pr-1.5\"\n >\n <div className=\"flex w-max items-center gap-2 whitespace-nowrap\">\n {selectAll && (\n <ControlledTooltip content={selectAll.tooltip}>\n <span className=\"inline-flex\">\n <ControlledButton\n defaultHtmlButton\n disabled={selectAll.disabled}\n onClick={selectAll.onToggle}\n className=\"flex size-9 shrink-0 items-center justify-center rounded-lg text-foreground-muted transition-colors hover:bg-muted-hover disabled:hover:bg-transparent\"\n >\n <span\n className={classNames(\n 'flex size-5 items-center justify-center rounded-md border-2 transition-colors',\n selectAll.allSelected\n ? 'border-brand bg-brand text-brand-fg'\n : 'border-border-emphasis bg-surface text-transparent',\n selectAll.disabled && 'border-border',\n )}\n >\n <MaterialSymbol icon=\"check\" size={14} />\n </span>\n </ControlledButton>\n </span>\n </ControlledTooltip>\n )}\n\n <span className=\"flex min-w-0 items-center gap-2 pr-1 text-sm text-foreground-muted\">\n {count > 0 ? (\n <>\n <OutlinedChip\n count={count}\n tone=\"purple\"\n variant=\"soft\"\n size=\"sm\"\n className=\"size-6 shrink-0 bg-brand-soft px-0\"\n labelClassName=\"font-bold text-brand-emphasis\"\n />\n <span className=\"truncate\">{countLabel}</span>\n </>\n ) : (\n <span className=\"flex min-w-0 items-center gap-1.5 text-foreground-subtle\">\n <MaterialSymbol icon=\"touch_app\" size={16} className=\"shrink-0\" />\n <span className=\"truncate\">{hintLabel ?? countLabel}</span>\n </span>\n )}\n </span>\n\n <div className=\"h-5 w-px shrink-0 bg-muted-active\" />\n\n {actions.map(renderAction)}\n\n <div className=\"h-5 w-px shrink-0 bg-muted-active\" />\n\n <ControlledTooltip content={closeTooltip}>\n <span className=\"inline-flex\">\n <ControlledButton\n defaultHtmlButton\n onClick={onClose}\n className=\"flex size-9 items-center justify-center rounded-lg text-foreground-subtle transition-colors hover:bg-muted-hover\"\n >\n <MaterialSymbol icon=\"close\" size={18} />\n </ControlledButton>\n </span>\n </ControlledTooltip>\n </div>\n </HorizontalScrollArea>\n );\n};\n\nexport default BulkActionBar;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BulkActionBar.js","sources":["../../../src/components/UI/BulkActionBar.tsx"],"sourcesContent":["import classNames from 'classnames';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport OutlinedChip from '@/components/UI/Chips/OutlinedChip';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport HorizontalScrollArea from '@/components/UI/HorizontalScrollArea';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nexport type TBulkActionTone = 'primary' | 'neutral' | 'danger';\n\nexport interface IBulkAction {\n key: string;\n label: string;\n icon: SymbolCodepoints;\n onClick: () => void;\n tone?: TBulkActionTone;\n disabled?: boolean;\n /** Explica o bloqueio quando a ação está desabilitada. */\n tooltip?: string;\n}\n\nexport interface IBulkActionBarSelectAll {\n allSelected: boolean;\n tooltip: string;\n onToggle: () => void;\n disabled?: boolean;\n}\n\ninterface IBulkActionBarProps {\n count: number;\n countLabel: string;\n /** Texto exibido quando nada está selecionado (a barra continua visível guiando o usuário). */\n hintLabel?: string;\n selectAll?: IBulkActionBarSelectAll;\n actions: Array<IBulkAction>;\n closeTooltip: string;\n onClose: () => void;\n className?: string;\n}\n\nconst TONE_CLASSES: Record<TBulkActionTone, { enabled: string; disabled: string }> = {\n primary: {\n enabled: 'bg-brand text-brand-fg hover:bg-brand-hover',\n disabled: 'cursor-not-allowed bg-muted-hover text-foreground-faint',\n },\n neutral: {\n enabled: 'border border-border text-foreground-muted hover:bg-muted-hover',\n disabled: 'cursor-not-allowed border border-border text-foreground-faint',\n },\n danger: {\n enabled: 'border border-danger-border text-danger-emphasis hover:bg-danger-soft',\n disabled: 'cursor-not-allowed border border-border text-foreground-faint',\n },\n};\n\n/**\n * Barra de ações em lote compartilhada (seleção do canvas do criAI, seleção do mapa de produtos).\n * As ações são declarativas para que cada superfície acrescente as suas sem forkar o visual.\n */\nconst BulkActionBar: React.FC<IBulkActionBarProps> = ({\n count,\n countLabel,\n hintLabel,\n selectAll,\n actions,\n closeTooltip,\n onClose,\n className,\n}) => {\n const renderAction = (action: IBulkAction) => {\n const tone = TONE_CLASSES[action.tone ?? 'primary'];\n\n const button = (\n <ControlledButton\n defaultHtmlButton\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex h-9 shrink-0 items-center gap-x-1.5 rounded-lg px-3 text-sm font-medium transition-colors',\n action.disabled ? tone.disabled : tone.enabled,\n )}\n >\n <MaterialSymbol icon={action.icon} size={17} />\n {action.label}\n </ControlledButton>\n );\n\n if (action.disabled && action.tooltip) {\n return (\n <ControlledTooltip key={action.key} content={action.tooltip} className=\"w-52 text-center\">\n <span className=\"inline-flex\">{button}</span>\n </ControlledTooltip>\n );\n }\n\n return (\n <span key={action.key} className=\"inline-flex\">\n {button}\n </span>\n );\n };\n\n return (\n // The bar is capped to the viewport, so on a narrow screen its actions used to\n // be clipped past the cap with no way to reach them. It now scrolls inside that\n // cap: every action stays reachable and the bar keeps its single-line shape.\n <HorizontalScrollArea\n containerClassName={classNames(\n 'pointer-events-auto max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-surface shadow-lg',\n className,\n )}\n className=\"py-1.5 pl-2 pr-1.5\"\n >\n <div className=\"flex w-max items-center gap-2 whitespace-nowrap\">\n {selectAll && (\n <ControlledTooltip content={selectAll.tooltip}>\n <span className=\"inline-flex\">\n <ControlledButton\n aria-label={selectAll.tooltip}\n defaultHtmlButton\n disabled={selectAll.disabled}\n onClick={selectAll.onToggle}\n className=\"flex size-9 shrink-0 items-center justify-center rounded-lg text-foreground-muted transition-colors hover:bg-muted-hover disabled:hover:bg-transparent\"\n >\n <span\n className={classNames(\n 'flex size-5 items-center justify-center rounded-md border-2 transition-colors',\n selectAll.allSelected\n ? 'border-brand bg-brand text-brand-fg'\n : 'border-border-emphasis bg-surface text-transparent',\n selectAll.disabled && 'border-border',\n )}\n >\n <MaterialSymbol icon=\"check\" size={14} />\n </span>\n </ControlledButton>\n </span>\n </ControlledTooltip>\n )}\n\n <span className=\"flex min-w-0 items-center gap-2 pr-1 text-sm text-foreground-muted\">\n {count > 0 ? (\n <>\n <OutlinedChip\n count={count}\n tone=\"purple\"\n variant=\"soft\"\n size=\"sm\"\n className=\"size-6 shrink-0 bg-brand-soft px-0\"\n labelClassName=\"font-bold text-brand-emphasis\"\n />\n <span className=\"truncate\">{countLabel}</span>\n </>\n ) : (\n <span className=\"flex min-w-0 items-center gap-1.5 text-foreground-subtle\">\n <MaterialSymbol icon=\"touch_app\" size={16} className=\"shrink-0\" />\n <span className=\"truncate\">{hintLabel ?? countLabel}</span>\n </span>\n )}\n </span>\n\n <div className=\"h-5 w-px shrink-0 bg-muted-active\" />\n\n {actions.map(renderAction)}\n\n <div className=\"h-5 w-px shrink-0 bg-muted-active\" />\n\n <ControlledTooltip content={closeTooltip}>\n <span className=\"inline-flex\">\n <ControlledButton\n aria-label={closeTooltip}\n defaultHtmlButton\n onClick={onClose}\n className=\"flex size-9 items-center justify-center rounded-lg text-foreground-subtle transition-colors hover:bg-muted-hover\"\n >\n <MaterialSymbol icon=\"close\" size={18} />\n </ControlledButton>\n </span>\n </ControlledTooltip>\n </div>\n </HorizontalScrollArea>\n );\n};\n\nexport default BulkActionBar;\n"],"names":[],"mappings":"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"}
|
|
@@ -2,11 +2,11 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { useState } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
5
|
-
import { twMerge } from "tailwind-merge";
|
|
6
5
|
import ControlledButton from "../../Form/ControlledButton.js";
|
|
7
6
|
import { ControlledMenuHandler, ControlledMenuRoot, ControlledMenuList, ControlledMenuItem } from "../ControlledMenu.js";
|
|
8
7
|
import ControlledTooltip from "../ControlledTooltip.js";
|
|
9
8
|
import LoadingSpinner from "../LoadingSpinner.js";
|
|
9
|
+
import { mergeClasses } from "../../../helpers/mergeClasses.js";
|
|
10
10
|
import { capitalizeAllLetters } from "../../../helpers/stringHelpers.js";
|
|
11
11
|
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
12
12
|
import { resolveChipToneClass, resolveChipDotClass } from "./chipTones.js";
|
|
@@ -72,7 +72,7 @@ const OutlinedChip = ({
|
|
|
72
72
|
"div",
|
|
73
73
|
{
|
|
74
74
|
onClick: (event) => event.stopPropagation(),
|
|
75
|
-
className:
|
|
75
|
+
className: mergeClasses(
|
|
76
76
|
classNames(
|
|
77
77
|
"flex cursor-pointer items-center justify-center gap-x-1 truncate rounded-full font-semibold transition-opacity hover:opacity-80",
|
|
78
78
|
CHIP_PADDING_CLASS[size],
|
|
@@ -95,7 +95,7 @@ const OutlinedChip = ({
|
|
|
95
95
|
/* @__PURE__ */ jsx(
|
|
96
96
|
ControlledMenuList,
|
|
97
97
|
{
|
|
98
|
-
className:
|
|
98
|
+
className: mergeClasses("zIndexMax max-h-72 min-w-[12rem] overflow-y-auto", dropdownMenuClassName),
|
|
99
99
|
onClick: (event) => {
|
|
100
100
|
event.stopPropagation();
|
|
101
101
|
},
|
|
@@ -131,7 +131,7 @@ const OutlinedChip = ({
|
|
|
131
131
|
)
|
|
132
132
|
] });
|
|
133
133
|
}
|
|
134
|
-
const containerClass =
|
|
134
|
+
const containerClass = mergeClasses(
|
|
135
135
|
classNames(
|
|
136
136
|
"flex items-center gap-x-1 rounded-full text-center",
|
|
137
137
|
CHIP_PADDING_CLASS[size],
|
|
@@ -149,13 +149,20 @@ const OutlinedChip = ({
|
|
|
149
149
|
const chipInner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
150
|
leadingDot,
|
|
151
151
|
leadingIcon,
|
|
152
|
-
/* @__PURE__ */ jsx(
|
|
152
|
+
/* @__PURE__ */ jsx(
|
|
153
|
+
"span",
|
|
154
|
+
{
|
|
155
|
+
className: mergeClasses(classNames("font-semibold", CHIP_LABEL_SIZE_CLASS[size], { truncate }), labelClassName),
|
|
156
|
+
children: displayLabel
|
|
157
|
+
}
|
|
158
|
+
),
|
|
153
159
|
trailingIcon && /* @__PURE__ */ jsx(MaterialSymbol, { icon: trailingIcon, size: chipIconSize }),
|
|
154
160
|
canClose && onClose && /* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.remove"), children: /* @__PURE__ */ jsx(
|
|
155
161
|
MaterialSymbol,
|
|
156
162
|
{
|
|
157
163
|
icon: "close",
|
|
158
164
|
size: 16,
|
|
165
|
+
"aria-label": t("actions.remove"),
|
|
159
166
|
onClick: (event) => {
|
|
160
167
|
event.stopPropagation();
|
|
161
168
|
onClose(event);
|
|
@@ -175,7 +182,7 @@ const OutlinedChip = ({
|
|
|
175
182
|
}
|
|
176
183
|
) : /* @__PURE__ */ jsx("div", { className: containerClass, style: inlineStyle, children: chipInner });
|
|
177
184
|
if (!tooltip) return chip;
|
|
178
|
-
return /* @__PURE__ */ jsx(ControlledTooltip, { content: tooltip, children: /* @__PURE__ */ jsx("div", { className:
|
|
185
|
+
return /* @__PURE__ */ jsx(ControlledTooltip, { content: tooltip, children: /* @__PURE__ */ jsx("div", { className: mergeClasses("inline-flex", wrapperClassName), children: chip }) });
|
|
179
186
|
};
|
|
180
187
|
export {
|
|
181
188
|
OutlinedChip as default
|