-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create basic input table component (#13719)
- Loading branch information
Showing
23 changed files
with
497 additions
and
0 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
frontend/libs/studio-components/src/components/StudioInputTable/Cell/BaseInputCell.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { ForwardedRef, ForwardRefRenderFunction, PropsWithoutRef } from 'react'; | ||
import { forwardRef } from 'react'; | ||
import type { InputCellComponent } from '../types/InputCellComponent'; | ||
import type { HTMLCellInputElement } from '../types/HTMLCellInputElement'; | ||
|
||
export abstract class BaseInputCell<Element extends HTMLCellInputElement, Props extends {}> { | ||
displayName: string; | ||
|
||
constructor(displayName: string) { | ||
this.displayName = displayName; | ||
} | ||
|
||
component(): InputCellComponent<Props, Element> { | ||
const component = forwardRef<Element, Props>(this.render); | ||
component.displayName = this.displayName; | ||
return component; | ||
} | ||
|
||
protected abstract render( | ||
props: PropsWithoutRef<Props>, | ||
ref: ForwardedRef<Element>, | ||
): ReturnType<ForwardRefRenderFunction<Element, Props>>; | ||
} |
19 changes: 19 additions & 0 deletions
19
frontend/libs/studio-components/src/components/StudioInputTable/Cell/Cell.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.textfieldCell, | ||
.textareaCell { | ||
padding: var(--fds-spacing-1) 0; | ||
font-size: var(--studio-input-table-font-size); | ||
} | ||
|
||
.buttonCell { | ||
text-align: center; | ||
} | ||
|
||
.buttonCell button { | ||
display: inline-flex; | ||
} | ||
|
||
.textfieldCell:not(:hover) input:not(:hover):not(:active):not(:focus), | ||
.textareaCell:not(:hover) textarea:not(:hover):not(:active):not(:focus) { | ||
background-color: transparent; | ||
border-color: transparent; | ||
} |
3 changes: 3 additions & 0 deletions
3
frontend/libs/studio-components/src/components/StudioInputTable/Cell/Cell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
|
||
export const Cell = StudioTable.Cell; |
24 changes: 24 additions & 0 deletions
24
frontend/libs/studio-components/src/components/StudioInputTable/Cell/CellButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
import type { ForwardedRef, ReactElement } from 'react'; | ||
import React from 'react'; | ||
import classes from './Cell.module.css'; | ||
import type { StudioButtonProps } from '../../StudioButton'; | ||
import { StudioButton } from '../../StudioButton'; | ||
import { BaseInputCell } from './BaseInputCell'; | ||
import cn from 'classnames'; | ||
|
||
export type CellButtonProps = StudioButtonProps; | ||
|
||
export class CellButton extends BaseInputCell<HTMLButtonElement, CellButtonProps> { | ||
render( | ||
{ className: givenClass, ...rest }: StudioButtonProps, | ||
ref: ForwardedRef<HTMLButtonElement>, | ||
): ReactElement { | ||
const className = cn(classes.buttonCell, givenClass); | ||
return ( | ||
<StudioTable.Cell className={className}> | ||
<StudioButton ref={ref} variant='secondary' {...rest} /> | ||
</StudioTable.Cell> | ||
); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
frontend/libs/studio-components/src/components/StudioInputTable/Cell/CellCheckbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
import type { ForwardedRef, ReactElement } from 'react'; | ||
import React from 'react'; | ||
import type { StudioCheckboxProps } from '../../StudioCheckbox'; | ||
import { StudioCheckbox } from '../../StudioCheckbox'; | ||
import { BaseInputCell } from './BaseInputCell'; | ||
|
||
export type CellCheckboxProps = StudioCheckboxProps; | ||
|
||
export class CellCheckbox extends BaseInputCell<HTMLInputElement, CellCheckboxProps> { | ||
render( | ||
{ className, ...rest }: CellCheckboxProps, | ||
ref: ForwardedRef<HTMLInputElement>, | ||
): ReactElement { | ||
return ( | ||
<StudioTable.Cell className={className}> | ||
<StudioCheckbox ref={ref} {...rest} /> | ||
</StudioTable.Cell> | ||
); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
frontend/libs/studio-components/src/components/StudioInputTable/Cell/CellTextarea.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
import type { ForwardedRef, ReactElement } from 'react'; | ||
import React from 'react'; | ||
import classes from './Cell.module.css'; | ||
import type { StudioTextareaProps } from '../../StudioTextarea'; | ||
import { StudioTextarea } from '../../StudioTextarea'; | ||
import { BaseInputCell } from './BaseInputCell'; | ||
import cn from 'classnames'; | ||
|
||
export type CellTextareaProps = StudioTextareaProps; | ||
|
||
export class CellTextarea extends BaseInputCell<HTMLTextAreaElement, CellTextareaProps> { | ||
render( | ||
{ className: givenClass, ...rest }: CellTextareaProps, | ||
ref: ForwardedRef<HTMLTextAreaElement>, | ||
): ReactElement { | ||
const className = cn(classes.textareaCell, givenClass); | ||
return ( | ||
<StudioTable.Cell className={className}> | ||
<StudioTextarea hideLabel ref={ref} size='small' {...rest} /> | ||
</StudioTable.Cell> | ||
); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
frontend/libs/studio-components/src/components/StudioInputTable/Cell/CellTextfield.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
import type { ForwardedRef, ReactElement } from 'react'; | ||
import React from 'react'; | ||
import type { StudioTextfieldProps } from '../../StudioTextfield'; | ||
import { StudioTextfield } from '../../StudioTextfield'; | ||
import classes from './Cell.module.css'; | ||
import { BaseInputCell } from './BaseInputCell'; | ||
import cn from 'classnames'; | ||
|
||
export type CellTextfieldProps = StudioTextfieldProps; | ||
|
||
export class CellTextfield extends BaseInputCell<HTMLInputElement, CellTextfieldProps> { | ||
render( | ||
{ className: givenClass, ...rest }: CellTextfieldProps, | ||
ref: ForwardedRef<HTMLInputElement>, | ||
): ReactElement { | ||
const className = cn(classes.textfieldCell, givenClass); | ||
return ( | ||
<StudioTable.Cell className={className}> | ||
<StudioTextfield hideLabel ref={ref} size='small' {...rest} /> | ||
</StudioTable.Cell> | ||
); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
frontend/libs/studio-components/src/components/StudioInputTable/Cell/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { CellTextfieldProps } from './CellTextfield'; | ||
import { CellTextfield } from './CellTextfield'; | ||
import type { CellTextareaProps } from './CellTextarea'; | ||
import { CellTextarea } from './CellTextarea'; | ||
import type { CellButtonProps } from './CellButton'; | ||
import { CellButton } from './CellButton'; | ||
import { Cell } from './Cell'; | ||
import type { CellCheckboxProps } from './CellCheckbox'; | ||
import { CellCheckbox } from './CellCheckbox'; | ||
import type { InputCellComponent } from '../types/InputCellComponent'; | ||
|
||
type CellComponent = typeof Cell & { | ||
Textfield: InputCellComponent<CellTextfieldProps, HTMLInputElement>; | ||
Textarea: InputCellComponent<CellTextareaProps, HTMLTextAreaElement>; | ||
Button: InputCellComponent<CellButtonProps, HTMLButtonElement>; | ||
Checkbox: InputCellComponent<CellCheckboxProps, HTMLInputElement>; | ||
}; | ||
|
||
export const StudioInputTableCell = Cell as CellComponent; | ||
|
||
StudioInputTableCell.Textfield = new CellTextfield('StudioInputTable.Cell.Textfield').component(); | ||
StudioInputTableCell.Textarea = new CellTextarea('StudioInputTable.Cell.Textarea').component(); | ||
StudioInputTableCell.Button = new CellButton('StudioInputTable.Cell.Button').component(); | ||
StudioInputTableCell.Checkbox = new CellCheckbox('StudioInputTable.Cell.Checkbox').component(); | ||
|
||
StudioInputTableCell.displayName = 'StudioInputTable.Cell'; |
3 changes: 3 additions & 0 deletions
3
frontend/libs/studio-components/src/components/StudioInputTable/HeaderCell/HeaderCell.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
|
||
export const HeaderCell = StudioTable.HeaderCell; |
20 changes: 20 additions & 0 deletions
20
.../libs/studio-components/src/components/StudioInputTable/HeaderCell/HeaderCellCheckbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { StudioTable } from '../../StudioTable'; | ||
import React, { forwardRef, useId } from 'react'; | ||
import type { StudioCheckboxProps } from '../../StudioCheckbox'; | ||
import { StudioCheckbox } from '../../StudioCheckbox'; | ||
|
||
export type HeaderCellCheckboxProps = Omit<StudioCheckboxProps, 'value'> & { value?: string }; | ||
|
||
export const HeaderCellCheckbox = forwardRef<HTMLInputElement, HeaderCellCheckboxProps>( | ||
({ value: givenValue, className, ...rest }, ref) => { | ||
const defaultValue = useId(); | ||
const value = givenValue ?? defaultValue; | ||
return ( | ||
<StudioTable.HeaderCell className={className}> | ||
<StudioCheckbox ref={ref} value={value} {...rest} /> | ||
</StudioTable.HeaderCell> | ||
); | ||
}, | ||
); | ||
|
||
HeaderCellCheckbox.displayName = 'HeaderCell.Checkbox'; |
13 changes: 13 additions & 0 deletions
13
frontend/libs/studio-components/src/components/StudioInputTable/HeaderCell/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { HeaderCell } from './HeaderCell'; | ||
import { HeaderCellCheckbox } from './HeaderCellCheckbox'; | ||
|
||
type HeaderCellComponent = typeof HeaderCell & { | ||
Checkbox: typeof HeaderCellCheckbox; | ||
}; | ||
|
||
export const StudioInputTableHeaderCell = HeaderCell as HeaderCellComponent; | ||
|
||
StudioInputTableHeaderCell.Checkbox = HeaderCellCheckbox; | ||
|
||
StudioInputTableHeaderCell.displayName = 'StudioInputTableHeaderCell.HeaderCell'; | ||
StudioInputTableHeaderCell.Checkbox.displayName = 'StudioInputTableHeaderCell.Checkbox'; |
13 changes: 13 additions & 0 deletions
13
frontend/libs/studio-components/src/components/StudioInputTable/Row/Row.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { ComponentProps } from 'react'; | ||
import React, { forwardRef } from 'react'; | ||
import { StudioTable } from '../../StudioTable'; | ||
|
||
type RowProps = ComponentProps<typeof StudioTable.Row>; | ||
|
||
export const Row = forwardRef<HTMLTableRowElement, RowProps>(({ children, ...rest }, ref) => ( | ||
<StudioTable.Row ref={ref} {...rest}> | ||
{children} | ||
</StudioTable.Row> | ||
)); | ||
|
||
Row.displayName = 'StudioInputTable.Row'; |
3 changes: 3 additions & 0 deletions
3
frontend/libs/studio-components/src/components/StudioInputTable/Row/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Row } from './Row'; | ||
|
||
export const StudioInputTableRow = Row; |
4 changes: 4 additions & 0 deletions
4
frontend/libs/studio-components/src/components/StudioInputTable/StudioInputTable.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.inputTable { | ||
--studio-input-table-font-size: --fds-font-size-f0; | ||
font-size: var(--studio-input-table-font-size); | ||
} |
18 changes: 18 additions & 0 deletions
18
frontend/libs/studio-components/src/components/StudioInputTable/StudioInputTable.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { ReactElement } from 'react'; | ||
import React from 'react'; | ||
import type { Meta, StoryFn } from '@storybook/react'; | ||
import { TestTable } from './test-data/TestTable'; | ||
import type { StudioInputTableProps } from './StudioInputTable'; | ||
|
||
type Story = StoryFn<typeof TestTable>; | ||
|
||
export function render(props: StudioInputTableProps): ReactElement { | ||
return <TestTable {...props} />; | ||
} | ||
|
||
const meta: Meta<Story> = { | ||
title: 'Components/StudioInputTable', | ||
component: TestTable, | ||
render, | ||
}; | ||
export default meta; |
133 changes: 133 additions & 0 deletions
133
frontend/libs/studio-components/src/components/StudioInputTable/StudioInputTable.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import type { ForwardedRef, ReactNode } from 'react'; | ||
import React from 'react'; | ||
import { StudioInputTable } from './'; | ||
import type { RenderResult } from '@testing-library/react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { TestTable } from './test-data/TestTable'; | ||
import type { StudioInputTableProps } from './StudioInputTable'; | ||
import { expect } from '@storybook/test'; | ||
import { testRefForwarding } from '../../test-utils/testRefForwarding'; | ||
import { testRootClassNameAppending } from '../../test-utils/testRootClassNameAppending'; | ||
import { testCustomAttributes } from '../../test-utils/testCustomAttributes'; | ||
|
||
describe('StudioInputTable', () => { | ||
it('Renders a table', () => { | ||
renderStudioInputTable(); | ||
expect(getTable()).toBeInTheDocument(); | ||
}); | ||
|
||
it('Forwards the ref if provided', () => { | ||
const renderTable = (ref: ForwardedRef<HTMLTableElement>) => | ||
render( | ||
<StudioInputTable ref={ref}> | ||
<StudioInputTable.Body> | ||
<StudioInputTable.Row> | ||
<StudioInputTable.Cell /> | ||
</StudioInputTable.Row> | ||
</StudioInputTable.Body> | ||
</StudioInputTable>, | ||
); | ||
testRefForwarding<HTMLTableElement>(renderTable, getTable); | ||
}); | ||
|
||
it('Appends the given class to the table', () => { | ||
testRootClassNameAppending((className) => renderStudioInputTable({ className })); | ||
}); | ||
|
||
it('Applies the given props to the table', () => { | ||
testCustomAttributes<HTMLTableElement>(renderStudioInputTable, getTable); | ||
}); | ||
|
||
it('Renders all headers', () => { | ||
render(<TestTable />); | ||
const headers = screen.getAllByRole('columnheader'); | ||
expect(headers).toHaveLength(expectedNumberOfColumns); | ||
}); | ||
|
||
it('Renders all rows', () => { | ||
render(<TestTable />); | ||
const rows = screen.getAllByRole('row'); | ||
expect(rows).toHaveLength(expectedNumberOfRows); | ||
}); | ||
|
||
describe('Forwards the refs to the input elements', () => { | ||
type TestCase<Element extends HTMLElement = HTMLElement> = { | ||
render: (ref: ForwardedRef<Element>) => RenderResult; | ||
getElement: () => Element; | ||
}; | ||
const testLabel = 'test'; | ||
const testCases: { | ||
checkbox: TestCase<HTMLInputElement>; | ||
textfield: TestCase<HTMLInputElement>; | ||
textarea: TestCase<HTMLTextAreaElement>; | ||
button: TestCase<HTMLButtonElement>; | ||
} = { | ||
checkbox: { | ||
render: (ref) => | ||
render( | ||
<SingleRow> | ||
<StudioInputTable.Cell.Checkbox value='test' aria-label={testLabel} ref={ref} /> | ||
</SingleRow>, | ||
), | ||
getElement: () => getCheckbox(testLabel), | ||
}, | ||
textfield: { | ||
render: (ref) => | ||
render( | ||
<SingleRow> | ||
<StudioInputTable.Cell.Textfield label={testLabel} ref={ref} /> | ||
</SingleRow>, | ||
), | ||
getElement: () => getTextbox(testLabel) as HTMLInputElement, | ||
}, | ||
textarea: { | ||
render: (ref) => | ||
render( | ||
<SingleRow> | ||
<StudioInputTable.Cell.Textarea label={testLabel} ref={ref} /> | ||
</SingleRow>, | ||
), | ||
getElement: () => getTextbox(testLabel) as HTMLTextAreaElement, | ||
}, | ||
button: { | ||
render: (ref) => | ||
render( | ||
<SingleRow> | ||
<StudioInputTable.Cell.Button ref={ref}>{testLabel}</StudioInputTable.Cell.Button> | ||
</SingleRow>, | ||
), | ||
getElement: () => getButton(testLabel), | ||
}, | ||
}; | ||
|
||
it.each(Object.keys(testCases))('%s', (key) => { | ||
const { render: renderComponent, getElement } = testCases[key]; | ||
testRefForwarding(renderComponent, getElement); | ||
}); | ||
}); | ||
}); | ||
|
||
const renderStudioInputTable = (props: StudioInputTableProps = {}) => | ||
render(<TestTable {...props} />); | ||
|
||
const getTable = (): HTMLTableElement => screen.getByRole('table'); | ||
const getCheckbox = (name: string): HTMLInputElement => | ||
screen.getByRole('checkbox', { name }) as HTMLInputElement; | ||
const getTextbox = (name: string) => screen.getByRole('textbox', { name }); | ||
const getButton = (name: string): HTMLButtonElement => | ||
screen.getByRole('button', { name }) as HTMLButtonElement; | ||
|
||
const expectedNumberOfColumns = 5; | ||
const expectedNumberOfHeaderRows = 1; | ||
const expectedNumberOfBodyRows = 3; | ||
const expectedNumberOfRows = expectedNumberOfBodyRows + expectedNumberOfHeaderRows; | ||
|
||
function SingleRow({ children }: { children: ReactNode }) { | ||
return ( | ||
<StudioInputTable> | ||
<StudioInputTable.Body> | ||
<StudioInputTable.Row>{children}</StudioInputTable.Row> | ||
</StudioInputTable.Body> | ||
</StudioInputTable> | ||
); | ||
} |
Oops, something went wrong.