From 1e9ded0355ecc1a9a46c2411267c4bd0f2c5d380 Mon Sep 17 00:00:00 2001 From: Marcin Sawicki Date: Fri, 28 Jun 2024 13:19:16 +0200 Subject: [PATCH 1/5] feat(ActionBar): logic refactor --- .../ActionBar/ActionBar.module.scss | 16 +-- .../ActionBar/ActionBar.stories.tsx | 2 +- .../src/components/ActionBar/ActionBar.tsx | 113 +++++------------- .../components/ActionBar/ActionBarItem.tsx | 5 - .../src/components/ActionBar/types.ts | 2 - 5 files changed, 35 insertions(+), 103 deletions(-) diff --git a/packages/react-components/src/components/ActionBar/ActionBar.module.scss b/packages/react-components/src/components/ActionBar/ActionBar.module.scss index 633bc79ab..5077e3d9f 100644 --- a/packages/react-components/src/components/ActionBar/ActionBar.module.scss +++ b/packages/react-components/src/components/ActionBar/ActionBar.module.scss @@ -10,17 +10,11 @@ $base-class: 'action-bar'; &__items { display: flex; flex-direction: inherit; - width: 100%; - overflow: hidden; &--scroll { overflow: auto; } - &--with-menu { - margin-right: 78px; - } - &__button-wrapper { display: flex; position: relative; @@ -64,21 +58,19 @@ $base-class: 'action-bar'; &__menu-wrapper { display: flex; - position: absolute; - top: 0; - bottom: 0; + position: relative; align-items: center; + margin-left: var(--spacing-1); &--active { @include select-indicator.select-indicator-h(); } &--vertical { - right: 0; - bottom: auto; - left: 0; align-items: normal; justify-content: center; + margin-top: var(--spacing-1); + margin-left: 0; &.#{$base-class}__menu-wrapper--active { @include select-indicator.select-indicator-v(); diff --git a/packages/react-components/src/components/ActionBar/ActionBar.stories.tsx b/packages/react-components/src/components/ActionBar/ActionBar.stories.tsx index 6d236e4b6..295ed071a 100644 --- a/packages/react-components/src/components/ActionBar/ActionBar.stories.tsx +++ b/packages/react-components/src/components/ActionBar/ActionBar.stories.tsx @@ -7,7 +7,7 @@ import { StoryDescriptor } from '../../stories/components/StoryDescriptor'; import { ActionBar } from './ActionBar'; import { getDefaultOptions } from './constants'; -const CONTAINER_SIZE = 220; +const CONTAINER_SIZE = 260; export default { title: 'Components/ActionBar', diff --git a/packages/react-components/src/components/ActionBar/ActionBar.tsx b/packages/react-components/src/components/ActionBar/ActionBar.tsx index 104b33681..3518bcd5d 100644 --- a/packages/react-components/src/components/ActionBar/ActionBar.tsx +++ b/packages/react-components/src/components/ActionBar/ActionBar.tsx @@ -8,7 +8,7 @@ import { Button } from '../Button'; import { Icon } from '../Icon'; import { ActionBarItem } from './ActionBarItem'; -import { IActionBarProps } from './types'; +import { IActionBarOption, IActionBarProps } from './types'; import styles from './ActionBar.module.scss'; @@ -24,8 +24,10 @@ export const ActionBar: React.FC = ({ vertical, menuFooter, }) => { - const [menuItemsKeys, setMenuItemsKeys] = React.useState([]); - const [menuPosition, setMenuPosition] = React.useState(0); + const [visibleItemsCount, setVisibleItemsCount] = React.useState( + options.length + ); + const [menuOptions, setMenuOptions] = React.useState([]); const [isMenuOpen, setIsMenuOpen] = React.useState(false); const isScrollType = type === 'scroll'; const mergedClassNames = cx( @@ -33,80 +35,44 @@ export const ActionBar: React.FC = ({ className, vertical && styles[`${baseClass}--vertical`] ); - const observerOptions = { - root: document.querySelector(`${id}`), - threshold: 1, - }; - const shouldDisplayMenu = !isScrollType && menuItemsKeys.length !== 0; + const singleElementSize = 44; React.useEffect(() => { if (isScrollType) { return; } - // Single element size with margin - const singleElementSize = 44; - // Extra spacing to include for menu placement - const menuPlacementSpacing = 4; - const allOptionsCount = options.length; - const hiddenOptionsCount = menuItemsKeys.length; - const visibleOptionsCount = allOptionsCount - hiddenOptionsCount; - const position = - visibleOptionsCount * singleElementSize + menuPlacementSpacing; - - setMenuPosition(position); - }, [menuItemsKeys, options, isScrollType]); - - const handleIntersection = (entries: IntersectionObserverEntry[]) => { - entries.map((entry) => { - const entryExistInMenu = menuItemsKeys.includes(entry.target.id); - - if (!entry.isIntersecting) { - entry.target.setAttribute('tabindex', '-1'); - - if (!entryExistInMenu) { - setMenuItemsKeys((prevItemKeys) => [ - ...prevItemKeys, - entry.target.id, - ]); - } - - return; - } - - entry.target.removeAttribute('tabindex'); - - if (entryExistInMenu) { - setMenuItemsKeys(menuItemsKeys.filter((i) => i !== entry.target.id)); - } - }); - }; + if (options.length !== visibleItemsCount) { + setMenuOptions(options.slice(visibleItemsCount, options.length)); + } + }, [options, visibleItemsCount]); + + const shouldDisplayMenu = !isScrollType && menuOptions.length !== 0; React.useEffect(() => { - const hasIOSupport = !!window.IntersectionObserver; + const hasIOSupport = !!window.ResizeObserver; if (!isScrollType && hasIOSupport) { - const target = document.querySelectorAll( - `button[data-actionbarid='${id}']` - ); + const observer = new ResizeObserver((entries) => { + const { width, height } = entries[0].contentRect; + const containerSize = vertical ? height : width; + const exstraSpacing = menuOptions.length > 0 ? 60 : 0; - const observer = new IntersectionObserver( - handleIntersection, - observerOptions - ); + const visibleOptionsCount = Math.floor( + (containerSize - exstraSpacing) / singleElementSize + ); + setVisibleItemsCount(visibleOptionsCount); + }); - target.forEach((e) => observer.observe(e)); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + observer.observe(document.querySelector(`#${id}`)!); return () => observer.disconnect(); } - }, [menuItemsKeys, isScrollType]); - - const getMenuItems = (keys: string[]) => { - const filteredOptions = options.filter((row) => - keys.find((k) => k === row.key && !row.hideInMenu) - ); + }, [menuOptions, isScrollType]); - return filteredOptions.map((o) => { + const getMenuItems = (menuOptions: IActionBarOption[]) => + menuOptions.map((o) => { return { key: o.key, element: ( @@ -115,37 +81,19 @@ export const ActionBar: React.FC = ({ onClick: o.onClick, }; }); - }; - - const buttonElement = options - .filter((row) => menuItemsKeys.find((i) => i === row.key)) - .find((o) => o.key === activeOptionKey); - - const getMenuPosition = (position: number, vertical?: boolean) => { - if (vertical) { - return { - top: position, - }; - } - return { - left: position, - }; - }; + const buttonElement = menuOptions.find((o) => o.key === activeOptionKey); return (
- {options.map((o) => ( + {options.slice(0, visibleItemsCount).map((o) => ( @@ -158,7 +106,6 @@ export const ActionBar: React.FC = ({ buttonElement && styles[`${menuWrapperClass}--active`], vertical && styles[`${menuWrapperClass}--vertical`] )} - style={getMenuPosition(menuPosition, vertical)} > = ({ onClose={() => setIsMenuOpen(false)} floatingStrategy="fixed" placement={vertical ? 'left-start' : 'bottom-end'} - options={getMenuItems(menuItemsKeys)} + options={getMenuItems(menuOptions)} triggerClassName={cx( vertical && styles[`${menuWrapperClass}__trigger-vertical`] )} diff --git a/packages/react-components/src/components/ActionBar/ActionBarItem.tsx b/packages/react-components/src/components/ActionBar/ActionBarItem.tsx index db9b2b91f..a0beaaae4 100644 --- a/packages/react-components/src/components/ActionBar/ActionBarItem.tsx +++ b/packages/react-components/src/components/ActionBar/ActionBarItem.tsx @@ -13,28 +13,23 @@ const baseClass = 'action-bar__items'; const menuWrapperClass = `${baseClass}__button-wrapper`; export const ActionBarItem: React.FC = ({ - id, option, - isHidden, isActive, vertical, }) => { const mergedButtonClassNames = cx(styles[menuWrapperClass], { - [styles[`${menuWrapperClass}--hidden`]]: isHidden, [styles[`${menuWrapperClass}--active`]]: isActive, [styles[`${menuWrapperClass}--vertical`]]: vertical, }); const button = (