diff --git a/src/components/ResponsiveList/ResponsiveList.jsx b/src/components/ResponsiveList/ResponsiveList.jsx index 7c868f9f41..2af074688d 100644 --- a/src/components/ResponsiveList/ResponsiveList.jsx +++ b/src/components/ResponsiveList/ResponsiveList.jsx @@ -14,6 +14,7 @@ const ResponsiveList = forwardRef( { id, className, + rootClassName, children, menuButtonSize, paddingSize, @@ -21,13 +22,20 @@ const ResponsiveList = forwardRef( 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) { @@ -47,19 +55,37 @@ const ResponsiveList = forwardRef( }, [children, index]); return ( -
- {directChildren} - {!!menuChildren.length && ( +
+ {index !== null && ( +
+ {directChildren} + {!!menuChildren.length && ( + +
{menuChildren}
+
+ )} +
+ )} +
+ {children} -
{menuChildren}
+
- )} +
); } @@ -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)), /** @@ -92,6 +120,8 @@ ResponsiveList.defaultProps = { className: "", dialogClassName: "", menuButtonClassName: "", + rootClassName: "", + menuButtonAriaLabel: "More Actions", menuButtonProps: {}, menuButtonSize: ResponsiveList.menuButtonSizes.SMALL, paddingSize: DEFAULT_MINIMAL_MARGIN, diff --git a/src/components/ResponsiveList/ResponsiveList.scss b/src/components/ResponsiveList/ResponsiveList.scss index a3701d27a2..f47c77ee5b 100644 --- a/src/components/ResponsiveList/ResponsiveList.scss +++ b/src/components/ResponsiveList/ResponsiveList.scss @@ -1,6 +1,9 @@ @import "../../styles/themes.scss"; @import "../../styles/typography.scss"; +.responsive-list--root { + position: relative; +} .responsive-list--wrapper { display: flex; justify-content: flex-end; @@ -8,6 +11,11 @@ flex-shrink: 0; } } +.responsive-list--dummy { + width: 100%; + position: absolute; + visibility: hidden; +} .responsive-list--menu-button-dialog { display: flex; } diff --git a/src/components/ResponsiveList/__stories__/responsiveList.stories.js b/src/components/ResponsiveList/__stories__/responsiveList.stories.js index 34886d8f3a..2e2d20f26e 100644 --- a/src/components/ResponsiveList/__stories__/responsiveList.stories.js +++ b/src/components/ResponsiveList/__stories__/responsiveList.stories.js @@ -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"; @@ -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 ( @@ -32,6 +34,7 @@ function SecondaryContentComponent() { } const DefaultExampleTemplate = (responseListProps) => { + const { lessItems } = responseListProps; return }> Add Item @@ -44,19 +47,19 @@ const DefaultExampleTemplate = (responseListProps) => { secondaryIconName={() => } />
- - } + {!lessItems && - } + {!lessItems && - } + {!lessItems && + } @@ -66,8 +69,10 @@ const DefaultExampleTemplate = (responseListProps) => { } -export const Sandbox = () => ( -
+export const Sandbox = () => { + const lessItems = boolean("Less iitems", false); + + return
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 display: flex; property in order to @@ -75,19 +80,40 @@ export const Sandbox = () => (
- +
-); +}; export const SandboxWithDifferentIcon = () => { return
- 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
- + +
+
+} + + +export const ChangeChildrenCheck = () => { + const width = number("width", 300) + const knobLessItems = boolean("less items", false); + const [lessItems, setLessItems] = useState(true); + setTimeout(() => { + setLessItems(false); + }, 100); + return
+ + 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. + +
+
+
} diff --git a/src/hooks/useElementsOverflowingIndex.js b/src/hooks/useElementsOverflowingIndex.js index 786e2fa0fb..500665bf7d 100644 --- a/src/hooks/useElementsOverflowingIndex.js +++ b/src/hooks/useElementsOverflowingIndex.js @@ -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] ); @@ -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(() => {