diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/table/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/table/demos.mdx index c09061ab36d..bed3a379913 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/table/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/table/demos.mdx @@ -52,6 +52,24 @@ It's also possible to use accordion to expand the table with more rows. +##### Collapse all rows at once + +You can collapse all expanded rows by sending a ref to the `collapseAllHandleRef` prop and calling the `.current()` function on your ref. + +```jsx +const myTableCollapseAll = React.useRef<() => void>() + +return ( + + + + {/* ... your table code */} +
+) +``` + ### Table with sticky header diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx index 786c9f7a57e..02ab1450ac9 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx @@ -9,6 +9,7 @@ showTabs: true | Properties | Description | | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `accordion` | _(optional)_ set to `true` if you have one or more rows that contains an accordion content. Defaults to `false`. | +| `collapseAllHandleRef` | _(optional)_ ref handle to collapse all expanded accordion rows. Send in a ref and use `.current()` to collapse all rows. Defaults to `undefined`. | | `border` | _(optional)_ use `true` to show borders between table data cells. Defaults to `false`. | | `outline` | _(optional)_ use `true` to show a outline border around the table. Defaults to `false`. | | `sticky` | _(optional)_ use `true` to enable a sticky Table header. Or use `css-position` to enable the CSS based scroll behavior. Defaults to `false`. | diff --git a/packages/dnb-eufemia/src/components/table/Table.tsx b/packages/dnb-eufemia/src/components/table/Table.tsx index 8f348138f83..4fb53841da9 100644 --- a/packages/dnb-eufemia/src/components/table/Table.tsx +++ b/packages/dnb-eufemia/src/components/table/Table.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import classnames from 'classnames' import Context from '../../shared/Context' import Provider from '../../shared/Provider' @@ -79,6 +79,13 @@ export type TableProps = { * Default: null. */ fixed?: boolean + + /** + * ref handle to collapse all expanded accordion rows. Send in a ref and use `.current()` to collapse all rows. + * + * Default: `undefined` + */ + collapseAllHandleRef?: React.MutableRefObject<() => void> } & StickyTableHeaderProps export type TableAllProps = TableProps & @@ -116,11 +123,21 @@ const Table = (componentProps: TableAllProps) => { outline, accordion, accordionChevronPlacement, // eslint-disable-line + collapseAllHandleRef, ...props } = allProps const { elementRef } = useStickyHeader(allProps) const { trCountRef, rerenderAlias } = useHandleOddEven({ children }) + const collapseTrCallbacks = React.useRef<(() => void)[]>([]) + + useEffect(() => { + if (collapseAllHandleRef) { + collapseAllHandleRef.current = () => { + collapseTrCallbacks.current.forEach((callback) => callback()) + } + } + }, [collapseAllHandleRef]) const skeletonClasses = createSkeletonClass('font', skeleton, context) const spacingClasses = createSpacingClasses(props) @@ -133,6 +150,7 @@ const Table = (componentProps: TableAllProps) => { value={{ trCountRef, rerenderAlias, + collapseTrCallbacks, allProps: { ...allProps, ...context.getTranslation(componentProps).Table, diff --git a/packages/dnb-eufemia/src/components/table/TableAccordion.tsx b/packages/dnb-eufemia/src/components/table/TableAccordion.tsx index fea3facc273..7c8c9e9dae3 100644 --- a/packages/dnb-eufemia/src/components/table/TableAccordion.tsx +++ b/packages/dnb-eufemia/src/components/table/TableAccordion.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import classnames from 'classnames' import Button from '../button/Button' import IconPrimary from '../icon/IconPrimary' @@ -12,22 +12,33 @@ import TableAccordionTd from './TableAccordionTd' import TableAccordionTr from './TableAccordionTr' import type { TableAccordionTdProps } from './TableAccordionTd' import type { TableAccordionTrProps } from './TableAccordionTr' +import type { TableTrProps } from './TableTr' type TableAccordionContentProps = | TableAccordionTdProps | TableAccordionTrProps -export function useTableAccordion({ - children, - className, - props, - expanded, - disabled, - noAnimation, - onClick, - onOpened, - onClosed, -}) { +type TableAccordionProps = { + count: number +} + +export function TableAccordion( + allProps: TableAccordionProps & + TableTrProps & + React.TableHTMLAttributes +) { + const { + children, + className, + expanded, + disabled, + noAnimation, + onClick, + onOpened, + onClosed, + count, + ...props + } = allProps const tableContext = React.useContext(TableContext) const [trIsOpen, setOpen] = React.useState(() => { @@ -45,10 +56,6 @@ export function useTableAccordion({ const [trIsHover, setHover] = React.useState(false) const [trHadClick, setHadClick] = React.useState(false) - if (!tableContext?.allProps?.accordion) { - return null - } - let headerContent = React.Children.toArray(children) const addExpandIcon = (icon) => { @@ -71,6 +78,18 @@ export function useTableAccordion({ accordionContent.length !== 0 && accordionContent.every((element) => React.isValidElement(element)) + useEffect(() => { + if ( + hasAccordionContent && + tableContext?.collapseTrCallbacks?.current && + count + ) { + tableContext.collapseTrCallbacks.current[count] = () => { + setOpen(false) + } + } + }, [count, tableContext?.collapseTrCallbacks, hasAccordionContent]) + const trParams = !disabled && hasAccordionContent ? { @@ -141,8 +160,8 @@ export function useTableAccordion({ ) - function onKeydownHandler(event: KeyboardEvent) { - switch (keycode(event)) { + function onKeydownHandler(event: React.SyntheticEvent) { + switch (keycode(event.nativeEvent)) { case 'space': case 'enter': { @@ -166,7 +185,7 @@ export function useTableAccordion({ setHadClick(false) } function toggleOpenTr( - event: MouseEvent, + event: React.SyntheticEvent, allowInteractiveElement = false ) { const target = event.target as HTMLElement diff --git a/packages/dnb-eufemia/src/components/table/TableTr.tsx b/packages/dnb-eufemia/src/components/table/TableTr.tsx index 82d9840a405..b0ee80136d7 100644 --- a/packages/dnb-eufemia/src/components/table/TableTr.tsx +++ b/packages/dnb-eufemia/src/components/table/TableTr.tsx @@ -1,6 +1,6 @@ import React from 'react' import classnames from 'classnames' -import { useTableAccordion } from './TableAccordion' +import { TableAccordion } from './TableAccordion' import { TableContext } from './TableContext' import TableAccordionTr from './TableAccordionTr' @@ -68,18 +68,11 @@ export default function Tr( const { variant, noWrap, - expanded, - disabled, - noAnimation, - onClick, - onOpened, - onClosed, - children, className: _className, - ...props + ...accordionProps } = componentProps - const { currentVariant, isLast } = useHandleTrVariant({ + const { currentVariant, isLast, count } = useHandleTrVariant({ variant, }) @@ -91,27 +84,28 @@ export default function Tr( _className ) - const accordionTr = useTableAccordion({ - className, - children, - props, + const tableContext = React.useContext(TableContext) + if (tableContext?.allProps?.accordion) { + return ( + + ) + } + + const { expanded, disabled, noAnimation, onClick, onOpened, onClosed, - }) + ...trProps + } = accordionProps - if (accordionTr) { - return accordionTr - } - - return ( - - {children} - - ) + return } function useHandleTrVariant({ variant }) { @@ -164,6 +158,7 @@ function useHandleTrVariant({ variant }) { return { currentVariant, isLast, + count, } } diff --git a/packages/dnb-eufemia/src/components/table/__tests__/Table.screenshot.test.ts b/packages/dnb-eufemia/src/components/table/__tests__/Table.screenshot.test.ts index e60309027a3..78d835f76b3 100644 --- a/packages/dnb-eufemia/src/components/table/__tests__/Table.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/table/__tests__/Table.screenshot.test.ts @@ -270,7 +270,7 @@ describe.each(['ui', 'sbanken'])( (themeName) => { setupPageScreenshot({ themeName, - url: '/uilib/components/table', + url: '/uilib/components/table/demos', }) it('have to match default state', async () => { @@ -367,7 +367,7 @@ describe.each(['ui', 'sbanken'])( (themeName) => { setupPageScreenshot({ themeName, - url: '/uilib/components/table', + url: '/uilib/components/table/demos', }) it('have to match default state', async () => { diff --git a/packages/dnb-eufemia/src/components/table/__tests__/TableAccordion.test.tsx b/packages/dnb-eufemia/src/components/table/__tests__/TableAccordion.test.tsx index 3d71d2f2791..66c811f38d0 100644 --- a/packages/dnb-eufemia/src/components/table/__tests__/TableAccordion.test.tsx +++ b/packages/dnb-eufemia/src/components/table/__tests__/TableAccordion.test.tsx @@ -1,5 +1,10 @@ import React from 'react' -import { render, fireEvent, createEvent } from '@testing-library/react' +import { + render, + fireEvent, + createEvent, + act, +} from '@testing-library/react' import Table from '../Table' import Tr from '../TableTr' import Td from '../TableTd' @@ -826,4 +831,66 @@ describe('TableAccordion', () => { expect(onClosed).toHaveBeenCalledTimes(2) }) }) + + describe('closeAllHandle', () => { + it('should close all tr when called', () => { + const collapseAllHandleRef = React.createRef<() => void>() + + render( + + + + + single content + + + + + + + + +
Accordion single
Accordion rowrow content
+ ) + + const trElements = document.querySelectorAll( + 'tr.dnb-table__tr--has-accordion-content' + ) + const accordionElems = document.querySelectorAll( + 'tr.dnb-table__tr__accordion-content' + ) + + // Assert all are expanded + expect(Array.from(trElements[0].classList)).toContain( + 'dnb-table__tr--expanded' + ) + expect(Array.from(trElements[1].classList)).toContain( + 'dnb-table__tr--expanded' + ) + expect(Array.from(accordionElems[0].classList)).toContain( + 'dnb-table__tr__accordion-content--expanded' + ) + expect(Array.from(accordionElems[1].classList)).toContain( + 'dnb-table__tr__accordion-content--expanded' + ) + + act(() => { + collapseAllHandleRef.current() + }) + + // Assert all are closed + expect(Array.from(trElements[0].classList)).not.toContain( + 'dnb-table__tr--expanded' + ) + expect(Array.from(trElements[1].classList)).not.toContain( + 'dnb-table__tr--expanded' + ) + expect(Array.from(accordionElems[0].classList)).not.toContain( + 'dnb-table__tr__accordion-content--expanded' + ) + expect(Array.from(accordionElems[1].classList)).not.toContain( + 'dnb-table__tr__accordion-content--expanded' + ) + }) + }) })