From 4df9595fb2c3654acd55437a0bbcf8d8a2fe4ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sj=C3=B6strand?= <99665802+DanielS-Qlik@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:34:21 +0200 Subject: [PATCH] fix(listbox): detach toolbar on resize (#1232) * fix: detach toolbar on resize * fix: add logical or --- .../src/components/listbox/ListBoxInline.jsx | 35 +++++++++++++------ .../__tests__/list-box-inline.test.jsx | 4 +-- .../listbox-show-toolbar-detached.test.js | 12 +++---- .../listbox-show-toolbar-detached.js | 6 ++-- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/apis/nucleus/src/components/listbox/ListBoxInline.jsx b/apis/nucleus/src/components/listbox/ListBoxInline.jsx index 469d98516..b02ab083a 100644 --- a/apis/nucleus/src/components/listbox/ListBoxInline.jsx +++ b/apis/nucleus/src/components/listbox/ListBoxInline.jsx @@ -22,6 +22,7 @@ import createSelectionState from './hooks/selections/selectionState'; import { CELL_PADDING_LEFT, ICON_WIDTH, ICON_PADDING, BUTTON_ICON_WIDTH } from './constants'; import useTempKeyboard from './components/useTempKeyboard'; import ListBoxError from './components/ListBoxError'; +import useRect from '../../hooks/useRect'; const PREFIX = 'ListBoxInline'; const classes = { @@ -110,6 +111,7 @@ function ListBoxInline({ options, layout }) { theme.listBox = addListboxTheme(themeApi); const containerRef = useRef(); + const [containerRectRef, containerRect] = useRect(); const [searchContainer, searchContainerRef] = useRefWithCallback(); const [showToolbar, setShowToolbar] = useState(false); @@ -125,6 +127,7 @@ function ListBoxInline({ options, layout }) { const isModalMode = useCallback(() => isModal({ app, appSelections }), [app, appSelections]); const isInvalid = layout?.qListObject.qDimensionInfo.qError; const errorText = isInvalid && constraints.active ? 'Visualization.Invalid.Dimension' : 'Visualization.Incomplete'; + const [isToolbarDetached, setIsToolbarDetached] = useState(false); const { handleKeyDown, handleOnMouseEnter, handleOnMouseLeave, globalKeyDown } = useMemo( () => @@ -207,13 +210,28 @@ function ListBoxInline({ options, layout }) { } }, [searchContainer && searchContainer.current, showSearch, search, focusSearch]); + const { wildCardSearch, searchEnabled, layoutOptions = {} } = layout ?? {}; + const showSearchIcon = searchEnabled !== false && search === 'toggle'; + const isLocked = layout?.qListObject?.qDimensionInfo?.qLocked === true; + const showSearchOrLockIcon = isLocked || showSearchIcon; + const isDrillDown = layout?.qListObject?.qDimensionInfo?.qGrouping === 'H'; + const showIcons = showSearchOrLockIcon || isDrillDown; + const iconsWidth = (showSearchOrLockIcon ? BUTTON_ICON_WIDTH : 0) + (isDrillDown ? ICON_WIDTH + ICON_PADDING : 0); // Drill-down icon needs padding right so there is space between the icon and the title + + useEffect(() => { + if (!titleRef.current || !containerRect) { + return; + } + + const isDetached = showToolbarDetached({ containerRect, titleRef, iconsWidth }); + setIsToolbarDetached(isDetached); + }, [titleRef.current, containerRect]); + if (!model || !layout || !translator) { return null; } - const isLocked = layout.qListObject.qDimensionInfo.qLocked === true; const isRtl = direction === 'rtl'; - const isDrillDown = layout.qListObject.qDimensionInfo.qGrouping === 'H'; const listboxSelectionToolbarItems = createListboxSelectionToolbar({ layout, model, @@ -221,7 +239,6 @@ function ListBoxInline({ options, layout }) { selectionState, }); - const { wildCardSearch, searchEnabled, layoutOptions = {} } = layout; const showTitle = true; const showSearchToggle = search === 'toggle' && showSearch; const searchVisible = (search === true || showSearchToggle) && !selectDisabled() && searchEnabled !== false; @@ -258,13 +275,8 @@ function ListBoxInline({ options, layout }) { }); const shouldAutoFocus = searchVisible && search === 'toggle'; - const showSearchIcon = searchEnabled !== false && search === 'toggle'; - const showSearchOrLockIcon = isLocked || showSearchIcon; - const showIcons = showSearchOrLockIcon || isDrillDown; - const iconsWidth = (showSearchOrLockIcon ? BUTTON_ICON_WIDTH : 0) + (isDrillDown ? ICON_WIDTH + ICON_PADDING : 0); // Drill-down icon needs padding right so there is space between the icon and the title const headerPaddingLeft = CELL_PADDING_LEFT - (showSearchOrLockIcon ? ICON_PADDING : 0); const headerPaddingRight = isRtl ? CELL_PADDING_LEFT - (showIcons ? ICON_PADDING : 0) : 0; - const isDetached = showToolbarDetached({ containerRef, titleRef, iconsWidth }); // Add a container padding for grid mode to harmonize with the grid item margins (should sum to 8px). const isGridMode = layoutOptions?.dataLayout === 'grid'; @@ -310,7 +322,10 @@ function ListBoxInline({ options, layout }) { onKeyDown={handleKeyDown} onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave} - ref={containerRef} + ref={(el) => { + containerRef.current = el; + containerRectRef(el); + }} > {showToolbarWithTitle && ( - + )} diff --git a/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx b/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx index 69e00070f..1cf4560e6 100644 --- a/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx +++ b/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx @@ -137,11 +137,11 @@ describe('', () => { useCallback .mockImplementationOnce((effectFunc, watchArr) => { - expect(watchArr[1].key).toBe('model'); + expect(watchArr).toHaveLength(0); return effectFunc; }) .mockImplementationOnce((effectFunc, watchArr) => { - expect(watchArr[1].key).toBe('model'); + expect(watchArr).toHaveLength(0); return effectFunc; }); }); diff --git a/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-show-toolbar-detached.test.js b/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-show-toolbar-detached.test.js index 07544256a..392e68bec 100644 --- a/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-show-toolbar-detached.test.js +++ b/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-show-toolbar-detached.test.js @@ -4,20 +4,20 @@ const iconsWidth = 28; describe('show listbox toolbar detached', () => { it('should return true if there is not enough space for toolbar', () => { - const containerRef = { current: { clientWidth: 100 } }; + const containerRect = { width: 100 }; const titleRef = { current: { clientWidth: 60, scrollWidth: 80, offsetWidth: 81 } }; - expect(showToolbarDetached({ containerRef, titleRef, iconsWidth })).toStrictEqual(true); + expect(showToolbarDetached({ containerRect, titleRef, iconsWidth })).toStrictEqual(true); }); it('should return true if title is truncated', () => { - const containerRef = { current: { clientWidth: 300 } }; + const containerRect = { width: 300 }; const titleRef = { current: { clientWidth: 60, scrollWidth: 200, offsetWidth: 199 } }; - expect(showToolbarDetached({ containerRef, titleRef, iconsWidth })).toStrictEqual(true); + expect(showToolbarDetached({ containerRect, titleRef, iconsWidth })).toStrictEqual(true); }); it('should return false if there is enough space for title and toolbar', () => { - const containerRef = { current: { clientWidth: 300 } }; + const containerRect = { width: 300 }; const titleRef = { current: { clientWidth: 60, scrollWidth: 200, offsetWidth: 201 } }; - expect(showToolbarDetached({ containerRef, titleRef, iconsWidth })).toStrictEqual(false); + expect(showToolbarDetached({ containerRect, titleRef, iconsWidth })).toStrictEqual(false); }); }); diff --git a/apis/nucleus/src/components/listbox/interactions/listbox-show-toolbar-detached.js b/apis/nucleus/src/components/listbox/interactions/listbox-show-toolbar-detached.js index 77a72ca56..1610e9c5b 100644 --- a/apis/nucleus/src/components/listbox/interactions/listbox-show-toolbar-detached.js +++ b/apis/nucleus/src/components/listbox/interactions/listbox-show-toolbar-detached.js @@ -1,11 +1,11 @@ -export default function showToolbarDetached({ containerRef, titleRef, iconsWidth }) { - const containerWidth = containerRef?.current?.clientWidth ?? 0; +export default function showToolbarDetached({ containerRect, titleRef, iconsWidth }) { + const containerWidth = containerRect.width; const padding = 16; const contentWidth = (titleRef?.current?.clientWidth ?? 0) + iconsWidth + padding; const actionToolbarWidth = 128; const notSufficientSpace = containerWidth < contentWidth + actionToolbarWidth; const isTruncated = titleRef?.current?.scrollWidth > titleRef?.current?.offsetWidth; - const isDetached = !!(notSufficientSpace | isTruncated); + const isDetached = !!(notSufficientSpace || isTruncated); return isDetached; }