@umodeapporg/ui 0.28.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/DataTableBulkEditRowsPicker.js +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/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
|
@@ -2,8 +2,8 @@ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useRef, useCallback, useEffect, isValidElement, cloneElement } from "react";
|
|
3
3
|
import { AnimatePresence, motion } from "motion/react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { twMerge } from "tailwind-merge";
|
|
6
5
|
import { composeRefs } from "../../helpers/composeRefs.js";
|
|
6
|
+
import { mergeClasses } from "../../helpers/mergeClasses.js";
|
|
7
7
|
import { getPortalHost } from "../../helpers/portalHost.js";
|
|
8
8
|
import { useAnchoredFloating } from "../../hooks/useAnchoredFloating.js";
|
|
9
9
|
import { tooltipVariants } from "../../motion/motionPresets.js";
|
|
@@ -101,6 +101,10 @@ const ControlledTooltip = ({
|
|
|
101
101
|
trigger.props.onPointerEnter?.(event);
|
|
102
102
|
if (!isControlledByHost) openTooltip();
|
|
103
103
|
},
|
|
104
|
+
onPointerDown: (event) => {
|
|
105
|
+
trigger.props.onPointerDown?.(event);
|
|
106
|
+
if (!isControlledByHost && opensOnHover) closeTooltip();
|
|
107
|
+
},
|
|
104
108
|
onMouseEnter: (event) => {
|
|
105
109
|
triggerRef.current = event.currentTarget;
|
|
106
110
|
pointerPositionRef.current = { x: event.clientX, y: event.clientY };
|
|
@@ -124,8 +128,8 @@ const ControlledTooltip = ({
|
|
|
124
128
|
animate: "visible",
|
|
125
129
|
exit: "exit",
|
|
126
130
|
role: "tooltip",
|
|
127
|
-
className:
|
|
128
|
-
"fixed left-0 top-0 z-[
|
|
131
|
+
className: mergeClasses(
|
|
132
|
+
"fixed left-0 top-0 z-[9999] max-w-xs rounded-lg bg-ink px-3 py-2 text-xs font-normal text-ink-fg shadow-lg",
|
|
129
133
|
className
|
|
130
134
|
),
|
|
131
135
|
children: content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlledTooltip.js","sources":["../../../src/components/UI/ControlledTooltip.tsx"],"sourcesContent":["import { cloneElement, isValidElement, useCallback, useEffect, useRef, useState } from 'react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport type { FocusEvent, MouseEvent, PointerEvent, ReactElement, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { twMerge } from 'tailwind-merge';\n\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { tooltipVariants } from '@/motion';\n\nexport interface IControlledTooltipProps {\n content: ReactNode;\n children: ReactNode;\n className?: string;\n placement?: TFloatingPlacement;\n offset?: number;\n /** Lifts the touch block: a click-to-open tooltip is a deliberate mobile pattern. */\n interactive?: boolean;\n open?: boolean;\n handler?: (open: boolean) => void;\n}\n\ntype TTooltipTriggerProps = {\n onPointerEnter?: (event: PointerEvent<HTMLElement>) => void;\n onMouseEnter?: (event: MouseEvent<HTMLElement>) => void;\n onFocus?: (event: FocusEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n};\n\nconst ORPHAN_TOOLTIP_CHECK_INTERVAL_MS = 250;\n\nconst isTouchFirstDevice = (): boolean => window.matchMedia('(hover: none), (pointer: coarse)').matches;\n\n/**\n * Owns the open state instead of delegating to the positioning library, because a raw\n * hover interaction opens on touch (the browser's compatibility `mouseenter`) and never\n * closes when the trigger stops receiving `mouseleave`: detached from the DOM, covered by\n * a dialog, or re-rendered. Both leave a bubble parked on the screen.\n */\nconst ControlledTooltip: React.FC<IControlledTooltipProps> = ({\n content,\n children,\n className,\n placement = 'top',\n offset = 6,\n interactive,\n open,\n handler,\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const triggerRef = useRef<HTMLElement | null>(null);\n const triggerPointerTypeRef = useRef<string | null>(null);\n const pointerPositionRef = useRef<{ x: number; y: number } | null>(null);\n\n const isControlledByHost = open !== undefined;\n const opensOnHover = !interactive;\n const resolvedOpen = isControlledByHost ? Boolean(open) : isOpen;\n\n const { setReference, setFloating } = useAnchoredFloating({\n open: resolvedOpen,\n placement,\n offsetPx: offset,\n constrainToViewport: false,\n });\n\n const closeTooltip = useCallback(() => {\n setIsOpen(false);\n handler?.(false);\n }, [handler]);\n\n const openTooltip = useCallback(() => {\n const pointerType = triggerPointerTypeRef.current;\n const openedByTouch = pointerType === null ? isTouchFirstDevice() : pointerType !== 'mouse';\n if (opensOnHover && openedByTouch) return;\n\n setIsOpen(true);\n handler?.(true);\n }, [handler, opensOnHover]);\n\n useEffect(() => {\n if (isControlledByHost || !resolvedOpen || !opensOnHover) return undefined;\n\n const belongsToTooltip = (element: Node | null): boolean => {\n const trigger = triggerRef.current;\n if (!element || !trigger) return false;\n if (trigger.contains(element)) return true;\n return element instanceof Element && element.closest('[role=\"tooltip\"]') !== null;\n };\n\n const closeWhenPointerLeftTrigger = (event: globalThis.PointerEvent) => {\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n if (!triggerRef.current) return;\n if (belongsToTooltip(event.target instanceof Node ? event.target : null)) return;\n closeTooltip();\n };\n\n const closeWhenPageIsHidden = () => {\n if (document.hidden) closeTooltip();\n };\n\n const orphanTooltipCheck = window.setInterval(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n if (!trigger.isConnected) {\n closeTooltip();\n return;\n }\n\n const pointerPosition = pointerPositionRef.current;\n if (!pointerPosition) return;\n if (!belongsToTooltip(document.elementFromPoint(pointerPosition.x, pointerPosition.y))) closeTooltip();\n }, ORPHAN_TOOLTIP_CHECK_INTERVAL_MS);\n\n document.addEventListener('pointermove', closeWhenPointerLeftTrigger, { capture: true, passive: true });\n document.addEventListener('pointerdown', closeTooltip, { capture: true, passive: true });\n document.addEventListener('visibilitychange', closeWhenPageIsHidden);\n window.addEventListener('blur', closeTooltip);\n\n return () => {\n window.clearInterval(orphanTooltipCheck);\n document.removeEventListener('pointermove', closeWhenPointerLeftTrigger, { capture: true });\n document.removeEventListener('pointerdown', closeTooltip, { capture: true });\n document.removeEventListener('visibilitychange', closeWhenPageIsHidden);\n window.removeEventListener('blur', closeTooltip);\n };\n }, [closeTooltip, isControlledByHost, resolvedOpen, opensOnHover]);\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const trigger = children as ReactElement<TTooltipTriggerProps>;\n\n const triggerWithTracking = cloneElement(trigger, {\n ref: composeRefs<HTMLElement>(\n (node) => {\n triggerRef.current = node;\n },\n setReference,\n (trigger as { ref?: React.Ref<HTMLElement> }).ref,\n ),\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n triggerPointerTypeRef.current = event.pointerType || null;\n triggerRef.current = event.currentTarget;\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n trigger.props.onPointerEnter?.(event);\n if (!isControlledByHost) openTooltip();\n },\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n triggerRef.current = event.currentTarget;\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n trigger.props.onMouseEnter?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n triggerRef.current = event.currentTarget;\n trigger.props.onFocus?.(event);\n },\n } as TTooltipTriggerProps);\n\n const portalHost = getPortalHost();\n\n return (\n <>\n {triggerWithTracking}\n\n {portalHost !== null &&\n createPortal(\n <AnimatePresence>\n {resolvedOpen && (\n <motion.div\n key=\"tooltip-bubble\"\n ref={setFloating}\n variants={tooltipVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"tooltip\"\n className={twMerge(\n 'fixed left-0 top-0 z-[999] max-w-xs rounded-lg bg-ink px-3 py-2 text-xs font-normal text-ink-fg shadow-lg',\n className,\n )}\n >\n {content}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n )}\n </>\n );\n};\n\nexport default ControlledTooltip;\n"],"names":["trigger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ControlledTooltip.js","sources":["../../../src/components/UI/ControlledTooltip.tsx"],"sourcesContent":["import { cloneElement, isValidElement, useCallback, useEffect, useRef, useState } from 'react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport type { FocusEvent, MouseEvent, PointerEvent, ReactElement, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { composeRefs } from '@/helpers/composeRefs';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { TFloatingPlacement, useAnchoredFloating } from '@/hooks/useAnchoredFloating';\nimport { tooltipVariants } from '@/motion';\n\nexport interface IControlledTooltipProps {\n content: ReactNode;\n children: ReactNode;\n className?: string;\n placement?: TFloatingPlacement;\n offset?: number;\n /** Lifts the touch block: a click-to-open tooltip is a deliberate mobile pattern. */\n interactive?: boolean;\n open?: boolean;\n handler?: (open: boolean) => void;\n}\n\ntype TTooltipTriggerProps = {\n onPointerEnter?: (event: PointerEvent<HTMLElement>) => void;\n onPointerDown?: (event: PointerEvent<HTMLElement>) => void;\n onMouseEnter?: (event: MouseEvent<HTMLElement>) => void;\n onFocus?: (event: FocusEvent<HTMLElement>) => void;\n ref?: React.Ref<HTMLElement>;\n};\n\nconst ORPHAN_TOOLTIP_CHECK_INTERVAL_MS = 250;\n\nconst isTouchFirstDevice = (): boolean => window.matchMedia('(hover: none), (pointer: coarse)').matches;\n\n/**\n * Owns the open state instead of delegating to the positioning library, because a raw\n * hover interaction opens on touch (the browser's compatibility `mouseenter`) and never\n * closes when the trigger stops receiving `mouseleave`: detached from the DOM, covered by\n * a dialog, or re-rendered. Both leave a bubble parked on the screen.\n */\nconst ControlledTooltip: React.FC<IControlledTooltipProps> = ({\n content,\n children,\n className,\n placement = 'top',\n offset = 6,\n interactive,\n open,\n handler,\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const triggerRef = useRef<HTMLElement | null>(null);\n const triggerPointerTypeRef = useRef<string | null>(null);\n const pointerPositionRef = useRef<{ x: number; y: number } | null>(null);\n\n const isControlledByHost = open !== undefined;\n const opensOnHover = !interactive;\n const resolvedOpen = isControlledByHost ? Boolean(open) : isOpen;\n\n const { setReference, setFloating } = useAnchoredFloating({\n open: resolvedOpen,\n placement,\n offsetPx: offset,\n constrainToViewport: false,\n });\n\n const closeTooltip = useCallback(() => {\n setIsOpen(false);\n handler?.(false);\n }, [handler]);\n\n const openTooltip = useCallback(() => {\n const pointerType = triggerPointerTypeRef.current;\n const openedByTouch = pointerType === null ? isTouchFirstDevice() : pointerType !== 'mouse';\n if (opensOnHover && openedByTouch) return;\n\n setIsOpen(true);\n handler?.(true);\n }, [handler, opensOnHover]);\n\n useEffect(() => {\n if (isControlledByHost || !resolvedOpen || !opensOnHover) return undefined;\n\n const belongsToTooltip = (element: Node | null): boolean => {\n const trigger = triggerRef.current;\n if (!element || !trigger) return false;\n if (trigger.contains(element)) return true;\n return element instanceof Element && element.closest('[role=\"tooltip\"]') !== null;\n };\n\n const closeWhenPointerLeftTrigger = (event: globalThis.PointerEvent) => {\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n if (!triggerRef.current) return;\n if (belongsToTooltip(event.target instanceof Node ? event.target : null)) return;\n closeTooltip();\n };\n\n const closeWhenPageIsHidden = () => {\n if (document.hidden) closeTooltip();\n };\n\n const orphanTooltipCheck = window.setInterval(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n if (!trigger.isConnected) {\n closeTooltip();\n return;\n }\n\n const pointerPosition = pointerPositionRef.current;\n if (!pointerPosition) return;\n if (!belongsToTooltip(document.elementFromPoint(pointerPosition.x, pointerPosition.y))) closeTooltip();\n }, ORPHAN_TOOLTIP_CHECK_INTERVAL_MS);\n\n document.addEventListener('pointermove', closeWhenPointerLeftTrigger, { capture: true, passive: true });\n document.addEventListener('pointerdown', closeTooltip, { capture: true, passive: true });\n document.addEventListener('visibilitychange', closeWhenPageIsHidden);\n window.addEventListener('blur', closeTooltip);\n\n return () => {\n window.clearInterval(orphanTooltipCheck);\n document.removeEventListener('pointermove', closeWhenPointerLeftTrigger, { capture: true });\n document.removeEventListener('pointerdown', closeTooltip, { capture: true });\n document.removeEventListener('visibilitychange', closeWhenPageIsHidden);\n window.removeEventListener('blur', closeTooltip);\n };\n }, [closeTooltip, isControlledByHost, resolvedOpen, opensOnHover]);\n\n if (!isValidElement(children)) return <>{children}</>;\n\n const trigger = children as ReactElement<TTooltipTriggerProps>;\n\n const triggerWithTracking = cloneElement(trigger, {\n ref: composeRefs<HTMLElement>(\n (node) => {\n triggerRef.current = node;\n },\n setReference,\n (trigger as { ref?: React.Ref<HTMLElement> }).ref,\n ),\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n triggerPointerTypeRef.current = event.pointerType || null;\n triggerRef.current = event.currentTarget;\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n trigger.props.onPointerEnter?.(event);\n if (!isControlledByHost) openTooltip();\n },\n onPointerDown: (event: PointerEvent<HTMLElement>) => {\n trigger.props.onPointerDown?.(event);\n // Not left to the document listener above, which only exists after the effect of the render\n // that opened the bubble: a press right behind the hover finds nobody listening, and the\n // bubble stays on the screen under whatever the press opened.\n if (!isControlledByHost && opensOnHover) closeTooltip();\n },\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n triggerRef.current = event.currentTarget;\n pointerPositionRef.current = { x: event.clientX, y: event.clientY };\n trigger.props.onMouseEnter?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n triggerRef.current = event.currentTarget;\n trigger.props.onFocus?.(event);\n },\n } as TTooltipTriggerProps);\n\n const portalHost = getPortalHost();\n\n return (\n <>\n {triggerWithTracking}\n\n {portalHost !== null &&\n createPortal(\n <AnimatePresence>\n {resolvedOpen && (\n <motion.div\n key=\"tooltip-bubble\"\n ref={setFloating}\n variants={tooltipVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"tooltip\"\n className={mergeClasses(\n 'fixed left-0 top-0 z-[9999] max-w-xs rounded-lg bg-ink px-3 py-2 text-xs font-normal text-ink-fg shadow-lg',\n className,\n )}\n >\n {content}\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n )}\n </>\n );\n};\n\nexport default ControlledTooltip;\n"],"names":["trigger"],"mappings":"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"}
|
|
@@ -11,10 +11,10 @@ import { buildEmptyFilterValues, emptyValueForKind } from "../../Search/filters/
|
|
|
11
11
|
import "date-fns";
|
|
12
12
|
import "date-fns/locale";
|
|
13
13
|
import "react-day-picker";
|
|
14
|
-
import "tailwind-merge";
|
|
15
14
|
import "../ControlledPopover.js";
|
|
16
15
|
import "motion/react";
|
|
17
16
|
import "react-dom";
|
|
17
|
+
import "../../../helpers/mergeClasses.js";
|
|
18
18
|
import "@floating-ui/dom";
|
|
19
19
|
import "../../../motion/motionPresets.js";
|
|
20
20
|
import "../../../motion/motionTokens.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment as Fragment$1 } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
4
5
|
import ControlledButton from "../../Form/ControlledButton.js";
|
|
5
6
|
import LoadingSpinner from "../LoadingSpinner.js";
|
|
6
7
|
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
@@ -32,6 +33,7 @@ const DataTableBody = ({
|
|
|
32
33
|
bodyTopRow,
|
|
33
34
|
bodyTopRowColSpan
|
|
34
35
|
}) => {
|
|
36
|
+
const { t } = useTranslation("ui");
|
|
35
37
|
const expansion = config.expansion;
|
|
36
38
|
const panelExpansionConfig = expansion && isDataTablePanelExpansion(expansion) ? expansion : null;
|
|
37
39
|
const subRowsExpansionConfig = expansion && isDataTableSubRowsExpansion(expansion) ? expansion : null;
|
|
@@ -70,6 +72,8 @@ const DataTableBody = ({
|
|
|
70
72
|
return /* @__PURE__ */ jsx(
|
|
71
73
|
ControlledButton,
|
|
72
74
|
{
|
|
75
|
+
"aria-label": t(row.getIsExpanded() ? "dataTable.collapseRow" : "dataTable.expandRow"),
|
|
76
|
+
"aria-expanded": row.getIsExpanded(),
|
|
73
77
|
defaultHtmlButton: true,
|
|
74
78
|
onClick: (event) => {
|
|
75
79
|
event.stopPropagation();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTableBody.js","sources":["../../../../src/components/UI/DataTable/DataTableBody.tsx"],"sourcesContent":["import React, { Fragment } from 'react';\nimport classNames from 'classnames';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { flexRender, type Row } from '@tanstack/react-table';\n\nimport { GRID_COLUMN_DATA_ATTR, GRID_ROW_DATA_ATTR, type IGridCellCoord } from './grid/gridTypes';\nimport DataTable from './DataTable';\nimport DataTableBodyCell from './DataTableBodyCell';\nimport { alignClass, FROZEN_COLUMN_SHADOW, FROZEN_LEFT_COLUMN_SHADOW } from './dataTableClasses';\nimport { resolveColumnMinWidthPx } from './dataTableColumnWidths';\nimport DataTableDetailPanel from './DataTableDetailPanel';\nimport { type IDataTableDetailColumnsState } from './DataTableDetailTable';\nimport { dataTableOpacityTransitionClass } from './dataTableMotion';\nimport {\n type IDataTableColumnConfig,\n type IDataTableConfig,\n type IDataTableRow,\n isDataTableDetailTableExpansion,\n isDataTablePanelExpansion,\n isDataTableSubRowsExpansion,\n isDataTableTableExpansion,\n} from './types';\n\ninterface IDataTableBodyProps<T extends IDataTableRow, C extends IDataTableRow = IDataTableRow> {\n config: IDataTableConfig<T, C>;\n visibleRows: Array<Row<T>>;\n columnConfigById: Map<string, IDataTableColumnConfig<T>>;\n /** Row-derived widths for actions/badge columns; other columns fall back to the seed. */\n columnContentWidths: Map<string, number>;\n registerRow: (id: string) => (element: HTMLElement | null) => void;\n onCollapseStart: (childRowIds: Array<string>) => void;\n detailColumnsState?: IDataTableDetailColumnsState;\n detailSlotWidth?: number;\n showActionsShadow?: boolean;\n /** Denser cell padding for embedded/editor (fit) layouts. */\n dense?: boolean;\n /** Roomier cell padding, for tables that need to read bigger. */\n spacious?: boolean;\n /** Whether editable columns show the drag-to-fill handle. */\n fillEnabled?: boolean;\n /** While true the cells own the pointer and the row click is suspended. */\n editMode?: boolean;\n /** Whether a click on the cell opens its editor, which then does not also open the row. */\n canEditCell?: (coord: IGridCellCoord) => boolean;\n /** Full-width band rendered as the first body row, spanning every column. */\n bodyTopRow?: React.ReactNode;\n /** Number of visible leaf columns the `bodyTopRow` cell must span. */\n bodyTopRowColSpan?: number;\n}\n\nconst INDENT_PER_LEVEL_PX = 24;\n\nconst DataTableBody = <T extends IDataTableRow, C extends IDataTableRow = IDataTableRow>({\n config,\n visibleRows,\n columnConfigById,\n columnContentWidths,\n registerRow,\n onCollapseStart,\n detailColumnsState,\n detailSlotWidth,\n showActionsShadow = false,\n dense = false,\n spacious = false,\n fillEnabled = false,\n editMode = false,\n canEditCell,\n bodyTopRow,\n bodyTopRowColSpan,\n}: IDataTableBodyProps<T, C>) => {\n const expansion = config.expansion;\n const panelExpansionConfig = expansion && isDataTablePanelExpansion(expansion) ? expansion : null;\n const subRowsExpansionConfig = expansion && isDataTableSubRowsExpansion(expansion) ? expansion : null;\n const detailTableExpansionConfig = expansion && isDataTableDetailTableExpansion(expansion) ? expansion : null;\n const tableExpansionConfig = expansion && isDataTableTableExpansion(expansion) ? expansion : null;\n\n const rowClickEnabled = !!config.onRowClick;\n\n // In a spreadsheet-enabled table an editable cell owns its click (it opens the editor),\n // so the row only opens the record from the other cells. A click that bubbled from a\n // portal (a select's listbox) is not inside the row in the DOM and never opens it either.\n const handleRowClick = (event: React.MouseEvent<HTMLTableRowElement>, row: Row<T>) => {\n const target = event.target;\n if (!(target instanceof Node) || !event.currentTarget.contains(target)) return;\n\n if (editMode && canEditCell && target instanceof Element) {\n const cellElement = target.closest<HTMLElement>(`[${GRID_ROW_DATA_ATTR}]`);\n const cellRowId = cellElement?.getAttribute(GRID_ROW_DATA_ATTR);\n const cellColumnId = cellElement?.getAttribute(GRID_COLUMN_DATA_ATTR);\n if (cellRowId && cellColumnId && canEditCell({ rowId: cellRowId, columnId: cellColumnId })) return;\n }\n\n config.onRowClick?.(row.original);\n };\n\n const summaryCells = config.summaryRow?.(visibleRows.map((row) => row.original)) ?? null;\n const summaryContentById = new Map((summaryCells ?? []).map((cell) => [cell.columnId, cell.content]));\n\n const expandTriggerColumnId = expansion?.triggerColumnId ?? null;\n\n const toggleRowExpansion = (row: Row<T>) => {\n if (!expansion || !row.getCanExpand()) return;\n\n const willExpand = !row.getIsExpanded();\n if (!willExpand && subRowsExpansionConfig) {\n onCollapseStart(row.subRows.map((subRow) => subRow.id));\n }\n if (willExpand && tableExpansionConfig && tableExpansionConfig.getChildRows(row.original) === undefined) {\n tableExpansionConfig.loadChildren?.(row.original);\n }\n row.toggleExpanded();\n expansion.onExpandChange?.(row.original, willExpand);\n };\n\n const renderExpandControl = (row: Row<T>) => {\n if (!expansion) return null;\n\n if (!row.getCanExpand()) return <span className=\"inline-block size-8 shrink-0\" />;\n\n return (\n <ControlledButton\n defaultHtmlButton\n onClick={(event) => {\n event.stopPropagation();\n toggleRowExpansion(row);\n }}\n className=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-foreground-subtle transition-colors hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon={row.getIsExpanded() ? 'expand_more' : 'chevron_right'} size={20} />\n </ControlledButton>\n );\n };\n\n const tableBody = (\n <tbody\n className={classNames(\n dataTableOpacityTransitionClass,\n '[&_tr:last-child_td:first-child]:rounded-bl-md [&_tr:last-child_td:last-child]:rounded-br-md [&_tr:last-child_td]:border-b-0',\n )}\n >\n {bodyTopRow && (\n <tr>\n <td colSpan={bodyTopRowColSpan} className=\"border-b border-border bg-surface p-0\">\n {/* Pin the band to the scroller's visible width so it never widens with\n the (possibly overflowing) table and its trailing controls stay in view. */}\n <div className=\"sticky left-0\" style={detailSlotWidth ? { width: detailSlotWidth } : undefined}>\n <div className={dense ? 'px-2 py-2' : 'px-4 py-2.5'}>{bodyTopRow}</div>\n </div>\n </td>\n </tr>\n )}\n\n {visibleRows.map((row) => {\n const isDimmed = config.rowIsDimmed?.(row.original) ?? false;\n const isChild = row.depth > 0;\n const isExpanded = row.getIsExpanded();\n const columnCount = row.getVisibleCells().length;\n\n const showSubRowsLoading =\n !!subRowsExpansionConfig &&\n isExpanded &&\n row.subRows.length === 0 &&\n !!subRowsExpansionConfig.isLoadingChildren?.(row.original);\n\n const showPanelContent = !!panelExpansionConfig && isExpanded;\n const showPanelLoading = showPanelContent && !!panelExpansionConfig.isLoadingContent?.(row.original);\n\n const showDetailTable = !!detailTableExpansionConfig && isExpanded;\n const detailRows = showDetailTable ? detailTableExpansionConfig.getDetailRows(row.original) : undefined;\n const showDetailLoading =\n showDetailTable &&\n (detailRows === undefined || !!detailTableExpansionConfig.isLoadingChildren?.(row.original));\n\n const showNestedTable = !!tableExpansionConfig && isExpanded;\n const nestedRows = showNestedTable ? tableExpansionConfig.getChildRows(row.original) : undefined;\n const showNestedLoading =\n showNestedTable && (nestedRows === undefined || !!tableExpansionConfig.isLoadingChildren?.(row.original));\n\n const visibleCells = row.getVisibleCells();\n const lastCellIndex = visibleCells.length - 1;\n\n return (\n <Fragment key={row.id}>\n <tr\n ref={registerRow(row.id)}\n onClick={rowClickEnabled ? (event) => handleRowClick(event, row) : undefined}\n // A altura na linha vale como MÍNIMO numa tabela: linhas de texto puro e\n // linhas com miniatura ou chip param de alternar de altura, e o olho\n // percorre a coluna sem tropeço. Conteúdo mais alto ainda expande.\n className={classNames('transition-colors', dense ? 'h-9' : 'h-12', {\n 'cursor-pointer': rowClickEnabled,\n 'bg-surface': !isChild,\n 'hover:bg-muted': rowClickEnabled && !isChild,\n 'bg-muted-hover': isChild,\n 'hover:bg-muted-active': isChild && rowClickEnabled,\n })}\n >\n {visibleCells.map((cell, cellIndex) => {\n const columnConfig = columnConfigById.get(cell.column.id);\n const cellContent = flexRender(cell.column.columnDef.cell, cell.getContext());\n const isFirstCell = cellIndex === 0;\n const isActionsCell = cellIndex === lastCellIndex && columnConfig?.type === 'actions';\n const isTriggerColumnCell = !!expandTriggerColumnId && cell.column.id === expandTriggerColumnId;\n const isExpandCell = !!expansion && (expandTriggerColumnId ? isTriggerColumnCell : isFirstCell);\n\n return (\n <DataTableBodyCell\n key={cell.id}\n rowId={row.id}\n columnId={cell.column.id}\n navigable={editMode && columnConfig?.type !== 'actions'}\n fillEnabled={fillEnabled && !!columnConfig?.editable && columnConfig.editable.fillable !== false}\n className={classNames(\n 'border-b border-border-subtle align-middle',\n dense ? 'px-3 py-1.5' : spacious ? 'px-5 py-4' : 'px-4 py-2.5',\n !isFirstCell && !isActionsCell && 'overflow-hidden',\n alignClass(columnConfig?.cellAlign),\n { 'opacity-60': isDimmed && columnConfig?.type !== 'actions' },\n isFirstCell && 'sticky left-0 z-10 bg-inherit',\n isFirstCell && showActionsShadow && FROZEN_LEFT_COLUMN_SHADOW,\n isActionsCell && 'sticky right-0 z-10 bg-inherit',\n isActionsCell && showActionsShadow && FROZEN_COLUMN_SHADOW,\n )}\n style={{\n minWidth:\n columnContentWidths.get(cell.column.id) ??\n resolveColumnMinWidthPx(columnConfig, {\n isFirstColumn: isFirstCell,\n hasColumnToggle: isFirstCell,\n }),\n }}\n >\n {isExpandCell ? (\n <div\n role={isTriggerColumnCell && row.getCanExpand() ? 'button' : undefined}\n tabIndex={isTriggerColumnCell && row.getCanExpand() ? 0 : undefined}\n onClick={\n isTriggerColumnCell && row.getCanExpand()\n ? (event) => {\n event.stopPropagation();\n toggleRowExpansion(row);\n }\n : undefined\n }\n onKeyDown={\n isTriggerColumnCell && row.getCanExpand()\n ? (event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n event.stopPropagation();\n toggleRowExpansion(row);\n }\n : undefined\n }\n className={classNames('flex min-w-0 items-center gap-1.5', {\n 'cursor-pointer rounded-md': isTriggerColumnCell && row.getCanExpand(),\n })}\n style={{ paddingLeft: isFirstCell ? row.depth * INDENT_PER_LEVEL_PX : undefined }}\n >\n {renderExpandControl(row)}\n <div className=\"min-w-0 flex-1\">{cellContent}</div>\n </div>\n ) : (\n cellContent\n )}\n </DataTableBodyCell>\n );\n })}\n </tr>\n\n {showSubRowsLoading && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border px-4 py-3\">\n <LoadingSpinner size={5} color=\"foreground-faint\" />\n </td>\n </tr>\n )}\n\n {showPanelContent && panelExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border p-0\">\n <div className=\"w-full bg-surface py-3\">\n {showPanelLoading ? (\n <div className=\"flex items-center justify-center py-2\">\n <LoadingSpinner size={5} color=\"foreground-faint\" />\n </div>\n ) : (\n panelExpansionConfig.renderExpandedContent(row.original, {\n collapse: () => {\n if (!row.getIsExpanded()) return;\n row.toggleExpanded(false);\n panelExpansionConfig.onExpandChange?.(row.original, false);\n },\n })\n )}\n </div>\n </td>\n </tr>\n )}\n\n {showDetailTable && detailTableExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border bg-muted/70 p-0\">\n <DataTableDetailPanel\n columns={\n typeof detailTableExpansionConfig.columns === 'function'\n ? detailTableExpansionConfig.columns(row.original)\n : detailTableExpansionConfig.columns\n }\n rows={detailRows ?? []}\n loading={showDetailLoading}\n onRowClick={\n detailTableExpansionConfig.onDetailRowClick\n ? (detailRow) => detailTableExpansionConfig.onDetailRowClick?.(detailRow, row.original)\n : undefined\n }\n rowIsDimmed={detailTableExpansionConfig.detailRowIsDimmed}\n emptyMessage={detailTableExpansionConfig.emptyMessage}\n columnsState={detailColumnsState}\n slotWidth={detailSlotWidth}\n />\n </td>\n </tr>\n )}\n\n {showNestedTable && tableExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border bg-muted/70 p-0\">\n <div className=\"sticky left-0\" style={detailSlotWidth ? { width: detailSlotWidth } : undefined}>\n <div className=\"px-3 py-3\">\n <DataTable\n config={tableExpansionConfig.childConfig}\n rows={nestedRows ?? []}\n loading={showNestedLoading}\n />\n </div>\n </div>\n </td>\n </tr>\n )}\n </Fragment>\n );\n })}\n </tbody>\n );\n\n const summaryFooter =\n summaryCells && summaryCells.length > 0 && visibleRows.length > 0 ? (\n <tfoot>\n <tr className=\"bg-muted font-semibold text-foreground-muted\">\n {visibleRows[0]?.getVisibleCells().map((cell, cellIndex) => {\n const columnConfig = columnConfigById.get(cell.column.id);\n const isFirstCell = cellIndex === 0;\n const isActionsCell =\n cellIndex === (visibleRows[0]?.getVisibleCells().length ?? 0) - 1 && columnConfig?.type === 'actions';\n const content = summaryContentById.get(cell.column.id) ?? null;\n\n return (\n <td\n key={cell.column.id}\n className={classNames(\n 'sticky bottom-0 z-10 border-t-2 border-border bg-muted align-middle',\n dense ? 'px-3 py-1.5' : 'px-4 py-2.5',\n alignClass(columnConfig?.cellAlign),\n isFirstCell && 'left-0 z-20',\n isFirstCell && showActionsShadow && FROZEN_LEFT_COLUMN_SHADOW,\n isActionsCell && 'right-0 z-20',\n isActionsCell && showActionsShadow && FROZEN_COLUMN_SHADOW,\n )}\n style={{\n minWidth: resolveColumnMinWidthPx(columnConfig, {\n isFirstColumn: isFirstCell,\n hasColumnToggle: isFirstCell,\n }),\n }}\n >\n {isFirstCell && expansion ? (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span className=\"inline-block size-8 shrink-0\" />\n <div className=\"min-w-0 flex-1\">{content}</div>\n </div>\n ) : (\n content\n )}\n </td>\n );\n })}\n </tr>\n </tfoot>\n ) : null;\n\n return (\n <>\n {tableBody}\n {summaryFooter}\n </>\n );\n};\n\nexport default DataTableBody;\n"],"names":["Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTableBody.js","sources":["../../../../src/components/UI/DataTable/DataTableBody.tsx"],"sourcesContent":["import React, { Fragment } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport LoadingSpinner from '@/components/UI/LoadingSpinner';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { flexRender, type Row } from '@tanstack/react-table';\n\nimport { GRID_COLUMN_DATA_ATTR, GRID_ROW_DATA_ATTR, type IGridCellCoord } from './grid/gridTypes';\nimport DataTable from './DataTable';\nimport DataTableBodyCell from './DataTableBodyCell';\nimport { alignClass, FROZEN_COLUMN_SHADOW, FROZEN_LEFT_COLUMN_SHADOW } from './dataTableClasses';\nimport { resolveColumnMinWidthPx } from './dataTableColumnWidths';\nimport DataTableDetailPanel from './DataTableDetailPanel';\nimport { type IDataTableDetailColumnsState } from './DataTableDetailTable';\nimport { dataTableOpacityTransitionClass } from './dataTableMotion';\nimport {\n type IDataTableColumnConfig,\n type IDataTableConfig,\n type IDataTableRow,\n isDataTableDetailTableExpansion,\n isDataTablePanelExpansion,\n isDataTableSubRowsExpansion,\n isDataTableTableExpansion,\n} from './types';\n\ninterface IDataTableBodyProps<T extends IDataTableRow, C extends IDataTableRow = IDataTableRow> {\n config: IDataTableConfig<T, C>;\n visibleRows: Array<Row<T>>;\n columnConfigById: Map<string, IDataTableColumnConfig<T>>;\n /** Row-derived widths for actions/badge columns; other columns fall back to the seed. */\n columnContentWidths: Map<string, number>;\n registerRow: (id: string) => (element: HTMLElement | null) => void;\n onCollapseStart: (childRowIds: Array<string>) => void;\n detailColumnsState?: IDataTableDetailColumnsState;\n detailSlotWidth?: number;\n showActionsShadow?: boolean;\n /** Denser cell padding for embedded/editor (fit) layouts. */\n dense?: boolean;\n /** Roomier cell padding, for tables that need to read bigger. */\n spacious?: boolean;\n /** Whether editable columns show the drag-to-fill handle. */\n fillEnabled?: boolean;\n /** While true the cells own the pointer and the row click is suspended. */\n editMode?: boolean;\n /** Whether a click on the cell opens its editor, which then does not also open the row. */\n canEditCell?: (coord: IGridCellCoord) => boolean;\n /** Full-width band rendered as the first body row, spanning every column. */\n bodyTopRow?: React.ReactNode;\n /** Number of visible leaf columns the `bodyTopRow` cell must span. */\n bodyTopRowColSpan?: number;\n}\n\nconst INDENT_PER_LEVEL_PX = 24;\n\nconst DataTableBody = <T extends IDataTableRow, C extends IDataTableRow = IDataTableRow>({\n config,\n visibleRows,\n columnConfigById,\n columnContentWidths,\n registerRow,\n onCollapseStart,\n detailColumnsState,\n detailSlotWidth,\n showActionsShadow = false,\n dense = false,\n spacious = false,\n fillEnabled = false,\n editMode = false,\n canEditCell,\n bodyTopRow,\n bodyTopRowColSpan,\n}: IDataTableBodyProps<T, C>) => {\n const { t } = useTranslation('ui');\n const expansion = config.expansion;\n const panelExpansionConfig = expansion && isDataTablePanelExpansion(expansion) ? expansion : null;\n const subRowsExpansionConfig = expansion && isDataTableSubRowsExpansion(expansion) ? expansion : null;\n const detailTableExpansionConfig = expansion && isDataTableDetailTableExpansion(expansion) ? expansion : null;\n const tableExpansionConfig = expansion && isDataTableTableExpansion(expansion) ? expansion : null;\n\n const rowClickEnabled = !!config.onRowClick;\n\n // In a spreadsheet-enabled table an editable cell owns its click (it opens the editor),\n // so the row only opens the record from the other cells. A click that bubbled from a\n // portal (a select's listbox) is not inside the row in the DOM and never opens it either.\n const handleRowClick = (event: React.MouseEvent<HTMLTableRowElement>, row: Row<T>) => {\n const target = event.target;\n if (!(target instanceof Node) || !event.currentTarget.contains(target)) return;\n\n if (editMode && canEditCell && target instanceof Element) {\n const cellElement = target.closest<HTMLElement>(`[${GRID_ROW_DATA_ATTR}]`);\n const cellRowId = cellElement?.getAttribute(GRID_ROW_DATA_ATTR);\n const cellColumnId = cellElement?.getAttribute(GRID_COLUMN_DATA_ATTR);\n if (cellRowId && cellColumnId && canEditCell({ rowId: cellRowId, columnId: cellColumnId })) return;\n }\n\n config.onRowClick?.(row.original);\n };\n\n const summaryCells = config.summaryRow?.(visibleRows.map((row) => row.original)) ?? null;\n const summaryContentById = new Map((summaryCells ?? []).map((cell) => [cell.columnId, cell.content]));\n\n const expandTriggerColumnId = expansion?.triggerColumnId ?? null;\n\n const toggleRowExpansion = (row: Row<T>) => {\n if (!expansion || !row.getCanExpand()) return;\n\n const willExpand = !row.getIsExpanded();\n if (!willExpand && subRowsExpansionConfig) {\n onCollapseStart(row.subRows.map((subRow) => subRow.id));\n }\n if (willExpand && tableExpansionConfig && tableExpansionConfig.getChildRows(row.original) === undefined) {\n tableExpansionConfig.loadChildren?.(row.original);\n }\n row.toggleExpanded();\n expansion.onExpandChange?.(row.original, willExpand);\n };\n\n const renderExpandControl = (row: Row<T>) => {\n if (!expansion) return null;\n\n if (!row.getCanExpand()) return <span className=\"inline-block size-8 shrink-0\" />;\n\n return (\n <ControlledButton\n aria-label={t(row.getIsExpanded() ? 'dataTable.collapseRow' : 'dataTable.expandRow')}\n aria-expanded={row.getIsExpanded()}\n defaultHtmlButton\n onClick={(event) => {\n event.stopPropagation();\n toggleRowExpansion(row);\n }}\n className=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-foreground-subtle transition-colors hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon={row.getIsExpanded() ? 'expand_more' : 'chevron_right'} size={20} />\n </ControlledButton>\n );\n };\n\n const tableBody = (\n <tbody\n className={classNames(\n dataTableOpacityTransitionClass,\n '[&_tr:last-child_td:first-child]:rounded-bl-md [&_tr:last-child_td:last-child]:rounded-br-md [&_tr:last-child_td]:border-b-0',\n )}\n >\n {bodyTopRow && (\n <tr>\n <td colSpan={bodyTopRowColSpan} className=\"border-b border-border bg-surface p-0\">\n {/* Pin the band to the scroller's visible width so it never widens with\n the (possibly overflowing) table and its trailing controls stay in view. */}\n <div className=\"sticky left-0\" style={detailSlotWidth ? { width: detailSlotWidth } : undefined}>\n <div className={dense ? 'px-2 py-2' : 'px-4 py-2.5'}>{bodyTopRow}</div>\n </div>\n </td>\n </tr>\n )}\n\n {visibleRows.map((row) => {\n const isDimmed = config.rowIsDimmed?.(row.original) ?? false;\n const isChild = row.depth > 0;\n const isExpanded = row.getIsExpanded();\n const columnCount = row.getVisibleCells().length;\n\n const showSubRowsLoading =\n !!subRowsExpansionConfig &&\n isExpanded &&\n row.subRows.length === 0 &&\n !!subRowsExpansionConfig.isLoadingChildren?.(row.original);\n\n const showPanelContent = !!panelExpansionConfig && isExpanded;\n const showPanelLoading = showPanelContent && !!panelExpansionConfig.isLoadingContent?.(row.original);\n\n const showDetailTable = !!detailTableExpansionConfig && isExpanded;\n const detailRows = showDetailTable ? detailTableExpansionConfig.getDetailRows(row.original) : undefined;\n const showDetailLoading =\n showDetailTable &&\n (detailRows === undefined || !!detailTableExpansionConfig.isLoadingChildren?.(row.original));\n\n const showNestedTable = !!tableExpansionConfig && isExpanded;\n const nestedRows = showNestedTable ? tableExpansionConfig.getChildRows(row.original) : undefined;\n const showNestedLoading =\n showNestedTable && (nestedRows === undefined || !!tableExpansionConfig.isLoadingChildren?.(row.original));\n\n const visibleCells = row.getVisibleCells();\n const lastCellIndex = visibleCells.length - 1;\n\n return (\n <Fragment key={row.id}>\n <tr\n ref={registerRow(row.id)}\n onClick={rowClickEnabled ? (event) => handleRowClick(event, row) : undefined}\n // A altura na linha vale como MÍNIMO numa tabela: linhas de texto puro e\n // linhas com miniatura ou chip param de alternar de altura, e o olho\n // percorre a coluna sem tropeço. Conteúdo mais alto ainda expande.\n className={classNames('transition-colors', dense ? 'h-9' : 'h-12', {\n 'cursor-pointer': rowClickEnabled,\n 'bg-surface': !isChild,\n 'hover:bg-muted': rowClickEnabled && !isChild,\n 'bg-muted-hover': isChild,\n 'hover:bg-muted-active': isChild && rowClickEnabled,\n })}\n >\n {visibleCells.map((cell, cellIndex) => {\n const columnConfig = columnConfigById.get(cell.column.id);\n const cellContent = flexRender(cell.column.columnDef.cell, cell.getContext());\n const isFirstCell = cellIndex === 0;\n const isActionsCell = cellIndex === lastCellIndex && columnConfig?.type === 'actions';\n const isTriggerColumnCell = !!expandTriggerColumnId && cell.column.id === expandTriggerColumnId;\n const isExpandCell = !!expansion && (expandTriggerColumnId ? isTriggerColumnCell : isFirstCell);\n\n return (\n <DataTableBodyCell\n key={cell.id}\n rowId={row.id}\n columnId={cell.column.id}\n navigable={editMode && columnConfig?.type !== 'actions'}\n fillEnabled={fillEnabled && !!columnConfig?.editable && columnConfig.editable.fillable !== false}\n className={classNames(\n 'border-b border-border-subtle align-middle',\n dense ? 'px-3 py-1.5' : spacious ? 'px-5 py-4' : 'px-4 py-2.5',\n !isFirstCell && !isActionsCell && 'overflow-hidden',\n alignClass(columnConfig?.cellAlign),\n { 'opacity-60': isDimmed && columnConfig?.type !== 'actions' },\n isFirstCell && 'sticky left-0 z-10 bg-inherit',\n isFirstCell && showActionsShadow && FROZEN_LEFT_COLUMN_SHADOW,\n isActionsCell && 'sticky right-0 z-10 bg-inherit',\n isActionsCell && showActionsShadow && FROZEN_COLUMN_SHADOW,\n )}\n style={{\n minWidth:\n columnContentWidths.get(cell.column.id) ??\n resolveColumnMinWidthPx(columnConfig, {\n isFirstColumn: isFirstCell,\n hasColumnToggle: isFirstCell,\n }),\n }}\n >\n {isExpandCell ? (\n <div\n role={isTriggerColumnCell && row.getCanExpand() ? 'button' : undefined}\n tabIndex={isTriggerColumnCell && row.getCanExpand() ? 0 : undefined}\n onClick={\n isTriggerColumnCell && row.getCanExpand()\n ? (event) => {\n event.stopPropagation();\n toggleRowExpansion(row);\n }\n : undefined\n }\n onKeyDown={\n isTriggerColumnCell && row.getCanExpand()\n ? (event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n event.stopPropagation();\n toggleRowExpansion(row);\n }\n : undefined\n }\n className={classNames('flex min-w-0 items-center gap-1.5', {\n 'cursor-pointer rounded-md': isTriggerColumnCell && row.getCanExpand(),\n })}\n style={{ paddingLeft: isFirstCell ? row.depth * INDENT_PER_LEVEL_PX : undefined }}\n >\n {renderExpandControl(row)}\n <div className=\"min-w-0 flex-1\">{cellContent}</div>\n </div>\n ) : (\n cellContent\n )}\n </DataTableBodyCell>\n );\n })}\n </tr>\n\n {showSubRowsLoading && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border px-4 py-3\">\n <LoadingSpinner size={5} color=\"foreground-faint\" />\n </td>\n </tr>\n )}\n\n {showPanelContent && panelExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border p-0\">\n <div className=\"w-full bg-surface py-3\">\n {showPanelLoading ? (\n <div className=\"flex items-center justify-center py-2\">\n <LoadingSpinner size={5} color=\"foreground-faint\" />\n </div>\n ) : (\n panelExpansionConfig.renderExpandedContent(row.original, {\n collapse: () => {\n if (!row.getIsExpanded()) return;\n row.toggleExpanded(false);\n panelExpansionConfig.onExpandChange?.(row.original, false);\n },\n })\n )}\n </div>\n </td>\n </tr>\n )}\n\n {showDetailTable && detailTableExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border bg-muted/70 p-0\">\n <DataTableDetailPanel\n columns={\n typeof detailTableExpansionConfig.columns === 'function'\n ? detailTableExpansionConfig.columns(row.original)\n : detailTableExpansionConfig.columns\n }\n rows={detailRows ?? []}\n loading={showDetailLoading}\n onRowClick={\n detailTableExpansionConfig.onDetailRowClick\n ? (detailRow) => detailTableExpansionConfig.onDetailRowClick?.(detailRow, row.original)\n : undefined\n }\n rowIsDimmed={detailTableExpansionConfig.detailRowIsDimmed}\n emptyMessage={detailTableExpansionConfig.emptyMessage}\n columnsState={detailColumnsState}\n slotWidth={detailSlotWidth}\n />\n </td>\n </tr>\n )}\n\n {showNestedTable && tableExpansionConfig && (\n <tr>\n <td colSpan={columnCount} className=\"border-b border-border bg-muted/70 p-0\">\n <div className=\"sticky left-0\" style={detailSlotWidth ? { width: detailSlotWidth } : undefined}>\n <div className=\"px-3 py-3\">\n <DataTable\n config={tableExpansionConfig.childConfig}\n rows={nestedRows ?? []}\n loading={showNestedLoading}\n />\n </div>\n </div>\n </td>\n </tr>\n )}\n </Fragment>\n );\n })}\n </tbody>\n );\n\n const summaryFooter =\n summaryCells && summaryCells.length > 0 && visibleRows.length > 0 ? (\n <tfoot>\n <tr className=\"bg-muted font-semibold text-foreground-muted\">\n {visibleRows[0]?.getVisibleCells().map((cell, cellIndex) => {\n const columnConfig = columnConfigById.get(cell.column.id);\n const isFirstCell = cellIndex === 0;\n const isActionsCell =\n cellIndex === (visibleRows[0]?.getVisibleCells().length ?? 0) - 1 && columnConfig?.type === 'actions';\n const content = summaryContentById.get(cell.column.id) ?? null;\n\n return (\n <td\n key={cell.column.id}\n className={classNames(\n 'sticky bottom-0 z-10 border-t-2 border-border bg-muted align-middle',\n dense ? 'px-3 py-1.5' : 'px-4 py-2.5',\n alignClass(columnConfig?.cellAlign),\n isFirstCell && 'left-0 z-20',\n isFirstCell && showActionsShadow && FROZEN_LEFT_COLUMN_SHADOW,\n isActionsCell && 'right-0 z-20',\n isActionsCell && showActionsShadow && FROZEN_COLUMN_SHADOW,\n )}\n style={{\n minWidth: resolveColumnMinWidthPx(columnConfig, {\n isFirstColumn: isFirstCell,\n hasColumnToggle: isFirstCell,\n }),\n }}\n >\n {isFirstCell && expansion ? (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span className=\"inline-block size-8 shrink-0\" />\n <div className=\"min-w-0 flex-1\">{content}</div>\n </div>\n ) : (\n content\n )}\n </td>\n );\n })}\n </tr>\n </tfoot>\n ) : null;\n\n return (\n <>\n {tableBody}\n {summaryFooter}\n </>\n );\n};\n\nexport default DataTableBody;\n"],"names":["Fragment"],"mappings":"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"}
|
|
@@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next";
|
|
|
4
4
|
import ControlledCheckbox from "../../Form/ControlledCheckbox.js";
|
|
5
5
|
import "classnames";
|
|
6
6
|
import "../../Form/ControlledButton.js";
|
|
7
|
-
import "tailwind-merge";
|
|
8
7
|
import "../ControlledMenu.js";
|
|
9
8
|
import "motion/react";
|
|
10
9
|
import "react-dom";
|
|
10
|
+
import "../../../helpers/mergeClasses.js";
|
|
11
11
|
import "@floating-ui/dom";
|
|
12
12
|
import "../../../motion/motionPresets.js";
|
|
13
13
|
import "../../../motion/motionTokens.js";
|
|
@@ -31,6 +31,7 @@ const DataTableColumnToggleItem = ({ id, header, isVisible, onToggleVisibility }
|
|
|
31
31
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("dataTable.reorderColumn"), children: /* @__PURE__ */ jsx(
|
|
32
32
|
ControlledButton,
|
|
33
33
|
{
|
|
34
|
+
"aria-label": t("dataTable.reorderColumn"),
|
|
34
35
|
defaultHtmlButton: true,
|
|
35
36
|
className: "flex cursor-grab touch-none items-center text-foreground-faint hover:text-foreground-muted active:cursor-grabbing",
|
|
36
37
|
...attributes,
|
|
@@ -44,7 +45,7 @@ const DataTableColumnToggleItem = ({ id, header, isVisible, onToggleVisibility }
|
|
|
44
45
|
defaultHtmlButton: true,
|
|
45
46
|
onClick: onToggleVisibility,
|
|
46
47
|
className: "flex flex-1 items-center gap-2 rounded-md px-1.5 py-1 text-left",
|
|
47
|
-
children: /* @__PURE__ */ jsx("span", { className: "pointer-events-none flex items-center", children: /* @__PURE__ */ jsx(ControlledCheckbox, { checked: isVisible, onChange: () => void 0, label: header, size: "sm" }) })
|
|
48
|
+
children: /* @__PURE__ */ jsx("span", { className: "pointer-events-none flex items-center", children: /* @__PURE__ */ jsx(ControlledCheckbox, { checked: isVisible, onChange: () => void 0, label: header, size: "sm", tabIndex: -1 }) })
|
|
48
49
|
}
|
|
49
50
|
)
|
|
50
51
|
]
|
|
@@ -92,6 +93,7 @@ const DataTableColumnMenu = ({
|
|
|
92
93
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("dataTable.columns"), children: /* @__PURE__ */ jsx(ControlledMenuHandler, { children: /* @__PURE__ */ jsx(
|
|
93
94
|
ControlledButton,
|
|
94
95
|
{
|
|
96
|
+
"aria-label": t("dataTable.columns"),
|
|
95
97
|
defaultHtmlButton: true,
|
|
96
98
|
className: variant === "toolbar" ? "inline-flex shrink-0 items-center justify-center rounded-lg border border-border bg-surface p-2.5 text-foreground-subtle shadow-sm transition-colors hover:bg-muted-hover hover:text-foreground-muted" : "inline-flex items-center justify-center rounded p-1 text-foreground-faint transition-colors hover:bg-muted-active hover:text-foreground-muted",
|
|
97
99
|
children: /* @__PURE__ */ jsx("span", { className: "flex", children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "view_column", size: variant === "toolbar" ? 18 : 16 }) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTableColumnToggle.js","sources":["../../../../src/components/UI/DataTable/DataTableColumnToggle.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledCheckbox from '@/components/Form/ControlledCheckbox';\nimport { ControlledMenuHandler, ControlledMenuList, ControlledMenuRoot } from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { DndContext, type DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';\nimport {\n arrayMove,\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport type { Table, Updater } from '@tanstack/react-table';\n\nimport type { IDataTableColumnConfig, IDataTableRow } from './types';\nimport { getFixedEndColumns, getFixedStartColumns, getReorderableColumns } from './useColumnOrder';\n\n/**\n * Table-agnostic core of the \"Colunas\" menu (visibility checkboxes + drag\n * reordering). Visibility is injected via callbacks so the same menu serves the\n * parent table (TanStack-backed, see the default export) and the nested detail\n * table (plain visibility map, see DataTableDetailTable).\n */\nexport interface IDataTableColumnMenuProps<R extends IDataTableRow> {\n columns: Array<IDataTableColumnConfig<R>>;\n columnOrder: Array<string>;\n onColumnOrderChange: (updater: Updater<Array<string>>) => void;\n resetColumnOrder: () => void;\n isColumnVisible: (columnId: string) => boolean;\n onToggleColumnVisibility: (columnId: string) => void;\n /** When provided, adds a \"restore default widths\" action to the menu footer. */\n resetColumnSizing?: () => void;\n /** `header` (padrão): ícone discreto no cabeçalho. `toolbar`: botão com borda, pareado com o toggle de visualização. */\n variant?: 'header' | 'toolbar';\n}\n\ninterface IDataTableColumnToggleProps<T extends IDataTableRow> {\n table: Table<T>;\n columns: Array<IDataTableColumnConfig<T>>;\n columnOrder: Array<string>;\n onColumnOrderChange: (updater: Updater<Array<string>>) => void;\n resetColumnOrder: () => void;\n /** When provided, adds a \"restore default widths\" action to the menu footer. */\n resetColumnSizing?: () => void;\n /** `header` (padrão): ícone discreto no cabeçalho. `toolbar`: botão com borda, pareado com o toggle de visualização. */\n variant?: 'header' | 'toolbar';\n}\n\ninterface IDataTableColumnToggleItemProps {\n id: string;\n header: string;\n isVisible: boolean;\n onToggleVisibility: () => void;\n}\n\nconst DataTableColumnToggleItem = ({ id, header, isVisible, onToggleVisibility }: IDataTableColumnToggleItemProps) => {\n const { t } = useTranslation('ui');\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n };\n\n return (\n <li\n ref={setNodeRef}\n style={style}\n className={classNames('flex items-center gap-1 rounded-md px-1.5 py-1.5', {\n 'relative z-10 bg-surface shadow-md': isDragging,\n 'hover:bg-muted-hover': !isDragging,\n })}\n >\n <ControlledTooltip content={t('dataTable.reorderColumn')}>\n <ControlledButton\n defaultHtmlButton\n className=\"flex cursor-grab touch-none items-center text-foreground-faint hover:text-foreground-muted active:cursor-grabbing\"\n {...attributes}\n {...listeners}\n >\n <MaterialSymbol icon=\"drag_indicator\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n {/* Checkbox is display-only: pointer-events-none lets the click fall through\n to the button, so clicking anywhere (checkbox or label) toggles once. */}\n <ControlledButton\n defaultHtmlButton\n onClick={onToggleVisibility}\n className=\"flex flex-1 items-center gap-2 rounded-md px-1.5 py-1 text-left\"\n >\n <span className=\"pointer-events-none flex items-center\">\n <ControlledCheckbox checked={isVisible} onChange={() => undefined} label={header} size=\"sm\" />\n </span>\n </ControlledButton>\n </li>\n );\n};\n\nexport const DataTableColumnMenu = <R extends IDataTableRow>({\n columns,\n columnOrder,\n onColumnOrderChange,\n resetColumnOrder,\n isColumnVisible,\n onToggleColumnVisibility,\n resetColumnSizing,\n variant = 'header',\n}: IDataTableColumnMenuProps<R>) => {\n const { t } = useTranslation('ui');\n const [isOpen, setIsOpen] = useState(false);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n );\n\n const fixedStartIds = useMemo(() => getFixedStartColumns(columns).map((column) => column.id), [columns]);\n const fixedEndIds = useMemo(() => getFixedEndColumns(columns).map((column) => column.id), [columns]);\n\n const reorderableColumns = useMemo(() => getReorderableColumns(columns), [columns]);\n\n // Reorderable columns rendered in the CURRENT persisted order. Any reorderable\n // column missing from `columnOrder` (defensive) is appended in config order.\n const orderedReorderableColumns = useMemo(() => {\n const reorderableColumnById = new Map(reorderableColumns.map((column) => [column.id, column]));\n const ordered = (columnOrder ?? [])\n .map((id) => reorderableColumnById.get(id))\n .filter((column): column is IDataTableColumnConfig<R> => !!column);\n\n const orderedIds = new Set(ordered.map((column) => column.id));\n for (const column of reorderableColumns) if (!orderedIds.has(column.id)) ordered.push(column);\n\n return ordered;\n }, [reorderableColumns, columnOrder]);\n\n const sortableIds = useMemo(() => orderedReorderableColumns.map((column) => column.id), [orderedReorderableColumns]);\n\n if (reorderableColumns.length === 0) return null;\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n const oldIndex = sortableIds.indexOf(String(active.id));\n const newIndex = sortableIds.indexOf(String(over.id));\n if (oldIndex === -1 || newIndex === -1) return;\n\n const nextReorderableIds = arrayMove(sortableIds, oldIndex, newIndex);\n onColumnOrderChange([...fixedStartIds, ...nextReorderableIds, ...fixedEndIds]);\n };\n\n return (\n <ControlledMenuRoot open={isOpen} handler={setIsOpen} placement=\"bottom-end\" dismiss={{ itemPress: false }}>\n <ControlledTooltip content={t('dataTable.columns')}>\n <ControlledMenuHandler>\n <ControlledButton\n defaultHtmlButton\n className={\n variant === 'toolbar'\n ? 'inline-flex shrink-0 items-center justify-center rounded-lg border border-border bg-surface p-2.5 text-foreground-subtle shadow-sm transition-colors hover:bg-muted-hover hover:text-foreground-muted'\n : 'inline-flex items-center justify-center rounded p-1 text-foreground-faint transition-colors hover:bg-muted-active hover:text-foreground-muted'\n }\n >\n <span className=\"flex\">\n <MaterialSymbol icon=\"view_column\" size={variant === 'toolbar' ? 18 : 16} />\n </span>\n </ControlledButton>\n </ControlledMenuHandler>\n </ControlledTooltip>\n {/* MenuList gets role=\"menu\", and global CSS forces `[role='menu'] { overflow: hidden !important }`,\n so scroll must live on an inner wrapper (same pattern as ControlledSelect). z-[9999] lifts the\n portaled menu above the table's sticky cells and the app chrome so it is never covered. */}\n <ControlledMenuList className=\"z-[9999] min-w-56 overflow-hidden p-0\">\n <div className=\"max-h-80 overflow-y-auto overscroll-contain p-1\" onWheel={(event) => event.stopPropagation()}>\n <DndContext sensors={sensors} onDragEnd={handleDragEnd}>\n <SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>\n <ul className=\"flex flex-col\">\n {orderedReorderableColumns.map((columnConfig) => (\n <DataTableColumnToggleItem\n key={columnConfig.id}\n id={columnConfig.id}\n header={columnConfig.header}\n isVisible={isColumnVisible(columnConfig.id)}\n onToggleVisibility={() => onToggleColumnVisibility(columnConfig.id)}\n />\n ))}\n </ul>\n </SortableContext>\n </DndContext>\n </div>\n <div className=\"border-t border-border-subtle p-1\">\n <ControlledButton\n defaultHtmlButton\n onClick={resetColumnOrder}\n className=\"flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-xs font-medium text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"restart_alt\" size={16} />\n {t('dataTable.restoreDefaultOrder')}\n </ControlledButton>\n {resetColumnSizing && (\n <ControlledButton\n defaultHtmlButton\n onClick={resetColumnSizing}\n className=\"flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-xs font-medium text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"width\" size={16} />\n {t('dataTable.restoreDefaultWidths')}\n </ControlledButton>\n )}\n </div>\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n};\n\n// Parent-table entry point: derives visibility from the TanStack table instance\n// so it stays the single source of truth for the main columns.\nconst DataTableColumnToggle = <T extends IDataTableRow>({\n table,\n columns,\n columnOrder,\n onColumnOrderChange,\n resetColumnOrder,\n resetColumnSizing,\n variant = 'header',\n}: IDataTableColumnToggleProps<T>) => (\n <DataTableColumnMenu\n columns={columns}\n columnOrder={columnOrder}\n onColumnOrderChange={onColumnOrderChange}\n resetColumnOrder={resetColumnOrder}\n resetColumnSizing={resetColumnSizing}\n isColumnVisible={(columnId) => table.getColumn(columnId)?.getIsVisible() ?? true}\n onToggleColumnVisibility={(columnId) => {\n const column = table.getColumn(columnId);\n column?.toggleVisibility(!(column?.getIsVisible() ?? true));\n }}\n variant={variant}\n />\n);\n\nexport default DataTableColumnToggle;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTableColumnToggle.js","sources":["../../../../src/components/UI/DataTable/DataTableColumnToggle.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledCheckbox from '@/components/Form/ControlledCheckbox';\nimport { ControlledMenuHandler, ControlledMenuList, ControlledMenuRoot } from '@/components/UI/ControlledMenu';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { DndContext, type DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';\nimport {\n arrayMove,\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport type { Table, Updater } from '@tanstack/react-table';\n\nimport type { IDataTableColumnConfig, IDataTableRow } from './types';\nimport { getFixedEndColumns, getFixedStartColumns, getReorderableColumns } from './useColumnOrder';\n\n/**\n * Table-agnostic core of the \"Colunas\" menu (visibility checkboxes + drag\n * reordering). Visibility is injected via callbacks so the same menu serves the\n * parent table (TanStack-backed, see the default export) and the nested detail\n * table (plain visibility map, see DataTableDetailTable).\n */\nexport interface IDataTableColumnMenuProps<R extends IDataTableRow> {\n columns: Array<IDataTableColumnConfig<R>>;\n columnOrder: Array<string>;\n onColumnOrderChange: (updater: Updater<Array<string>>) => void;\n resetColumnOrder: () => void;\n isColumnVisible: (columnId: string) => boolean;\n onToggleColumnVisibility: (columnId: string) => void;\n /** When provided, adds a \"restore default widths\" action to the menu footer. */\n resetColumnSizing?: () => void;\n /** `header` (padrão): ícone discreto no cabeçalho. `toolbar`: botão com borda, pareado com o toggle de visualização. */\n variant?: 'header' | 'toolbar';\n}\n\ninterface IDataTableColumnToggleProps<T extends IDataTableRow> {\n table: Table<T>;\n columns: Array<IDataTableColumnConfig<T>>;\n columnOrder: Array<string>;\n onColumnOrderChange: (updater: Updater<Array<string>>) => void;\n resetColumnOrder: () => void;\n /** When provided, adds a \"restore default widths\" action to the menu footer. */\n resetColumnSizing?: () => void;\n /** `header` (padrão): ícone discreto no cabeçalho. `toolbar`: botão com borda, pareado com o toggle de visualização. */\n variant?: 'header' | 'toolbar';\n}\n\ninterface IDataTableColumnToggleItemProps {\n id: string;\n header: string;\n isVisible: boolean;\n onToggleVisibility: () => void;\n}\n\nconst DataTableColumnToggleItem = ({ id, header, isVisible, onToggleVisibility }: IDataTableColumnToggleItemProps) => {\n const { t } = useTranslation('ui');\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n };\n\n return (\n <li\n ref={setNodeRef}\n style={style}\n className={classNames('flex items-center gap-1 rounded-md px-1.5 py-1.5', {\n 'relative z-10 bg-surface shadow-md': isDragging,\n 'hover:bg-muted-hover': !isDragging,\n })}\n >\n <ControlledTooltip content={t('dataTable.reorderColumn')}>\n <ControlledButton\n aria-label={t('dataTable.reorderColumn')}\n defaultHtmlButton\n className=\"flex cursor-grab touch-none items-center text-foreground-faint hover:text-foreground-muted active:cursor-grabbing\"\n {...attributes}\n {...listeners}\n >\n <MaterialSymbol icon=\"drag_indicator\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n {/* Checkbox is display-only: pointer-events-none lets the click fall through\n to the button, so clicking anywhere (checkbox or label) toggles once. */}\n <ControlledButton\n defaultHtmlButton\n onClick={onToggleVisibility}\n className=\"flex flex-1 items-center gap-2 rounded-md px-1.5 py-1 text-left\"\n >\n <span className=\"pointer-events-none flex items-center\">\n <ControlledCheckbox checked={isVisible} onChange={() => undefined} label={header} size=\"sm\" tabIndex={-1} />\n </span>\n </ControlledButton>\n </li>\n );\n};\n\nexport const DataTableColumnMenu = <R extends IDataTableRow>({\n columns,\n columnOrder,\n onColumnOrderChange,\n resetColumnOrder,\n isColumnVisible,\n onToggleColumnVisibility,\n resetColumnSizing,\n variant = 'header',\n}: IDataTableColumnMenuProps<R>) => {\n const { t } = useTranslation('ui');\n const [isOpen, setIsOpen] = useState(false);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n );\n\n const fixedStartIds = useMemo(() => getFixedStartColumns(columns).map((column) => column.id), [columns]);\n const fixedEndIds = useMemo(() => getFixedEndColumns(columns).map((column) => column.id), [columns]);\n\n const reorderableColumns = useMemo(() => getReorderableColumns(columns), [columns]);\n\n // Reorderable columns rendered in the CURRENT persisted order. Any reorderable\n // column missing from `columnOrder` (defensive) is appended in config order.\n const orderedReorderableColumns = useMemo(() => {\n const reorderableColumnById = new Map(reorderableColumns.map((column) => [column.id, column]));\n const ordered = (columnOrder ?? [])\n .map((id) => reorderableColumnById.get(id))\n .filter((column): column is IDataTableColumnConfig<R> => !!column);\n\n const orderedIds = new Set(ordered.map((column) => column.id));\n for (const column of reorderableColumns) if (!orderedIds.has(column.id)) ordered.push(column);\n\n return ordered;\n }, [reorderableColumns, columnOrder]);\n\n const sortableIds = useMemo(() => orderedReorderableColumns.map((column) => column.id), [orderedReorderableColumns]);\n\n if (reorderableColumns.length === 0) return null;\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n\n const oldIndex = sortableIds.indexOf(String(active.id));\n const newIndex = sortableIds.indexOf(String(over.id));\n if (oldIndex === -1 || newIndex === -1) return;\n\n const nextReorderableIds = arrayMove(sortableIds, oldIndex, newIndex);\n onColumnOrderChange([...fixedStartIds, ...nextReorderableIds, ...fixedEndIds]);\n };\n\n return (\n <ControlledMenuRoot open={isOpen} handler={setIsOpen} placement=\"bottom-end\" dismiss={{ itemPress: false }}>\n <ControlledTooltip content={t('dataTable.columns')}>\n <ControlledMenuHandler>\n <ControlledButton\n aria-label={t('dataTable.columns')}\n defaultHtmlButton\n className={\n variant === 'toolbar'\n ? 'inline-flex shrink-0 items-center justify-center rounded-lg border border-border bg-surface p-2.5 text-foreground-subtle shadow-sm transition-colors hover:bg-muted-hover hover:text-foreground-muted'\n : 'inline-flex items-center justify-center rounded p-1 text-foreground-faint transition-colors hover:bg-muted-active hover:text-foreground-muted'\n }\n >\n <span className=\"flex\">\n <MaterialSymbol icon=\"view_column\" size={variant === 'toolbar' ? 18 : 16} />\n </span>\n </ControlledButton>\n </ControlledMenuHandler>\n </ControlledTooltip>\n {/* MenuList gets role=\"menu\", and global CSS forces `[role='menu'] { overflow: hidden !important }`,\n so scroll must live on an inner wrapper (same pattern as ControlledSelect). z-[9999] lifts the\n portaled menu above the table's sticky cells and the app chrome so it is never covered. */}\n <ControlledMenuList className=\"z-[9999] min-w-56 overflow-hidden p-0\">\n <div className=\"max-h-80 overflow-y-auto overscroll-contain p-1\" onWheel={(event) => event.stopPropagation()}>\n <DndContext sensors={sensors} onDragEnd={handleDragEnd}>\n <SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>\n <ul className=\"flex flex-col\">\n {orderedReorderableColumns.map((columnConfig) => (\n <DataTableColumnToggleItem\n key={columnConfig.id}\n id={columnConfig.id}\n header={columnConfig.header}\n isVisible={isColumnVisible(columnConfig.id)}\n onToggleVisibility={() => onToggleColumnVisibility(columnConfig.id)}\n />\n ))}\n </ul>\n </SortableContext>\n </DndContext>\n </div>\n <div className=\"border-t border-border-subtle p-1\">\n <ControlledButton\n defaultHtmlButton\n onClick={resetColumnOrder}\n className=\"flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-xs font-medium text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"restart_alt\" size={16} />\n {t('dataTable.restoreDefaultOrder')}\n </ControlledButton>\n {resetColumnSizing && (\n <ControlledButton\n defaultHtmlButton\n onClick={resetColumnSizing}\n className=\"flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-xs font-medium text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted\"\n >\n <MaterialSymbol icon=\"width\" size={16} />\n {t('dataTable.restoreDefaultWidths')}\n </ControlledButton>\n )}\n </div>\n </ControlledMenuList>\n </ControlledMenuRoot>\n );\n};\n\n// Parent-table entry point: derives visibility from the TanStack table instance\n// so it stays the single source of truth for the main columns.\nconst DataTableColumnToggle = <T extends IDataTableRow>({\n table,\n columns,\n columnOrder,\n onColumnOrderChange,\n resetColumnOrder,\n resetColumnSizing,\n variant = 'header',\n}: IDataTableColumnToggleProps<T>) => (\n <DataTableColumnMenu\n columns={columns}\n columnOrder={columnOrder}\n onColumnOrderChange={onColumnOrderChange}\n resetColumnOrder={resetColumnOrder}\n resetColumnSizing={resetColumnSizing}\n isColumnVisible={(columnId) => table.getColumn(columnId)?.getIsVisible() ?? true}\n onToggleColumnVisibility={(columnId) => {\n const column = table.getColumn(columnId);\n column?.toggleVisibility(!(column?.getIsVisible() ?? true));\n }}\n variant={variant}\n />\n);\n\nexport default DataTableColumnToggle;\n"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,10 @@ import { useMemo } from "react";
|
|
|
3
3
|
import "classnames";
|
|
4
4
|
import "react-i18next";
|
|
5
5
|
import "../../Form/ControlledButton.js";
|
|
6
|
-
import "tailwind-merge";
|
|
7
6
|
import "../ControlledMenu.js";
|
|
8
7
|
import "motion/react";
|
|
9
8
|
import "react-dom";
|
|
9
|
+
import "../../../helpers/mergeClasses.js";
|
|
10
10
|
import "@floating-ui/dom";
|
|
11
11
|
import "../../../motion/motionPresets.js";
|
|
12
12
|
import "../../../motion/motionTokens.js";
|
|
@@ -77,6 +77,7 @@ const DataTablePagination = ({
|
|
|
77
77
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("pagination.first"), children: /* @__PURE__ */ jsx(
|
|
78
78
|
ControlledButton,
|
|
79
79
|
{
|
|
80
|
+
"aria-label": t("pagination.first"),
|
|
80
81
|
defaultHtmlButton: true,
|
|
81
82
|
disabled: !canPrev,
|
|
82
83
|
onClick: () => setCurrentPage(1),
|
|
@@ -87,6 +88,7 @@ const DataTablePagination = ({
|
|
|
87
88
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.previous"), children: /* @__PURE__ */ jsx(
|
|
88
89
|
ControlledButton,
|
|
89
90
|
{
|
|
91
|
+
"aria-label": t("actions.previous"),
|
|
90
92
|
defaultHtmlButton: true,
|
|
91
93
|
disabled: !canPrev,
|
|
92
94
|
onClick: () => setCurrentPage(currentPage - 1),
|
|
@@ -118,6 +120,7 @@ const DataTablePagination = ({
|
|
|
118
120
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("actions.next"), children: /* @__PURE__ */ jsx(
|
|
119
121
|
ControlledButton,
|
|
120
122
|
{
|
|
123
|
+
"aria-label": t("actions.next"),
|
|
121
124
|
defaultHtmlButton: true,
|
|
122
125
|
disabled: !canNext,
|
|
123
126
|
onClick: () => setCurrentPage(currentPage + 1),
|
|
@@ -128,6 +131,7 @@ const DataTablePagination = ({
|
|
|
128
131
|
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("pagination.last"), children: /* @__PURE__ */ jsx(
|
|
129
132
|
ControlledButton,
|
|
130
133
|
{
|
|
134
|
+
"aria-label": t("pagination.last"),
|
|
131
135
|
defaultHtmlButton: true,
|
|
132
136
|
disabled: !canNext,
|
|
133
137
|
onClick: () => setCurrentPage(numberOfPages),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTablePagination.js","sources":["../../../../src/components/UI/DataTable/DataTablePagination.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatNumber } from '@/helpers/formatHelpers';\n\ninterface IDataTablePaginationProps {\n pageSize: number;\n numberOfPages: number;\n currentPage: number;\n setCurrentPage: (page: number) => void;\n onPageSizeChange?: (pageSize: number) => void;\n totalItems?: number;\n}\n\nconst PAGE_SIZE_OPTIONS = [20, 50, 100];\n\nexport const DATA_TABLE_MIN_PAGE_SIZE = PAGE_SIZE_OPTIONS[0];\nconst PAGE_SIZE_SELECT_OPTIONS = PAGE_SIZE_OPTIONS.map((size) => ({ value: size.toString(), label: size.toString() }));\n\n/** Botões de página ao redor da atual, com as pontas sempre alcançáveis. */\nconst PAGE_WINDOW_RADIUS = 1;\nconst ELLIPSIS = 'ellipsis';\n\n// The shared ControlledSelect trigger ships a wide (min-w-52) field at the default\n// input height. For the pagination we want a compact, discreet picker, so we release\n// the min-widths and shrink the trigger's width and height via arbitrary variants\n// (same technique used elsewhere in the app to shrink this trigger).\nconst COMPACT_PAGE_SIZE_SELECT = classNames(\n 'w-[72px]',\n '[&_.controlled-select-input]:!min-w-0',\n '[&_.controlled-select-container]:!min-w-0',\n '[&_.controlled-select-container]:!h-8',\n '[&_.controlled-select-input]:!py-1',\n '[&_.controlled-select-input]:!text-xs',\n);\n\n/**\n * Páginas visíveis: primeira, última, a atual e uma vizinha de cada lado.\n * As reticências entram só quando existe salto real, então a barra nunca muda\n * de largura a cada clique.\n */\nconst buildPageItems = (currentPage: number, numberOfPages: number): Array<number | typeof ELLIPSIS> => {\n if (numberOfPages <= 1) return [1];\n\n const pages = new Set<number>([1, numberOfPages]);\n for (let page = currentPage - PAGE_WINDOW_RADIUS; page <= currentPage + PAGE_WINDOW_RADIUS; page += 1) {\n if (page >= 1 && page <= numberOfPages) pages.add(page);\n }\n\n const sortedPages = [...pages].sort((first, second) => first - second);\n\n return sortedPages.flatMap((page, index) => {\n const previousPage = sortedPages[index - 1];\n return previousPage !== undefined && page - previousPage > 1 ? [ELLIPSIS, page] : [page];\n });\n};\n\n/**\n * Compact, right-aligned pagination for the DataTable. Deliberately smaller and\n * more discreet than the shared TableComponentPagination.\n */\nconst DataTablePagination: React.FC<IDataTablePaginationProps> = ({\n pageSize,\n numberOfPages,\n currentPage,\n setCurrentPage,\n onPageSizeChange,\n totalItems,\n}) => {\n const { t } = useTranslation('ui');\n\n const canPrev = currentPage > 1;\n const canNext = currentPage < numberOfPages;\n const pageItems = buildPageItems(currentPage, numberOfPages);\n\n // The arrows are the only way to page on touch, so they keep a ~40px target\n // instead of the icon's own 18px box, while staying visually compact.\n const stepClass = (enabled: boolean) =>\n classNames(\n 'flex size-10 items-center justify-center rounded transition-colors tablet:size-8',\n enabled ? 'text-foreground-subtle hover:bg-muted-hover' : 'text-foreground-faint',\n );\n\n const pageClass = (isCurrent: boolean) =>\n classNames(\n 'flex size-10 items-center justify-center rounded text-xs tabular-nums transition-colors tablet:size-8',\n isCurrent\n ? 'bg-brand font-semibold text-brand-fg'\n : 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted',\n );\n\n const rangeStart = (currentPage - 1) * pageSize + 1;\n const rangeEnd = totalItems != null ? Math.min(currentPage * pageSize, totalItems) : currentPage * pageSize;\n\n return (\n <div className=\"flex flex-wrap items-center justify-end gap-x-4 gap-y-2 pb-3 text-xs text-foreground-subtle\">\n {onPageSizeChange && (\n <div className=\"flex items-center gap-1.5\">\n <span>{t('pagination.resultsPerPage')}</span>\n <ControlledSelect\n options={PAGE_SIZE_SELECT_OPTIONS}\n selectedValue={pageSize.toString()}\n onChangeHook={(selected) => onPageSizeChange(Number(selected))}\n searchable={false}\n clearable={false}\n className={COMPACT_PAGE_SIZE_SELECT}\n />\n </div>\n )}\n\n <span className=\"tabular-nums\">\n {totalItems != null\n ? t('pagination.range', {\n from: formatNumber(Math.min(rangeStart, rangeEnd)),\n to: formatNumber(rangeEnd),\n total: formatNumber(totalItems),\n })\n : t('pagination.pageOf', { current: currentPage, total: numberOfPages })}\n </span>\n\n <div className=\"flex items-center gap-0.5\">\n <ControlledTooltip content={t('pagination.first')}>\n <ControlledButton\n defaultHtmlButton\n disabled={!canPrev}\n onClick={() => setCurrentPage(1)}\n className={stepClass(canPrev)}\n >\n <MaterialSymbol icon=\"first_page\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('actions.previous')}>\n <ControlledButton\n defaultHtmlButton\n disabled={!canPrev}\n onClick={() => setCurrentPage(currentPage - 1)}\n className={stepClass(canPrev)}\n >\n <MaterialSymbol icon=\"chevron_left\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n\n {pageItems.map((item, index) =>\n item === ELLIPSIS ? (\n <span\n key={`data-table-pagination-gap-${index}`}\n className=\"flex size-10 items-center justify-center text-foreground-faint tablet:size-8\"\n aria-hidden\n >\n {t('pagination.gap')}\n </span>\n ) : (\n <ControlledButton\n key={`data-table-pagination-page-${item}`}\n defaultHtmlButton\n aria-current={item === currentPage ? 'page' : undefined}\n onClick={() => setCurrentPage(item)}\n className={pageClass(item === currentPage)}\n >\n {formatNumber(item)}\n </ControlledButton>\n ),\n )}\n\n <ControlledTooltip content={t('actions.next')}>\n <ControlledButton\n defaultHtmlButton\n disabled={!canNext}\n onClick={() => setCurrentPage(currentPage + 1)}\n className={stepClass(canNext)}\n >\n <MaterialSymbol icon=\"chevron_right\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('pagination.last')}>\n <ControlledButton\n defaultHtmlButton\n disabled={!canNext}\n onClick={() => setCurrentPage(numberOfPages)}\n className={stepClass(canNext)}\n >\n <MaterialSymbol icon=\"last_page\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n </div>\n );\n};\n\nexport default DataTablePagination;\n"],"names":[],"mappings":";;;;;;;;AAkBA,MAAM,oBAAoB,CAAC,IAAI,IAAI,GAAG;AAE/B,MAAM,2BAA2B,kBAAkB,CAAC;AAC3D,MAAM,2BAA2B,kBAAkB,IAAI,CAAC,UAAU,EAAE,OAAO,KAAK,SAAA,GAAY,OAAO,KAAK,SAAA,IAAa;AAGrH,MAAM,qBAAqB;AAC3B,MAAM,WAAW;AAMjB,MAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,MAAM,iBAAiB,CAAC,aAAqB,kBAA2D;AACtG,MAAI,iBAAiB,EAAG,QAAO,CAAC,CAAC;AAEjC,QAAM,QAAQ,oBAAI,IAAY,CAAC,GAAG,aAAa,CAAC;AAChD,WAAS,OAAO,cAAc,oBAAoB,QAAQ,cAAc,oBAAoB,QAAQ,GAAG;AACrG,QAAI,QAAQ,KAAK,QAAQ,cAAe,OAAM,IAAI,IAAI;AAAA,EACxD;AAEA,QAAM,cAAc,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,OAAO,WAAW,QAAQ,MAAM;AAErE,SAAO,YAAY,QAAQ,CAAC,MAAM,UAAU;AAC1C,UAAM,eAAe,YAAY,QAAQ,CAAC;AAC1C,WAAO,iBAAiB,UAAa,OAAO,eAAe,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI;AAAA,EACzF,CAAC;AACH;AAMA,MAAM,sBAA2D,CAAC;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,UAAU,cAAc;AAC9B,QAAM,UAAU,cAAc;AAC9B,QAAM,YAAY,eAAe,aAAa,aAAa;AAI3D,QAAM,YAAY,CAAC,YACjB;AAAA,IACE;AAAA,IACA,UAAU,gDAAgD;AAAA,EAAA;AAG9D,QAAM,YAAY,CAAC,cACjB;AAAA,IACE;AAAA,IACA,YACI,yCACA;AAAA,EAAA;AAGR,QAAM,cAAc,cAAc,KAAK,WAAW;AAClD,QAAM,WAAW,cAAc,OAAO,KAAK,IAAI,cAAc,UAAU,UAAU,IAAI,cAAc;AAEnG,SACE,qBAAC,OAAA,EAAI,WAAU,+FACZ,UAAA;AAAA,IAAA,oBACC,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAM,UAAA,EAAE,2BAA2B,EAAA,CAAE;AAAA,MACtC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS;AAAA,UACT,eAAe,SAAS,SAAA;AAAA,UACxB,cAAc,CAAC,aAAa,iBAAiB,OAAO,QAAQ,CAAC;AAAA,UAC7D,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,WAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IACb,GACF;AAAA,wBAGD,QAAA,EAAK,WAAU,gBACb,UAAA,cAAc,OACX,EAAE,oBAAoB;AAAA,MACpB,MAAM,aAAa,KAAK,IAAI,YAAY,QAAQ,CAAC;AAAA,MACjD,IAAI,aAAa,QAAQ;AAAA,MACzB,OAAO,aAAa,UAAU;AAAA,IAAA,CAC/B,IACD,EAAE,qBAAqB,EAAE,SAAS,aAAa,OAAO,cAAA,CAAe,GAC3E;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,MAAA,oBAAC,mBAAA,EAAkB,SAAS,EAAE,kBAAkB,GAC9C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,eAAe,CAAC;AAAA,UAC/B,WAAW,UAAU,OAAO;AAAA,UAE5B,UAAA,oBAAC,gBAAA,EAAe,MAAK,cAAa,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,GAEhD;AAAA,MACA,oBAAC,mBAAA,EAAkB,SAAS,EAAE,kBAAkB,GAC9C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,eAAe,cAAc,CAAC;AAAA,UAC7C,WAAW,UAAU,OAAO;AAAA,UAE5B,UAAA,oBAAC,gBAAA,EAAe,MAAK,gBAAe,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,GAElD;AAAA,MAEC,UAAU;AAAA,QAAI,CAAC,MAAM,UACpB,SAAS,WACP;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAU;AAAA,YACV,eAAW;AAAA,YAEV,YAAE,gBAAgB;AAAA,UAAA;AAAA,UAJd,6BAA6B,KAAK;AAAA,QAAA,IAOzC;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,mBAAiB;AAAA,YACjB,gBAAc,SAAS,cAAc,SAAS;AAAA,YAC9C,SAAS,MAAM,eAAe,IAAI;AAAA,YAClC,WAAW,UAAU,SAAS,WAAW;AAAA,YAExC,uBAAa,IAAI;AAAA,UAAA;AAAA,UANb,8BAA8B,IAAI;AAAA,QAAA;AAAA,MAOzC;AAAA,MAIJ,oBAAC,mBAAA,EAAkB,SAAS,EAAE,cAAc,GAC1C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,eAAe,cAAc,CAAC;AAAA,UAC7C,WAAW,UAAU,OAAO;AAAA,UAE5B,UAAA,oBAAC,gBAAA,EAAe,MAAK,iBAAgB,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,GAEnD;AAAA,MACA,oBAAC,mBAAA,EAAkB,SAAS,EAAE,iBAAiB,GAC7C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,eAAe,aAAa;AAAA,UAC3C,WAAW,UAAU,OAAO;AAAA,UAE5B,UAAA,oBAAC,gBAAA,EAAe,MAAK,aAAY,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,EAC7C,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"DataTablePagination.js","sources":["../../../../src/components/UI/DataTable/DataTablePagination.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatNumber } from '@/helpers/formatHelpers';\n\ninterface IDataTablePaginationProps {\n pageSize: number;\n numberOfPages: number;\n currentPage: number;\n setCurrentPage: (page: number) => void;\n onPageSizeChange?: (pageSize: number) => void;\n totalItems?: number;\n}\n\nconst PAGE_SIZE_OPTIONS = [20, 50, 100];\n\nexport const DATA_TABLE_MIN_PAGE_SIZE = PAGE_SIZE_OPTIONS[0];\nconst PAGE_SIZE_SELECT_OPTIONS = PAGE_SIZE_OPTIONS.map((size) => ({ value: size.toString(), label: size.toString() }));\n\n/** Botões de página ao redor da atual, com as pontas sempre alcançáveis. */\nconst PAGE_WINDOW_RADIUS = 1;\nconst ELLIPSIS = 'ellipsis';\n\n// The shared ControlledSelect trigger ships a wide (min-w-52) field at the default\n// input height. For the pagination we want a compact, discreet picker, so we release\n// the min-widths and shrink the trigger's width and height via arbitrary variants\n// (same technique used elsewhere in the app to shrink this trigger).\nconst COMPACT_PAGE_SIZE_SELECT = classNames(\n 'w-[72px]',\n '[&_.controlled-select-input]:!min-w-0',\n '[&_.controlled-select-container]:!min-w-0',\n '[&_.controlled-select-container]:!h-8',\n '[&_.controlled-select-input]:!py-1',\n '[&_.controlled-select-input]:!text-xs',\n);\n\n/**\n * Páginas visíveis: primeira, última, a atual e uma vizinha de cada lado.\n * As reticências entram só quando existe salto real, então a barra nunca muda\n * de largura a cada clique.\n */\nconst buildPageItems = (currentPage: number, numberOfPages: number): Array<number | typeof ELLIPSIS> => {\n if (numberOfPages <= 1) return [1];\n\n const pages = new Set<number>([1, numberOfPages]);\n for (let page = currentPage - PAGE_WINDOW_RADIUS; page <= currentPage + PAGE_WINDOW_RADIUS; page += 1) {\n if (page >= 1 && page <= numberOfPages) pages.add(page);\n }\n\n const sortedPages = [...pages].sort((first, second) => first - second);\n\n return sortedPages.flatMap((page, index) => {\n const previousPage = sortedPages[index - 1];\n return previousPage !== undefined && page - previousPage > 1 ? [ELLIPSIS, page] : [page];\n });\n};\n\n/**\n * Compact, right-aligned pagination for the DataTable. Deliberately smaller and\n * more discreet than the shared TableComponentPagination.\n */\nconst DataTablePagination: React.FC<IDataTablePaginationProps> = ({\n pageSize,\n numberOfPages,\n currentPage,\n setCurrentPage,\n onPageSizeChange,\n totalItems,\n}) => {\n const { t } = useTranslation('ui');\n\n const canPrev = currentPage > 1;\n const canNext = currentPage < numberOfPages;\n const pageItems = buildPageItems(currentPage, numberOfPages);\n\n // The arrows are the only way to page on touch, so they keep a ~40px target\n // instead of the icon's own 18px box, while staying visually compact.\n const stepClass = (enabled: boolean) =>\n classNames(\n 'flex size-10 items-center justify-center rounded transition-colors tablet:size-8',\n enabled ? 'text-foreground-subtle hover:bg-muted-hover' : 'text-foreground-faint',\n );\n\n const pageClass = (isCurrent: boolean) =>\n classNames(\n 'flex size-10 items-center justify-center rounded text-xs tabular-nums transition-colors tablet:size-8',\n isCurrent\n ? 'bg-brand font-semibold text-brand-fg'\n : 'text-foreground-subtle hover:bg-muted-hover hover:text-foreground-muted',\n );\n\n const rangeStart = (currentPage - 1) * pageSize + 1;\n const rangeEnd = totalItems != null ? Math.min(currentPage * pageSize, totalItems) : currentPage * pageSize;\n\n return (\n <div className=\"flex flex-wrap items-center justify-end gap-x-4 gap-y-2 pb-3 text-xs text-foreground-subtle\">\n {onPageSizeChange && (\n <div className=\"flex items-center gap-1.5\">\n <span>{t('pagination.resultsPerPage')}</span>\n <ControlledSelect\n options={PAGE_SIZE_SELECT_OPTIONS}\n selectedValue={pageSize.toString()}\n onChangeHook={(selected) => onPageSizeChange(Number(selected))}\n searchable={false}\n clearable={false}\n className={COMPACT_PAGE_SIZE_SELECT}\n />\n </div>\n )}\n\n <span className=\"tabular-nums\">\n {totalItems != null\n ? t('pagination.range', {\n from: formatNumber(Math.min(rangeStart, rangeEnd)),\n to: formatNumber(rangeEnd),\n total: formatNumber(totalItems),\n })\n : t('pagination.pageOf', { current: currentPage, total: numberOfPages })}\n </span>\n\n <div className=\"flex items-center gap-0.5\">\n <ControlledTooltip content={t('pagination.first')}>\n <ControlledButton\n aria-label={t('pagination.first')}\n defaultHtmlButton\n disabled={!canPrev}\n onClick={() => setCurrentPage(1)}\n className={stepClass(canPrev)}\n >\n <MaterialSymbol icon=\"first_page\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('actions.previous')}>\n <ControlledButton\n aria-label={t('actions.previous')}\n defaultHtmlButton\n disabled={!canPrev}\n onClick={() => setCurrentPage(currentPage - 1)}\n className={stepClass(canPrev)}\n >\n <MaterialSymbol icon=\"chevron_left\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n\n {pageItems.map((item, index) =>\n item === ELLIPSIS ? (\n <span\n key={`data-table-pagination-gap-${index}`}\n className=\"flex size-10 items-center justify-center text-foreground-faint tablet:size-8\"\n aria-hidden\n >\n {t('pagination.gap')}\n </span>\n ) : (\n <ControlledButton\n key={`data-table-pagination-page-${item}`}\n defaultHtmlButton\n aria-current={item === currentPage ? 'page' : undefined}\n onClick={() => setCurrentPage(item)}\n className={pageClass(item === currentPage)}\n >\n {formatNumber(item)}\n </ControlledButton>\n ),\n )}\n\n <ControlledTooltip content={t('actions.next')}>\n <ControlledButton\n aria-label={t('actions.next')}\n defaultHtmlButton\n disabled={!canNext}\n onClick={() => setCurrentPage(currentPage + 1)}\n className={stepClass(canNext)}\n >\n <MaterialSymbol icon=\"chevron_right\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('pagination.last')}>\n <ControlledButton\n aria-label={t('pagination.last')}\n defaultHtmlButton\n disabled={!canNext}\n onClick={() => setCurrentPage(numberOfPages)}\n className={stepClass(canNext)}\n >\n <MaterialSymbol icon=\"last_page\" size={18} />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n </div>\n );\n};\n\nexport default DataTablePagination;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
|
-
import { twMerge } from "tailwind-merge";
|
|
4
3
|
import NoDataFound from "../NoDataFound/index.js";
|
|
5
4
|
import SkeletonLoading from "../SkeletonLoading.js";
|
|
5
|
+
import { mergeClasses } from "../../../helpers/mergeClasses.js";
|
|
6
6
|
import { describeUiError } from "../../../helpers/uiErrorPresenter.js";
|
|
7
7
|
const DEFAULT_SKELETON_ROWS = 8;
|
|
8
8
|
const DataTableSkeletonState = ({ rows = DEFAULT_SKELETON_ROWS }) => /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 py-4", children: Array.from({ length: Math.max(1, rows) }).map((_, index) => /* @__PURE__ */ jsx(SkeletonLoading, { className: "h-9" }, `data-table-skeleton-${index}`)) });
|
|
@@ -12,7 +12,7 @@ const DataTableCardsSkeletonState = ({ cardClassName }) => (
|
|
|
12
12
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-center gap-4 py-2 @container @md:justify-start", children: Array.from({ length: 12 }).map((_, index) => /* @__PURE__ */ jsxs(
|
|
13
13
|
"div",
|
|
14
14
|
{
|
|
15
|
-
className:
|
|
15
|
+
className: mergeClasses(
|
|
16
16
|
"w-52 animate-pulse overflow-hidden rounded-lg border border-border-subtle bg-surface",
|
|
17
17
|
cardClassName
|
|
18
18
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTableStates.js","sources":["../../../../src/components/UI/DataTable/DataTableStates.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\
|
|
1
|
+
{"version":3,"file":"DataTableStates.js","sources":["../../../../src/components/UI/DataTable/DataTableStates.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport SkeletonLoading from '@/components/UI/SkeletonLoading';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { describeUiError } from '@/helpers/uiErrorPresenter';\n\nconst DEFAULT_SKELETON_ROWS = 8;\n\nexport const DataTableSkeletonState: React.FC<{ rows?: number }> = ({ rows = DEFAULT_SKELETON_ROWS }) => (\n <div className=\"flex flex-col gap-2 py-4\">\n {Array.from({ length: Math.max(1, rows) }).map((_, index) => (\n <SkeletonLoading key={`data-table-skeleton-${index}`} className=\"h-9\" />\n ))}\n </div>\n);\n\nexport const DataTableCardsSkeletonState: React.FC<{ cardClassName?: string }> = ({ cardClassName }) => (\n // Mesmo contêiner e mesmo alinhamento da grade real: sem o `@container` aqui, um\n // card de largura responsiva não teria contra o que medir enquanto carrega.\n <div className=\"flex flex-wrap justify-center gap-4 py-2 @container @md:justify-start\">\n {Array.from({ length: 12 }).map((_, index) => (\n <div\n key={`data-table-card-skeleton-${index}`}\n className={mergeClasses(\n 'w-52 animate-pulse overflow-hidden rounded-lg border border-border-subtle bg-surface',\n cardClassName,\n )}\n >\n <div className=\"aspect-square w-full bg-muted-active\" />\n <div className=\"flex flex-col gap-2 p-3\">\n <SkeletonLoading className=\"h-4 w-3/4\" />\n <SkeletonLoading className=\"h-3 w-1/2\" />\n <SkeletonLoading className=\"h-3 w-2/3\" />\n <SkeletonLoading className=\"mt-1 h-3 w-1/3\" />\n </div>\n </div>\n ))}\n </div>\n);\n\ninterface IDataTableErrorStateProps {\n onRetry?: () => void;\n /** The caught error, so a denial or a dropped connection does not read as a system failure. */\n errorCause?: unknown;\n}\n\nexport const DataTableErrorState: React.FC<IDataTableErrorStateProps> = ({ onRetry, errorCause }) => {\n const { t } = useTranslation('ui');\n const presentation = describeUiError(errorCause);\n const retryAction = onRetry\n ? { label: t('actions.tryAgain'), onClick: onRetry, icon: 'refresh' as const }\n : undefined;\n\n if (!presentation) {\n return <NoDataFound variant=\"error\" title={t('dataTable.error')} action={retryAction} />;\n }\n\n return (\n <NoDataFound\n variant={presentation.variant}\n title={t(presentation.titleKey)}\n description={t(presentation.descriptionKey)}\n action={presentation.isRetryable ? retryAction : undefined}\n />\n );\n};\n\ninterface IDataTableEmptyStateProps {\n emptyMessage?: string;\n}\n\nexport const DataTableEmptyState: React.FC<IDataTableEmptyStateProps> = ({ emptyMessage }) => {\n const { t } = useTranslation('ui');\n\n return <NoDataFound message={emptyMessage ?? t('dataTable.empty')} />;\n};\n"],"names":[],"mappings":";;;;;;AAOA,MAAM,wBAAwB;AAEvB,MAAM,yBAAsD,CAAC,EAAE,OAAO,4BAC3E,oBAAC,OAAA,EAAI,WAAU,4BACZ,UAAA,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,EAAA,CAAG,EAAE,IAAI,CAAC,GAAG,UACjD,oBAAC,iBAAA,EAAqD,WAAU,MAAA,GAA1C,uBAAuB,KAAK,EAAoB,CACvE,EAAA,CACH;AAGK,MAAM,8BAAoE,CAAC,EAAE,cAAA;AAAA;AAAA;AAAA,EAGlF,oBAAC,OAAA,EAAI,WAAU,yEACZ,gBAAM,KAAK,EAAE,QAAQ,GAAA,CAAI,EAAE,IAAI,CAAC,GAAG,UAClC;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,WAAU,uCAAA,CAAuC;AAAA,QACtD,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,UAAA,oBAAC,iBAAA,EAAgB,WAAU,YAAA,CAAY;AAAA,UACvC,oBAAC,iBAAA,EAAgB,WAAU,YAAA,CAAY;AAAA,UACvC,oBAAC,iBAAA,EAAgB,WAAU,YAAA,CAAY;AAAA,UACvC,oBAAC,iBAAA,EAAgB,WAAU,iBAAA,CAAiB;AAAA,QAAA,EAAA,CAC9C;AAAA,MAAA;AAAA,IAAA;AAAA,IAZK,4BAA4B,KAAK;AAAA,EAAA,CAczC,EAAA,CACH;AAAA;AASK,MAAM,sBAA2D,CAAC,EAAE,SAAS,iBAAiB;AACnG,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,eAAe,gBAAgB,UAAU;AAC/C,QAAM,cAAc,UAChB,EAAE,OAAO,EAAE,kBAAkB,GAAG,SAAS,SAAS,MAAM,UAAA,IACxD;AAEJ,MAAI,CAAC,cAAc;AACjB,WAAO,oBAAC,eAAY,SAAQ,SAAQ,OAAO,EAAE,iBAAiB,GAAG,QAAQ,YAAA,CAAa;AAAA,EACxF;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAS,aAAa;AAAA,MACtB,OAAO,EAAE,aAAa,QAAQ;AAAA,MAC9B,aAAa,EAAE,aAAa,cAAc;AAAA,MAC1C,QAAQ,aAAa,cAAc,cAAc;AAAA,IAAA;AAAA,EAAA;AAGvD;AAMO,MAAM,sBAA2D,CAAC,EAAE,mBAAmB;AAC5F,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,6BAAQ,aAAA,EAAY,SAAS,gBAAgB,EAAE,iBAAiB,GAAG;AACrE;"}
|
|
@@ -4,10 +4,10 @@ import ControlledViewToggle from "../../Form/ControlledViewToggle.js";
|
|
|
4
4
|
import "classnames";
|
|
5
5
|
import "../../Form/ControlledButton.js";
|
|
6
6
|
import "react";
|
|
7
|
-
import "tailwind-merge";
|
|
8
7
|
import "../ControlledMenu.js";
|
|
9
8
|
import "motion/react";
|
|
10
9
|
import "react-dom";
|
|
10
|
+
import "../../../helpers/mergeClasses.js";
|
|
11
11
|
import "@floating-ui/dom";
|
|
12
12
|
import "../../../motion/motionPresets.js";
|
|
13
13
|
import "../../../motion/motionTokens.js";
|
|
@@ -3,10 +3,10 @@ import "classnames";
|
|
|
3
3
|
import "react-i18next";
|
|
4
4
|
import "../../Form/ControlledButton.js";
|
|
5
5
|
import "react";
|
|
6
|
-
import "tailwind-merge";
|
|
7
6
|
import "../ControlledMenu.js";
|
|
8
7
|
import "motion/react";
|
|
9
8
|
import "react-dom";
|
|
9
|
+
import "../../../helpers/mergeClasses.js";
|
|
10
10
|
import "@floating-ui/dom";
|
|
11
11
|
import "../../../motion/motionPresets.js";
|
|
12
12
|
import "../../../motion/motionTokens.js";
|
|
@@ -68,6 +68,7 @@ const FlowBlockNode = ({ id, data, selected }) => {
|
|
|
68
68
|
trigger: /* @__PURE__ */ jsx(
|
|
69
69
|
ControlledButton,
|
|
70
70
|
{
|
|
71
|
+
"aria-label": t("flow.node.addBlock"),
|
|
71
72
|
defaultHtmlButton: true,
|
|
72
73
|
className: "interactive flex size-6 items-center justify-center rounded-full border border-border-emphasis bg-surface text-foreground-muted shadow-sm hover:border-brand-border hover:bg-brand-soft hover:text-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
73
74
|
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "add", size: 16, "aria-hidden": true })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlowBlockNode.js","sources":["../../../../src/components/UI/Flow/FlowBlockNode.tsx"],"sourcesContent":["import { ReactNode, useEffect } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport { MaterialSymbol, type SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Handle, type Node, type NodeProps, Position, useUpdateNodeInternals } from '@xyflow/react';\n\nimport FlowNodeShell from './FlowNodeShell';\nimport type { TFlowIssueSeverity, TFlowPortId, TFlowRunStepStatus } from './flowTypes';\n\nexport const FLOW_INPUT_HANDLE_ID = 'in';\n\nexport interface IFlowAddableBlock {\n type: string;\n label: string;\n icon: SymbolCodepoints;\n}\n\nexport interface IFlowBlockNodeData extends Record<string, unknown> {\n icon: SymbolCodepoints;\n title: string;\n categoryLabel: string;\n summary?: ReactNode;\n hasInput: boolean;\n outputs: Array<TFlowPortId>;\n /** Ports already wired: their edge carries the label, so the port does not repeat it. */\n connectedPorts: Array<TFlowPortId>;\n portLabels: Record<TFlowPortId, string>;\n /** What a free port can grow into; empty in a read-only canvas. */\n addableBlocks: Array<IFlowAddableBlock>;\n onAddFromPort?: (port: TFlowPortId, type: string) => void;\n disabled?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n issueCount: number;\n runStatus?: TFlowRunStepStatus;\n}\n\nexport type TFlowBlockNode = Node<IFlowBlockNodeData, 'flowBlock'>;\n\nconst portTop = (index: number, count: number): string => `${((index + 1) / (count + 1)) * 100}%`;\n\n/**\n * Input on the left edge, output ports spread along the right edge; a lone `next` port needs no\n * label. A free port shows a \"+\" that adds the next block already wired to it.\n */\nconst FlowBlockNode: React.FC<NodeProps<TFlowBlockNode>> = ({ id, data, selected }) => {\n const { t } = useTranslation('ui');\n const updateNodeInternals = useUpdateNodeInternals();\n const outputs = Array.isArray(data.outputs) ? data.outputs : [];\n const handleSignature = `${data.hasInput ? FLOW_INPUT_HANDLE_ID : ''}|${outputs.join(',')}`;\n\n // React Flow measures handle positions only when the node is resized. An edge whose handle was\n // not in that measurement is never drawn, so remeasure after mount and whenever the ports change.\n useEffect(() => {\n updateNodeInternals(id);\n }, [id, handleSignature, updateNodeInternals]);\n const connectedPorts = Array.isArray(data.connectedPorts) ? data.connectedPorts : [];\n const addableBlocks = Array.isArray(data.addableBlocks) ? data.addableBlocks : [];\n const showPortLabels = !(outputs.length === 1 && outputs[0] === 'next');\n const canAdd = Boolean(data.onAddFromPort) && addableBlocks.length > 0;\n\n return (\n <FlowNodeShell\n icon={data.icon}\n title={data.title}\n categoryLabel={data.categoryLabel}\n summary={data.summary}\n selected={selected}\n disabled={data.disabled}\n issueSeverity={data.issueSeverity}\n issueCount={data.issueCount}\n runStatus={data.runStatus}\n className={showPortLabels ? 'pr-12' : undefined}\n >\n {data.hasInput && (\n <Handle id={FLOW_INPUT_HANDLE_ID} type=\"target\" position={Position.Left} className=\"umode-flow-handle\" />\n )}\n {outputs.map((port, index) => (\n <Handle\n key={port}\n id={port}\n type=\"source\"\n position={Position.Right}\n className=\"umode-flow-handle\"\n data-port={port}\n style={{ top: portTop(index, outputs.length) }}\n >\n {showPortLabels && !connectedPorts.includes(port) && (\n <span className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 whitespace-nowrap text-xxs font-semibold text-foreground-subtle\">\n {data.portLabels[port]}\n </span>\n )}\n </Handle>\n ))}\n {canAdd &&\n outputs.map((port, index) =>\n connectedPorts.includes(port) ? null : (\n <div\n key={`add|${port}`}\n className=\"nodrag nopan absolute -right-10 -translate-y-1/2\"\n style={{ top: portTop(index, outputs.length) }}\n >\n <ControlledMenu\n placement=\"right-start\"\n triggerTooltip={t('flow.node.addBlock')}\n items={addableBlocks.map((block) => ({\n key: block.type,\n label: block.label,\n icon: block.icon,\n onClick: () => data.onAddFromPort?.(port, block.type),\n }))}\n trigger={\n <ControlledButton\n defaultHtmlButton\n className=\"interactive flex size-6 items-center justify-center rounded-full border border-border-emphasis bg-surface text-foreground-muted shadow-sm hover:border-brand-border hover:bg-brand-soft hover:text-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"add\" size={16} aria-hidden />\n </ControlledButton>\n }\n />\n </div>\n ),\n )}\n </FlowNodeShell>\n );\n};\n\nexport default FlowBlockNode;\n"],"names":[],"mappings":";;;;;;;;AAWO,MAAM,uBAAuB;AA6BpC,MAAM,UAAU,CAAC,OAAe,UAA0B,IAAK,QAAQ,MAAM,QAAQ,KAAM,GAAG;AAM9F,MAAM,gBAAqD,CAAC,EAAE,IAAI,MAAM,eAAe;AACrF,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,sBAAsB,uBAAA;AAC5B,QAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAA;AAC7D,QAAM,kBAAkB,GAAG,KAAK,WAAW,uBAAuB,EAAE,IAAI,QAAQ,KAAK,GAAG,CAAC;AAIzF,YAAU,MAAM;AACd,wBAAoB,EAAE;AAAA,EACxB,GAAG,CAAC,IAAI,iBAAiB,mBAAmB,CAAC;AAC7C,QAAM,iBAAiB,MAAM,QAAQ,KAAK,cAAc,IAAI,KAAK,iBAAiB,CAAA;AAClF,QAAM,gBAAgB,MAAM,QAAQ,KAAK,aAAa,IAAI,KAAK,gBAAgB,CAAA;AAC/E,QAAM,iBAAiB,EAAE,QAAQ,WAAW,KAAK,QAAQ,CAAC,MAAM;AAChE,QAAM,SAAS,QAAQ,KAAK,aAAa,KAAK,cAAc,SAAS;AAErE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,eAAe,KAAK;AAAA,MACpB,SAAS,KAAK;AAAA,MACd;AAAA,MACA,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,MAChB,WAAW,iBAAiB,UAAU;AAAA,MAErC,UAAA;AAAA,QAAA,KAAK,YACJ,oBAAC,QAAA,EAAO,IAAI,sBAAsB,MAAK,UAAS,UAAU,SAAS,MAAM,WAAU,oBAAA,CAAoB;AAAA,QAExG,QAAQ,IAAI,CAAC,MAAM,UAClB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,IAAI;AAAA,YACJ,MAAK;AAAA,YACL,UAAU,SAAS;AAAA,YACnB,WAAU;AAAA,YACV,aAAW;AAAA,YACX,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,YAE1C,UAAA,kBAAkB,CAAC,eAAe,SAAS,IAAI,KAC9C,oBAAC,QAAA,EAAK,WAAU,iIACb,UAAA,KAAK,WAAW,IAAI,EAAA,CACvB;AAAA,UAAA;AAAA,UAXG;AAAA,QAAA,CAcR;AAAA,QACA,UACC,QAAQ;AAAA,UAAI,CAAC,MAAM,UACjB,eAAe,SAAS,IAAI,IAAI,OAC9B;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,WAAU;AAAA,cACV,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,cAE3C,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAU;AAAA,kBACV,gBAAgB,EAAE,oBAAoB;AAAA,kBACtC,OAAO,cAAc,IAAI,CAAC,WAAW;AAAA,oBACnC,KAAK,MAAM;AAAA,oBACX,OAAO,MAAM;AAAA,oBACb,MAAM,MAAM;AAAA,oBACZ,SAAS,MAAM,KAAK,gBAAgB,MAAM,MAAM,IAAI;AAAA,kBAAA,EACpD;AAAA,kBACF,SACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,mBAAiB;AAAA,sBACjB,WAAU;AAAA,sBAEV,8BAAC,gBAAA,EAAe,MAAK,OAAM,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACnD;AAAA,cAAA;AAAA,YAEJ;AAAA,
|
|
1
|
+
{"version":3,"file":"FlowBlockNode.js","sources":["../../../../src/components/UI/Flow/FlowBlockNode.tsx"],"sourcesContent":["import { ReactNode, useEffect } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport { MaterialSymbol, type SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Handle, type Node, type NodeProps, Position, useUpdateNodeInternals } from '@xyflow/react';\n\nimport FlowNodeShell from './FlowNodeShell';\nimport type { TFlowIssueSeverity, TFlowPortId, TFlowRunStepStatus } from './flowTypes';\n\nexport const FLOW_INPUT_HANDLE_ID = 'in';\n\nexport interface IFlowAddableBlock {\n type: string;\n label: string;\n icon: SymbolCodepoints;\n}\n\nexport interface IFlowBlockNodeData extends Record<string, unknown> {\n icon: SymbolCodepoints;\n title: string;\n categoryLabel: string;\n summary?: ReactNode;\n hasInput: boolean;\n outputs: Array<TFlowPortId>;\n /** Ports already wired: their edge carries the label, so the port does not repeat it. */\n connectedPorts: Array<TFlowPortId>;\n portLabels: Record<TFlowPortId, string>;\n /** What a free port can grow into; empty in a read-only canvas. */\n addableBlocks: Array<IFlowAddableBlock>;\n onAddFromPort?: (port: TFlowPortId, type: string) => void;\n disabled?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n issueCount: number;\n runStatus?: TFlowRunStepStatus;\n}\n\nexport type TFlowBlockNode = Node<IFlowBlockNodeData, 'flowBlock'>;\n\nconst portTop = (index: number, count: number): string => `${((index + 1) / (count + 1)) * 100}%`;\n\n/**\n * Input on the left edge, output ports spread along the right edge; a lone `next` port needs no\n * label. A free port shows a \"+\" that adds the next block already wired to it.\n */\nconst FlowBlockNode: React.FC<NodeProps<TFlowBlockNode>> = ({ id, data, selected }) => {\n const { t } = useTranslation('ui');\n const updateNodeInternals = useUpdateNodeInternals();\n const outputs = Array.isArray(data.outputs) ? data.outputs : [];\n const handleSignature = `${data.hasInput ? FLOW_INPUT_HANDLE_ID : ''}|${outputs.join(',')}`;\n\n // React Flow measures handle positions only when the node is resized. An edge whose handle was\n // not in that measurement is never drawn, so remeasure after mount and whenever the ports change.\n useEffect(() => {\n updateNodeInternals(id);\n }, [id, handleSignature, updateNodeInternals]);\n const connectedPorts = Array.isArray(data.connectedPorts) ? data.connectedPorts : [];\n const addableBlocks = Array.isArray(data.addableBlocks) ? data.addableBlocks : [];\n const showPortLabels = !(outputs.length === 1 && outputs[0] === 'next');\n const canAdd = Boolean(data.onAddFromPort) && addableBlocks.length > 0;\n\n return (\n <FlowNodeShell\n icon={data.icon}\n title={data.title}\n categoryLabel={data.categoryLabel}\n summary={data.summary}\n selected={selected}\n disabled={data.disabled}\n issueSeverity={data.issueSeverity}\n issueCount={data.issueCount}\n runStatus={data.runStatus}\n className={showPortLabels ? 'pr-12' : undefined}\n >\n {data.hasInput && (\n <Handle id={FLOW_INPUT_HANDLE_ID} type=\"target\" position={Position.Left} className=\"umode-flow-handle\" />\n )}\n {outputs.map((port, index) => (\n <Handle\n key={port}\n id={port}\n type=\"source\"\n position={Position.Right}\n className=\"umode-flow-handle\"\n data-port={port}\n style={{ top: portTop(index, outputs.length) }}\n >\n {showPortLabels && !connectedPorts.includes(port) && (\n <span className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 whitespace-nowrap text-xxs font-semibold text-foreground-subtle\">\n {data.portLabels[port]}\n </span>\n )}\n </Handle>\n ))}\n {canAdd &&\n outputs.map((port, index) =>\n connectedPorts.includes(port) ? null : (\n <div\n key={`add|${port}`}\n className=\"nodrag nopan absolute -right-10 -translate-y-1/2\"\n style={{ top: portTop(index, outputs.length) }}\n >\n <ControlledMenu\n placement=\"right-start\"\n triggerTooltip={t('flow.node.addBlock')}\n items={addableBlocks.map((block) => ({\n key: block.type,\n label: block.label,\n icon: block.icon,\n onClick: () => data.onAddFromPort?.(port, block.type),\n }))}\n trigger={\n <ControlledButton\n aria-label={t('flow.node.addBlock')}\n defaultHtmlButton\n className=\"interactive flex size-6 items-center justify-center rounded-full border border-border-emphasis bg-surface text-foreground-muted shadow-sm hover:border-brand-border hover:bg-brand-soft hover:text-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"add\" size={16} aria-hidden />\n </ControlledButton>\n }\n />\n </div>\n ),\n )}\n </FlowNodeShell>\n );\n};\n\nexport default FlowBlockNode;\n"],"names":[],"mappings":";;;;;;;;AAWO,MAAM,uBAAuB;AA6BpC,MAAM,UAAU,CAAC,OAAe,UAA0B,IAAK,QAAQ,MAAM,QAAQ,KAAM,GAAG;AAM9F,MAAM,gBAAqD,CAAC,EAAE,IAAI,MAAM,eAAe;AACrF,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,sBAAsB,uBAAA;AAC5B,QAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAA;AAC7D,QAAM,kBAAkB,GAAG,KAAK,WAAW,uBAAuB,EAAE,IAAI,QAAQ,KAAK,GAAG,CAAC;AAIzF,YAAU,MAAM;AACd,wBAAoB,EAAE;AAAA,EACxB,GAAG,CAAC,IAAI,iBAAiB,mBAAmB,CAAC;AAC7C,QAAM,iBAAiB,MAAM,QAAQ,KAAK,cAAc,IAAI,KAAK,iBAAiB,CAAA;AAClF,QAAM,gBAAgB,MAAM,QAAQ,KAAK,aAAa,IAAI,KAAK,gBAAgB,CAAA;AAC/E,QAAM,iBAAiB,EAAE,QAAQ,WAAW,KAAK,QAAQ,CAAC,MAAM;AAChE,QAAM,SAAS,QAAQ,KAAK,aAAa,KAAK,cAAc,SAAS;AAErE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,eAAe,KAAK;AAAA,MACpB,SAAS,KAAK;AAAA,MACd;AAAA,MACA,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,MAChB,WAAW,iBAAiB,UAAU;AAAA,MAErC,UAAA;AAAA,QAAA,KAAK,YACJ,oBAAC,QAAA,EAAO,IAAI,sBAAsB,MAAK,UAAS,UAAU,SAAS,MAAM,WAAU,oBAAA,CAAoB;AAAA,QAExG,QAAQ,IAAI,CAAC,MAAM,UAClB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,IAAI;AAAA,YACJ,MAAK;AAAA,YACL,UAAU,SAAS;AAAA,YACnB,WAAU;AAAA,YACV,aAAW;AAAA,YACX,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,YAE1C,UAAA,kBAAkB,CAAC,eAAe,SAAS,IAAI,KAC9C,oBAAC,QAAA,EAAK,WAAU,iIACb,UAAA,KAAK,WAAW,IAAI,EAAA,CACvB;AAAA,UAAA;AAAA,UAXG;AAAA,QAAA,CAcR;AAAA,QACA,UACC,QAAQ;AAAA,UAAI,CAAC,MAAM,UACjB,eAAe,SAAS,IAAI,IAAI,OAC9B;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,WAAU;AAAA,cACV,OAAO,EAAE,KAAK,QAAQ,OAAO,QAAQ,MAAM,EAAA;AAAA,cAE3C,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAU;AAAA,kBACV,gBAAgB,EAAE,oBAAoB;AAAA,kBACtC,OAAO,cAAc,IAAI,CAAC,WAAW;AAAA,oBACnC,KAAK,MAAM;AAAA,oBACX,OAAO,MAAM;AAAA,oBACb,MAAM,MAAM;AAAA,oBACZ,SAAS,MAAM,KAAK,gBAAgB,MAAM,MAAM,IAAI;AAAA,kBAAA,EACpD;AAAA,kBACF,SACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,cAAY,EAAE,oBAAoB;AAAA,sBAClC,mBAAiB;AAAA,sBACjB,WAAU;AAAA,sBAEV,8BAAC,gBAAA,EAAe,MAAK,OAAM,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACnD;AAAA,cAAA;AAAA,YAEJ;AAAA,YAtBK,OAAO,IAAI;AAAA,UAAA;AAAA,QAuBlB;AAAA,MAEJ;AAAA,IAAA;AAAA,EAAA;AAGR;"}
|