diff --git a/packages/core/src/ExpandableList/ExpandableListItem.tsx b/packages/core/src/ExpandableList/ExpandableListItem.tsx index f3aeeb01..e46b379d 100644 --- a/packages/core/src/ExpandableList/ExpandableListItem.tsx +++ b/packages/core/src/ExpandableList/ExpandableListItem.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, FC, Dispatch, SetStateAction } from 'react' +import React, { useCallback, Dispatch, SetStateAction } from 'react' import styled from 'styled-components' import { FoldTransition } from '../Transition' @@ -34,6 +34,7 @@ export interface ExpandableListItemProps extends BaseProps { readonly expandedItems: ReadonlyArray readonly setExpandedItems: Dispatch>> readonly isNestedItem: boolean + readonly isAccordion: boolean } /** @@ -45,23 +46,34 @@ export interface ExpandableListItemProps extends BaseProps { * */ -export const ExpandableListItem: FC = ({ +export const ExpandableListItem: React.VFC = ({ item, expandedItems, setExpandedItems, isNestedItem, + isAccordion, ...props }) => { const { id, label, icon, selected = false, onClick, items } = item const onChildClick = useCallback( () => ((itemId: string) => { - const nextExpandedItems = expandedItems.includes(itemId) - ? expandedItems.filter(i => i !== itemId) - : [...expandedItems, id] + let nextExpandedItems = [] + + if (expandedItems.includes(itemId)) { + // Close the expanded item + nextExpandedItems = expandedItems.filter(i => i !== itemId) + } else if (isAccordion) { + // Only add one expanded item when accordion + nextExpandedItems = [id] + } else { + // Extend expanded items with a new one + nextExpandedItems = [...expandedItems, id] + } + setExpandedItems(nextExpandedItems) })(id), - [expandedItems, id, setExpandedItems] + [expandedItems, id, isAccordion, setExpandedItems] ) const hasChildren = items !== undefined const onItemClick = hasChildren ? onChildClick : onClick @@ -93,6 +105,7 @@ export const ExpandableListItem: FC = ({ expandedItems={expandedItems} setExpandedItems={setExpandedItems} isNestedItem={true} + isAccordion={isAccordion} /> ))} diff --git a/packages/core/src/ExpandableList/ListItemContainer.tsx b/packages/core/src/ExpandableList/ListItemContainer.tsx index 9a610de3..c8597313 100644 --- a/packages/core/src/ExpandableList/ListItemContainer.tsx +++ b/packages/core/src/ExpandableList/ListItemContainer.tsx @@ -1,11 +1,11 @@ -import React, { useCallback, useRef, useState, useLayoutEffect } from 'react' - +import React, { useCallback } from 'react' import styled, { css } from 'styled-components' -import { shape, spacing } from '../designparams' +import { useHasOverflow } from 'react-hooks-shareable' +import { shape, spacing } from '../designparams' import { Icon, IconType } from '../Icon' import { Typography } from '../Typography' -import { Tooltip } from '../Tooltip' +import { Tooltip, ExpandedTooltipTypography } from '../Tooltip' interface ListItemMarkerBaseProps { readonly selected: boolean @@ -113,7 +113,9 @@ const ListItemMarker = styled.div` `} ` -const Label = styled(Typography)` +const Label = styled(Typography).attrs({ + variant: 'default-text', +})` margin-right: ${spacing.huge}; white-space: nowrap; ` @@ -160,27 +162,20 @@ interface OverflowTooltipProps { * Used to show a tooltip if label is too long to be fully shown in container. */ const LabelOverflowTooltip: React.FC = ({ label }) => { - const [hasOverflow, setHasOverflow] = useState(false) - const labelRef = useRef(null) - - useLayoutEffect(() => { - if (labelRef.current === null) { - return - } - - setHasOverflow( - labelRef.current.offsetHeight < labelRef.current.scrollHeight || - labelRef.current.offsetWidth < labelRef.current.scrollWidth - ) - }, [labelRef]) - - const text = ( - - ) + const { hasOverflow, ref } = useHasOverflow() - return hasOverflow ? {text} : text + const text = + + return hasOverflow ? ( + {label}} + > + + + ) : ( + text + ) } /** @@ -193,7 +188,7 @@ const LabelOverflowTooltip: React.FC = ({ label }) => { * */ -export const ListItemContainer: React.FC = ({ +export const ListItemContainer: React.VFC = ({ selected, isNestedItem, hasChildren, diff --git a/packages/core/src/ExpandableList/index.tsx b/packages/core/src/ExpandableList/index.tsx index 69616af2..029f8c67 100644 --- a/packages/core/src/ExpandableList/index.tsx +++ b/packages/core/src/ExpandableList/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, FC } from 'react' +import React, { useState } from 'react' import { ExpandableListContainer, @@ -20,10 +20,16 @@ interface ExpandableListProps extends BaseProps { * Used to create an array of items. */ readonly items: ReadonlyArray + /** + * Whether the list should work as accordion + * and allows only one expanded item at once. + */ + readonly accordion?: boolean } -export const ExpandableList: FC = ({ +export const ExpandableList: React.VFC = ({ items, + accordion, ...props }) => { const [expandedItems, setExpandedItems] = useState>([]) @@ -36,6 +42,7 @@ export const ExpandableList: FC = ({ item={item} expandedItems={expandedItems} setExpandedItems={setExpandedItems} + isAccordion={accordion === true} isNestedItem={false} /> ))} diff --git a/packages/docs/src/mdx/coreComponents/ExpandableList.mdx b/packages/docs/src/mdx/coreComponents/ExpandableList.mdx index 6f627b27..65015f78 100644 --- a/packages/docs/src/mdx/coreComponents/ExpandableList.mdx +++ b/packages/docs/src/mdx/coreComponents/ExpandableList.mdx @@ -8,7 +8,7 @@ import { useState } from 'react' import styled from 'styled-components' import { DeviceIcon } from 'practical-react-components-icons' -import { ExpandableList } from 'practical-react-components-core' +import { ExpandableList, Typography } from 'practical-react-components-core' # ExpandableList @@ -29,32 +29,32 @@ export const ITEMS = [ selected: true, items: [ { - id: 'item-1', + id: 'category-item-1', label: 'Item 1', icon: DeviceIcon, }, { - id: 'something-2', + id: 'category-something-2', label: 'Something 2', icon: DeviceIcon, }, { - id: 'something-else-3', + id: 'category-something-else-3', label: 'Something else 3', icon: DeviceIcon, }, { - id: 'entirely-different-thing', + id: 'category-entirely-different-thing', label: 'Entirely different thing', icon: DeviceIcon, }, { - id: 'and-this-thing', + id: 'category-and-this-thing', label: 'And this thing', icon: DeviceIcon, }, { - id: 'also-this', + id: 'category-also-this', label: 'Also this', icon: DeviceIcon, }, @@ -64,6 +64,43 @@ export const ITEMS = [ id: 'site-log', label: 'Site log', icon: DeviceIcon, + items: [ + { + id: 'site-log-item-1', + label: 'Item 1', + icon: DeviceIcon, + }, + { + id: 'site-log-something-2', + label: 'Something 2', + icon: DeviceIcon, + }, + { + id: 'site-log-something-else-3', + label: 'Something else 3', + icon: DeviceIcon, + }, + { + id: 'site-log-entirely-different-thing', + label: 'Entirely different thing', + icon: DeviceIcon, + }, + { + id: 'site-log-and-this-thing', + label: 'And this thing', + icon: DeviceIcon, + }, + { + id: 'site-log-also-this', + label: 'Also this', + icon: DeviceIcon, + }, + ], + }, + { + id: 'something-else', + label: 'Something else', + icon: DeviceIcon, }, ] @@ -71,6 +108,18 @@ export const Container = styled.div` width: 256px; ` +export const DemoContainer = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 32px; +` + +export const DemoContainerItem = styled.div` + display: grid; + grid-template-rows: auto 1fr; + grid-gap: 16px; +` + export const DemoComponent = ({}) => { const [selectedItem, setSelectedItem] = useState('none') const listItems = ITEMS.map(item => { @@ -101,9 +150,48 @@ export const DemoComponent = ({}) => { ) } +export const DemoComponentAccordion = ({}) => { + const [selectedItem, setSelectedItem] = useState('none') + const listItems = ITEMS.map(item => { + return { + ...item, + selected: selectedItem === item.id, + onClick: () => { + setSelectedItem(item.id) + }, + items: + item.items !== undefined + ? item.items.map(subItem => { + return { + ...subItem, + selected: selectedItem === subItem.id, + onClick: () => { + setSelectedItem(subItem.id) + }, + } + }) + : undefined, + } + }) + return ( + + + + ) +} + export const onClick = () => {} - + + + Expandable list + + + + Expandable list accordion + + + ## Basic usage