Skip to content

Commit

Permalink
feat(ActionBar): logic refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinsawicki committed Jun 28, 2024
1 parent f0a4131 commit 1e9ded0
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
113 changes: 30 additions & 83 deletions packages/react-components/src/components/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -24,89 +24,55 @@ export const ActionBar: React.FC<IActionBarProps> = ({
vertical,
menuFooter,
}) => {
const [menuItemsKeys, setMenuItemsKeys] = React.useState<string[]>([]);
const [menuPosition, setMenuPosition] = React.useState<number>(0);
const [visibleItemsCount, setVisibleItemsCount] = React.useState<number>(
options.length
);
const [menuOptions, setMenuOptions] = React.useState<IActionBarOption[]>([]);
const [isMenuOpen, setIsMenuOpen] = React.useState<boolean>(false);
const isScrollType = type === 'scroll';
const mergedClassNames = cx(
styles[baseClass],
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: (
Expand All @@ -115,37 +81,19 @@ export const ActionBar: React.FC<IActionBarProps> = ({
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 (
<div id={id} className={mergedClassNames}>
<div
className={cx(styles[`${baseClass}__items`], {
[styles[`${baseClass}__items--scroll`]]: isScrollType,
[styles[`${baseClass}__items--with-menu`]]: shouldDisplayMenu,
})}
>
{options.map((o) => (
{options.slice(0, visibleItemsCount).map((o) => (
<ActionBarItem
id={id}
option={o}
isHidden={menuItemsKeys.includes(o.key)}
isActive={o.key === activeOptionKey}
vertical={vertical}
/>
Expand All @@ -158,15 +106,14 @@ export const ActionBar: React.FC<IActionBarProps> = ({
buttonElement && styles[`${menuWrapperClass}--active`],
vertical && styles[`${menuWrapperClass}--vertical`]
)}
style={getMenuPosition(menuPosition, vertical)}
>
<ActionMenu
selectedOptions={activeOptionKey ? [activeOptionKey] : []}
onOpen={() => setIsMenuOpen(true)}
onClose={() => setIsMenuOpen(false)}
floatingStrategy="fixed"
placement={vertical ? 'left-start' : 'bottom-end'}
options={getMenuItems(menuItemsKeys)}
options={getMenuItems(menuOptions)}
triggerClassName={cx(
vertical && styles[`${menuWrapperClass}__trigger-vertical`]
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,23 @@ const baseClass = 'action-bar__items';
const menuWrapperClass = `${baseClass}__button-wrapper`;

export const ActionBarItem: React.FC<IActionBarItem> = ({
id,
option,
isHidden,
isActive,
vertical,
}) => {
const mergedButtonClassNames = cx(styles[menuWrapperClass], {
[styles[`${menuWrapperClass}--hidden`]]: isHidden,
[styles[`${menuWrapperClass}--active`]]: isActive,
[styles[`${menuWrapperClass}--vertical`]]: vertical,
});

const button = (
<Button
data-actionbarid={id}
id={option.key}
key={option.key}
title={option?.showTooltip ? undefined : option.label}
kind="plain"
onClick={option.onClick}
icon={option.element}
disabled={isHidden}
className={styles[`${menuWrapperClass}__button`]}
/>
);
Expand Down
2 changes: 0 additions & 2 deletions packages/react-components/src/components/ActionBar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ export interface IActionBarOption {
}

export interface IActionBarItem {
id: string;
option: IActionBarOption;
isHidden: boolean;
isActive?: boolean;
vertical?: boolean;
}

0 comments on commit 1e9ded0

Please sign in to comment.