diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index dfe218fba05..d4f77976dc5 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -21,14 +21,14 @@ export const ExpandControl: React.FC = ({ expanded, onExpand, }) => ( - + { onExpand?.({ rowId: id, toggle: expanded }); }} diff --git a/packages/gamut/src/DataList/DataGrid.tsx b/packages/gamut/src/DataList/DataGrid.tsx index ec912312301..fe017e3f37d 100644 --- a/packages/gamut/src/DataList/DataGrid.tsx +++ b/packages/gamut/src/DataList/DataGrid.tsx @@ -55,6 +55,8 @@ export function DataGrid< hideSelectAll = false, scrollToTopOnUpdate = false, id, + wrapperWidth, + overflow, } = props; const empty = rows.length === 0; @@ -117,11 +119,13 @@ export function DataGrid< id={id} loading={loading} minHeight={minHeight} + overflow={overflow} scrollToTopOnUpdate={scrollToTopOnUpdate} scrollable={scrollable} shadow={shadow} spacing={spacing} variant={variant} + wrapperWidth={wrapperWidth ?? undefined} > {renderedRows.map((row) => { const rowId = row[idKey]; diff --git a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx index a7714fb4430..592a668fb14 100644 --- a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx +++ b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx @@ -77,7 +77,7 @@ export const TableRow: DataRow = ({ {selectable && ( @@ -136,7 +136,11 @@ export const TableRow: DataRow = ({ ); })} {expandable && ( - + { }); it("clicking the row's checkbox deselects the row when the row is already selected", () => { - renderView({ selected: [1] }); + renderView({ selected: ['1'] }); const checkbox = screen.getByRole('checkbox', { name: 'Select 1' }); @@ -110,7 +110,7 @@ describe('DataGrid', () => { }); it('selecting another row adds the row to the selection', () => { - renderView({ selected: [2] }); + renderView({ selected: ['2'] }); const checkbox = screen.getByRole('checkbox', { name: 'Select 1' }); @@ -173,14 +173,14 @@ describe('DataGrid', () => { describe('Expanding Rows', () => { it('Renders an expanded row when passed the correct id', () => { - renderView({ expanded: [1] }); + renderView({ expanded: ['1'] }); screen.getByText('Expanded 1'); screen.getByRole('button', { expanded: true }); }); it('allows multiple expanded rows by default', () => { - renderView({ expanded: [1, 2] }); + renderView({ expanded: ['1', '2'] }); screen.getByText('Expanded 1'); screen.getByText('Expanded 2'); @@ -205,7 +205,7 @@ describe('DataGrid', () => { }); }); it('calls the onRowExpand with the id omitted when an expanded row toggle is clicked', () => { - renderView({ expanded: [1] }); + renderView({ expanded: ['1'] }); const expandButton = screen.getByRole('button', { name: `Expand 1 Row` }); @@ -516,4 +516,36 @@ describe('DataGrid', () => { }); }); }); + + describe('wrapperWidth prop', () => { + it('applies wrapperWidth to the table container when provided', () => { + renderView({ wrapperWidth: '600px' }); + + const tableContainer = screen.getByTestId('scrollable-test'); + expect(tableContainer).toHaveStyle({ + maxWidth: '600px', + width: '600px', + }); + }); + + it('uses default width when wrapperWidth is not provided', () => { + renderView(); + + const tableContainer = screen.getByTestId('scrollable-test'); + expect(tableContainer).toHaveStyle({ + maxWidth: '100%', + width: 'inherit', + }); + }); + + it('passes wrapperWidth through to the underlying List component', () => { + renderView({ wrapperWidth: '750px' }); + + const tableContainer = screen.getByTestId('scrollable-test'); + expect(tableContainer).toHaveStyle({ + maxWidth: '750px', + width: '750px', + }); + }); + }); }); diff --git a/packages/gamut/src/List/List.tsx b/packages/gamut/src/List/List.tsx index 02ad8ce5439..40aa53274e6 100644 --- a/packages/gamut/src/List/List.tsx +++ b/packages/gamut/src/List/List.tsx @@ -29,6 +29,11 @@ export interface ListProps extends AllListProps> { * How the List container should handle overflow. */ overflow?: BoxProps['overflow']; + /** + * This is an override for the width of the wrapper element that contains the List. + * It is useful for custom scroll and breakpoint handling. Use with caution. + */ + wrapperWidth?: BoxProps['width']; } export const List = forwardRef( @@ -49,6 +54,7 @@ export const List = forwardRef( emptyMessage, overflow = 'auto', scrollToTopOnUpdate = false, + wrapperWidth, ...rest }, ref @@ -140,12 +146,12 @@ export const List = forwardRef( as={isTable && !isEmpty && !loading ? 'table' : 'div'} data-testid={`scrollable-${id}`} height={isEmpty ? height : 'fit-content'} - maxWidth={1} + maxWidth={wrapperWidth || 1} minHeight={minHeight} overflow="inherit" position="relative" ref={!isEmpty ? tableRef : undefined} - width="inherit" + width={wrapperWidth || 'inherit'} > {content} diff --git a/packages/gamut/src/List/ListCol.tsx b/packages/gamut/src/List/ListCol.tsx index 732781fda0a..64b960acea0 100644 --- a/packages/gamut/src/List/ListCol.tsx +++ b/packages/gamut/src/List/ListCol.tsx @@ -31,7 +31,10 @@ export const ListCol = forwardRef( ); if (sticky) { return ( - + {col} ); diff --git a/packages/gamut/src/List/ListRow.tsx b/packages/gamut/src/List/ListRow.tsx index 3eec8de1c25..9b512a58db9 100644 --- a/packages/gamut/src/List/ListRow.tsx +++ b/packages/gamut/src/List/ListRow.tsx @@ -38,7 +38,7 @@ export type ListRowProps = ExpandableRowProps | SimpleRowProps; const expandStyles = css({ overflow: 'hidden', - gridColumn: { _: 'span 2', xs: 'span 12' }, + gridColumn: { _: 'span 2', c_sm: 'span 12' }, }); const DivExpand = styled(motion.div)(expandStyles); @@ -124,10 +124,10 @@ export const ListRow = forwardRef( @@ -44,9 +46,6 @@ describe('List', () => { expect(rowEl).toHaveStyle({ borderTop: 'none' }); expect(rowEl).toHaveStyle({ gap: theme.spacing[8] }); - expect(rowEl).toHaveStyleRule('gap', theme.spacing[40], { - media: theme.breakpoints.xs, - }); }); it('configures columns with the correct variants', () => { @@ -55,12 +54,8 @@ describe('List', () => { const colEl = view.getByText('Hello'); expect(colEl).not.toHaveStyle({ py: 16 }); - expect(colEl).toHaveStyleRule('padding-top', theme.spacing[16], { - media: theme.breakpoints.xs, - }); - expect(colEl).toHaveStyleRule('padding-bottom', theme.spacing[16], { - media: theme.breakpoints.xs, - }); + expect(colEl).toHaveStyleRule('padding-left', theme.spacing[8]); + expect(colEl).toHaveStyleRule('padding-right', theme.spacing[8]); expect(colEl).not.toHaveStyle({ position: 'sticky' }); }); @@ -128,4 +123,27 @@ describe('List', () => { }); expect(view.queryByText('Surprise!')).toBeNull(); }); + + describe('wrapperWidth prop', () => { + it('applies wrapperWidth to the table container when provided', () => { + const { view } = renderView({ wrapperWidth: '500px' }); + + const tableContainer = view.container.querySelector( + '[data-testid="scrollable-list-el"]' + ); + expect(tableContainer).toHaveStyle({ maxWidth: '500px', width: '500px' }); + }); + + it('uses inherit width when wrapperWidth is not provided', () => { + const { view } = renderView(); + + const tableContainer = view.container.querySelector( + '[data-testid="scrollable-list-el"]' + ); + expect(tableContainer).toHaveStyle({ + maxWidth: '100%', + width: 'inherit', + }); + }); + }); }); diff --git a/packages/gamut/src/List/elements.tsx b/packages/gamut/src/List/elements.tsx index b2818211543..350c372c073 100644 --- a/packages/gamut/src/List/elements.tsx +++ b/packages/gamut/src/List/elements.tsx @@ -59,7 +59,7 @@ const rowStates = states({ isOl: { '&::before': { ...olStyles, - display: { _: 'none', xs: 'flex' }, + display: { _: 'none', c_sm: 'flex' }, pl: 16, }, }, @@ -69,7 +69,7 @@ const rowStates = states({ }, expanded: { display: 'flex', - flexDirection: { xs: 'column' }, + flexDirection: { c_sm: 'column' }, }, clickable: { cursor: 'pointer', @@ -88,11 +88,11 @@ const spacingVariants = variant({ prop: 'spacing', variants: { normal: { - gap: { _: 8, xs: 40 }, + gap: { _: 8, c_sm: 40 }, }, condensed: { fontSize: 16, - gap: { _: 8, xs: 32 }, + gap: { _: 8, c_sm: 32 }, }, compact: { gap: 0, @@ -137,8 +137,8 @@ const rowBreakpointVariants = variant({ defaultVariant: 'xs', variants: { xs: { - display: { _: 'grid', xs: 'flex' }, - flexDirection: { _: 'column', xs: 'row' }, + display: { _: 'grid', c_sm: 'flex' }, + flexDirection: { _: 'column', c_sm: 'row' }, }, sm: { display: { _: 'grid', sm: 'flex' }, @@ -161,7 +161,7 @@ export interface RowProps export const RowEl = styled('li', styledOptions<'li'>())( css({ - py: { _: 8, xs: 0 }, + py: { _: 8, c_sm: 0 }, bg: 'inherit', }), variance.compose(system.grid), @@ -192,12 +192,13 @@ export interface HeaderProps export const HeaderRowEl = styled('tr', styledOptions)( css({ display: 'flex', - position: { _: 'initial', xs: 'sticky' }, - flexDirection: ['column', 'row'], + position: { _: 'initial', c_sm: 'sticky' }, + flexDirection: { _: 'column', c_sm: 'row' }, top: 0, bg: 'background-current', zIndex: 2, fontFamily: 'accent', + pb: { _: 8, c_sm: 0 }, }), spacingVariants, rowStates, @@ -213,7 +214,7 @@ const columnType = variant({ orderedHeader: { '&::before': { ...olStyles, - display: { _: 'flex', xs: 'none' }, + display: { _: 'flex', c_sm: 'none' }, pl: 8, }, }, @@ -225,14 +226,14 @@ const columnType = variant({ minWidth: 'min-content', alignItems: { _: 'flex-start', - xs: 'center', + c_sm: 'center', }, justifyItems: { _: 'end', - xs: undefined, + c_sm: undefined, }, - gridColumn: { _: 2, xs: 1 }, + gridColumn: { _: 2, c_sm: 1 }, gridRow: 1, }, expand: { @@ -246,10 +247,10 @@ const columnJustify = variant({ defaultVariant: 'left', variants: { left: { - justifyContent: { xs: 'flex-start' }, + justifyContent: { c_sm: 'flex-start' }, }, right: { - justifyContent: { xs: 'flex-end' }, + justifyContent: { c_sm: 'flex-end' }, '& div': { width: { sm: 'fit-content' }, }, @@ -263,20 +264,20 @@ const columnSizes = variant({ base: { minWidth: 0, maxWidth: 1, flexShrink: 1 }, variants: { sm: { - flexBasis: { xs: '6rem' }, - width: { xs: '6rem' }, + flexBasis: { c_sm: '6rem' }, + width: { c_sm: '6rem' }, }, md: { - flexBasis: { xs: '10rem' }, - width: { xs: '10rem' }, + flexBasis: { c_sm: '10rem' }, + width: { c_sm: '10rem' }, }, lg: { - flexBasis: { xs: '12rem' }, - width: { xs: '12rem' }, + flexBasis: { c_sm: '12rem' }, + width: { c_sm: '12rem' }, }, xl: { - flexBasis: { xs: '20rem' }, - width: { xs: '20rem' }, + flexBasis: { c_sm: '20rem' }, + width: { c_sm: '20rem' }, }, content: { flexShrink: 0, @@ -285,7 +286,7 @@ const columnSizes = variant({ }); const columnStates = states({ - fill: { flexGrow: { xs: 1 } }, + fill: { flexGrow: { c_sm: 1 } }, sticky: { width: '100%', height: '100%', @@ -294,7 +295,7 @@ const columnStates = states({ delimiter: { overflow: 'visible', '&:after': { - display: { _: 'none', xs: 'block' }, + display: { _: 'none', c_sm: 'block' }, content: '""', bg: 'background-current', right: -4, @@ -330,7 +331,7 @@ const columnStates = states({ const columnSpacing = variant({ prop: 'spacing', base: { - px: { _: 16, xs: 0 }, + px: { _: 8, c_sm: 0 }, '&:first-of-type': { pl: 8, }, @@ -340,10 +341,10 @@ const columnSpacing = variant({ }, variants: { normal: { - py: { _: 0, xs: 16 }, + py: { _: 0, c_sm: 16 }, }, condensed: { - py: { _: 0, xs: 8 }, + py: { _: 0, c_sm: 8 }, }, compact: {}, }, @@ -414,11 +415,11 @@ export const StickyHeaderColWrapper = styled.th( bg: 'inherit', '&:not(:first-of-type)': { - left: { xs: 16 }, + left: { c_sm: 16 }, overflow: 'visible', }, '&:not(:first-of-type):before': { - display: { _: 'none', xs: 'block' }, + display: { _: 'none', c_sm: 'block' }, content: '""', bg: 'inherit', left: -16, @@ -430,9 +431,12 @@ export const StickyHeaderColWrapper = styled.th( ); export const ListWrapper = styled(Box)( + css({ + containerType: 'inline-size', + }), states({ scrollable: { - boxShadow: { _: undefined, xs: 'inset -24px 0 24px -24px black' }, + boxShadow: { _: undefined, c_sm: 'inset -24px 0 24px -24px black' }, }, }) );