Skip to content

Commit

Permalink
responsive list issues (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
mentaman authored Jul 25, 2021
1 parent 124d9ba commit 861314a
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 27 deletions.
42 changes: 36 additions & 6 deletions src/components/ResponsiveList/ResponsiveList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,28 @@ const ResponsiveList = forwardRef(
{
id,
className,
rootClassName,
children,
menuButtonSize,
paddingSize,
dialogZIndex,
dialogClassName,
menuButtonClassName,
resizeDebounceTime,
menuButtonAriaLabel,
menuButtonProps
},
ref
) => {
const componentRef = useRef(null);
const mergedRef = useMergeRefs({ refs: [ref, componentRef] });
const index = useElementsOverflowingIndex({ ref: componentRef, children, paddingSize, resizeDebounceTime });
const index = useElementsOverflowingIndex({
ref: componentRef,
children,
paddingSize,
resizeDebounceTime,
ignoreLast: true
});

const directChildren = useMemo(() => {
if (index === -1) {
Expand All @@ -47,19 +55,37 @@ const ResponsiveList = forwardRef(
}, [children, index]);

return (
<div ref={mergedRef} className={cx("responsive-list--wrapper", className)} id={id}>
{directChildren}
{!!menuChildren.length && (
<div className={cx("responsive-list--root", rootClassName)} id={id}>
{index !== null && (
<div className={cx("responsive-list--wrapper", className)}>
{directChildren}
{!!menuChildren.length && (
<MenuButton
componentClassName={cx("responsive-list-menu-button", menuButtonClassName)}
size={menuButtonSize}
openDialogComponentClassName={cx("responsive-list--menu-button-dialog", dialogClassName)}
zIndex={dialogZIndex}
ariaLabel={menuButtonAriaLabel}
{...menuButtonProps}
>
<div className="responsive-list-menu-wrapper-flex">{menuChildren}</div>
</MenuButton>
)}
</div>
)}
<div ref={mergedRef} className={cx("responsive-list--wrapper responsive-list--dummy", className)}>
{children}
<MenuButton
componentClassName={cx("responsive-list-menu-button", menuButtonClassName)}
size={menuButtonSize}
openDialogComponentClassName={cx("responsive-list--menu-button-dialog", dialogClassName)}
zIndex={dialogZIndex}
ariaLabel={menuButtonAriaLabel}
{...menuButtonProps}
>
<div className="responsive-list-menu-wrapper-flex">{menuChildren}</div>
<div className="responsive-list-menu-wrapper-flex" />
</MenuButton>
)}
</div>
</div>
);
}
Expand All @@ -75,6 +101,8 @@ ResponsiveList.propTypes = {
These attributes will be passed to the MenuButton
*/
menuButtonProps: PropTypes.object,
menuButtonAriaLabel: "More Actions",
rootClassName: PropTypes.string,
dialogClassName: PropTypes.string,
menuButtonSize: PropTypes.oneOf(Object.keys(ResponsiveList.menuButtonSizes)),
/**
Expand All @@ -92,6 +120,8 @@ ResponsiveList.defaultProps = {
className: "",
dialogClassName: "",
menuButtonClassName: "",
rootClassName: "",
menuButtonAriaLabel: "More Actions",
menuButtonProps: {},
menuButtonSize: ResponsiveList.menuButtonSizes.SMALL,
paddingSize: DEFAULT_MINIMAL_MARGIN,
Expand Down
8 changes: 8 additions & 0 deletions src/components/ResponsiveList/ResponsiveList.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
@import "../../styles/themes.scss";
@import "../../styles/typography.scss";

.responsive-list--root {
position: relative;
}
.responsive-list--wrapper {
display: flex;
justify-content: flex-end;
.responsive-list-menu-button {
flex-shrink: 0;
}
}
.responsive-list--dummy {
width: 100%;
position: absolute;
visibility: hidden;
}
.responsive-list--menu-button-dialog {
display: flex;
}
Expand Down
58 changes: 42 additions & 16 deletions src/components/ResponsiveList/__stories__/responsiveList.stories.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { withPerformance } from "storybook-addon-performance";
import ResponsiveList from "../ResponsiveList";
import "./responsiveListStory.scss";
Expand All @@ -16,11 +16,13 @@ import {
Alert,
Broom,
Search as SearchIcon,
CloseSmall
CloseSmall,
MoreActions
} from "../../Icon/Icons";
import { MenuItem } from "../../index";
import Menu from "../../Menu/Menu/Menu";
import DescriptionLabel from "../../storybook-helpers/description-label/description-label";
import { boolean, number } from "@storybook/addon-knobs";

function SecondaryContentComponent() {
return (
Expand All @@ -32,6 +34,7 @@ function SecondaryContentComponent() {
}

const DefaultExampleTemplate = (responseListProps) => {
const { lessItems } = responseListProps;
return <ResponsiveList id="Knobs" className="responsive-story" menuButtonSize={ResponsiveList.menuButtonSizes.MEDIUM} {...responseListProps}>
<SplitButton size={SplitButton.sizes.MAIN} marginRight secondaryDialogContent={<SecondaryContentComponent />}>
Add Item
Expand All @@ -44,19 +47,19 @@ const DefaultExampleTemplate = (responseListProps) => {
secondaryIconName={() => <CloseSmall />}
/>
</div>
<Button kind={Button.kinds.TERTIARY} marginLeft>
{!lessItems && <Button kind={Button.kinds.TERTIARY} marginLeft>
<Icon icon={Broom} ignoreFocusStyle className="responsive-icon-margin" />
Clean
</Button>
<Button kind={Button.kinds.TERTIARY} marginLeft>
</Button>}
{!lessItems && <Button kind={Button.kinds.TERTIARY} marginLeft>
<Icon icon={Group} ignoreFocusStyle />
</Button>
<Button kind={Button.kinds.TERTIARY} marginLeft>
</Button>}
{!lessItems && <Button kind={Button.kinds.TERTIARY} marginLeft>
<Icon icon={Moon} ignoreFocusStyle />
</Button>
<Button kind={Button.kinds.TERTIARY} marginLeft>
</Button>}
{!lessItems && <Button kind={Button.kinds.TERTIARY} marginLeft>
<Icon icon={API} ignoreFocusStyle />
</Button>
</Button>}
<Button kind={Button.kinds.TERTIARY} marginLeft>
<Icon icon={Sun} ignoreFocusStyle />
</Button>
Expand All @@ -66,28 +69,51 @@ const DefaultExampleTemplate = (responseListProps) => {
</ResponsiveList>
}

export const Sandbox = () => (
<div style={{ width: "100%" }}>
export const Sandbox = () => {
const lessItems = boolean("Less iitems", false);

return <div style={{ width: "100%" }}>
<DescriptionLabel>
Use this component when you want to collapse elements into a menu button which appends to the end of the row. The
list wraps the element with <code>display: flex;</code> property in order to
calculate the
</DescriptionLabel>
<br />
<div style={{ width: "80%" }}>
<DefaultExampleTemplate />
<DefaultExampleTemplate lessItems={lessItems} />
</div>
</div>
);
};

export const SandboxWithDifferentIcon = () => {
return <div style={{ width: "100%" }}>
<DescriptionLabel>
You can also override the MenuButton props. Here's an example of how to use it with Bolt icon
You can also override the MenuButton props. Here's an example of how to use it with MoreActions icon
</DescriptionLabel>
<br />
<div style={{ width: "80%" }}>
<DefaultExampleTemplate menuButtonProps={{component: Bolt}} />
<DefaultExampleTemplate menuButtonProps={{component: MoreActions}} />
</div>
</div>
}


export const ChangeChildrenCheck = () => {
const width = number("width", 300)
const knobLessItems = boolean("less items", false);
const [lessItems, setLessItems] = useState(true);
setTimeout(() => {
setLessItems(false);
}, 100);
return <div style={{ width: "100%" }}>
<DescriptionLabel>
This is a story to reproduce a case where the items in the list are changed,
it should still be able to recalculate their size.
It adds some items at the beginning of the story, and it should be able to calculate the new items size although they weren't drawn yet.
</DescriptionLabel>
<br />
<div style={{ width: width }}>
<DefaultExampleTemplate menuButtonProps={{component: MoreActions}} lessItems={lessItems || knobLessItems} />
</div>
</div>
}
Expand Down
22 changes: 17 additions & 5 deletions src/hooks/useElementsOverflowingIndex.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { useCallback, useEffect, useState } from "react";
import last from "lodash/last";
import useResizeObserver from "./useResizeObserver";

// Use this hook when you want to get the index of the child which should be hidden from

function useElementsOverflowingIndex({ ref, children, paddingSize, resizeDebounceTime }) {
function useElementsOverflowingIndex({ ref, children, paddingSize, resizeDebounceTime, ignoreLast }) {
const [size, setSize] = useState(null);

const onResize = useCallback(
() => {
setSize(ref.current.scrollWidth);
({ borderBoxSize }) => {
setSize(borderBoxSize.inlineSize);
},
[setSize]
);
Expand All @@ -19,10 +20,21 @@ function useElementsOverflowingIndex({ ref, children, paddingSize, resizeDebounc
});

const [aggregatedChildLengths, setAggregatedChildLengths] = useState([]);
const [indexToSplit, setIndexToSplit] = useState(-1);
const [indexToSplit, setIndexToSplit] = useState(null);

useEffect(() => {
setIndexToSplit(aggregatedChildLengths.findIndex(({ totalLength }) => totalLength > size - paddingSize));
if(ignoreLast) {
const withoutLast = aggregatedChildLengths.slice(0, -1);
const allInWithoutLast = !withoutLast.find(({ totalLength }) => totalLength > size - paddingSize);
if(allInWithoutLast) {
setIndexToSplit(-1);
} else {
const lastSize = aggregatedChildLengths.length > 0 ? last(aggregatedChildLengths).childLength : 0;
setIndexToSplit(aggregatedChildLengths.findIndex(({ totalLength }) => totalLength > size - paddingSize - lastSize));
}
} else {
setIndexToSplit(aggregatedChildLengths.findIndex(({ totalLength }) => totalLength > size - paddingSize));
}
}, [aggregatedChildLengths, size, setIndexToSplit, paddingSize]);

useEffect(() => {
Expand Down

0 comments on commit 861314a

Please sign in to comment.