Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: create new Form package (#2925)
- Loading branch information
1 parent
6e8b78f
commit a4b9a58
Showing
24 changed files
with
1,012 additions
and
241 deletions.
There are no files selected for viewing
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,6 @@ | ||
--- | ||
'@twilio-paste/form': major | ||
'@twilio-paste/core': minor | ||
--- | ||
|
||
[Form] Create a set of components to handle form layouts. |
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
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
Empty file.
179 changes: 179 additions & 0 deletions
179
packages/paste-core/components/form/__tests__/index.spec.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,179 @@ | ||
import * as React from 'react'; | ||
import {render, screen} from '@testing-library/react'; | ||
import {CustomizationProvider} from '@twilio-paste/customization'; | ||
|
||
import { | ||
Form, | ||
FormActions, | ||
FormControl, | ||
FormControlTwoColumn, | ||
FormSection, | ||
FormSectionDescription, | ||
FormSectionHeading, | ||
} from '../src'; | ||
|
||
describe('Form', () => { | ||
it('should render correctly', () => { | ||
render( | ||
<Form aria-label="My Form"> | ||
<FormSection id="foo"> | ||
<FormSectionHeading data-testid="section-heading">Settings</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc">These are the settings</FormSectionDescription> | ||
</FormSection> | ||
<FormControlTwoColumn>Two column content</FormControlTwoColumn> | ||
<FormControl>Control content</FormControl> | ||
<FormActions>Actions content</FormActions> | ||
</Form> | ||
); | ||
|
||
const form = screen.getByRole('form'); | ||
expect(form).toBeDefined(); | ||
expect(form).toHaveAttribute('aria-label', 'My Form'); | ||
|
||
const description = screen.getByTestId('section-desc'); | ||
expect(description.id).toBe('foo-section-description'); | ||
|
||
const section = screen.getByRole('group', {name: 'Settings'}); | ||
expect(section).toBeDefined(); | ||
expect(section).toHaveAttribute('aria-describedby', description.id); | ||
|
||
expect(screen.getByTestId('section-heading').nodeName).toBe('LEGEND'); | ||
expect(screen.getByText('Two column content')).toBeDefined(); | ||
expect(screen.getByText('Control content')).toBeDefined(); | ||
expect(screen.getByText('Actions content')).toBeDefined(); | ||
}); | ||
|
||
it('should set the description id if no id prop is passed to the form section', () => { | ||
render( | ||
<FormSection> | ||
<FormSectionHeading>Section</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc">Description</FormSectionDescription> | ||
</FormSection> | ||
); | ||
|
||
expect(screen.getByRole('group', {name: 'Section'})).toHaveAttribute( | ||
'aria-describedby', | ||
screen.getByTestId('section-desc').id | ||
); | ||
}); | ||
}); | ||
|
||
describe('Form customization', () => { | ||
it('should set an element data attribute', () => { | ||
render( | ||
<Form aria-label="My Form"> | ||
<FormSection id="foo"> | ||
<FormSectionHeading data-testid="section-heading">Settings</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc">These are the settings</FormSectionDescription> | ||
</FormSection> | ||
<FormControlTwoColumn>Two column content</FormControlTwoColumn> | ||
<FormControl>Control content</FormControl> | ||
<FormActions>Actions content</FormActions> | ||
</Form> | ||
); | ||
|
||
expect(screen.getByRole('form').dataset.pasteElement).toBe('FORM'); | ||
expect(screen.getByRole('group', {name: 'Settings'}).dataset.pasteElement).toBe('FORM_SECTION'); | ||
expect(screen.getByTestId('section-heading').dataset.pasteElement).toBe('FORM_SECTION_HEADING'); | ||
expect(screen.getByText('Two column content').dataset.pasteElement).toBe('FORM_CONTROL_TWO_COLUMN'); | ||
expect(screen.getByText('Control content').dataset.pasteElement).toBe('FORM_CONTROL'); | ||
expect(screen.getByText('Actions content').dataset.pasteElement).toBe('FORM_ACTIONS'); | ||
}); | ||
|
||
it('should set a custom element data attribute', () => { | ||
render( | ||
<Form aria-label="My Form" element="MY_FORM"> | ||
<FormSection id="foo" element="MY_FORM_SECTION"> | ||
<FormSectionHeading data-testid="section-heading" element="MY_FORM_SECTION_HEADING"> | ||
Settings | ||
</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc" element="MY_FORM_SECTION_DESCRIPTION"> | ||
These are the settings | ||
</FormSectionDescription> | ||
</FormSection> | ||
<FormControlTwoColumn element="MY_FORM_CONTROL_TWO_COLUMN">Two column content</FormControlTwoColumn> | ||
<FormControl element="MY_FORM_CONTROL">Control content</FormControl> | ||
<FormActions element="MY_FORM_ACTIONS">Actions content</FormActions> | ||
</Form> | ||
); | ||
|
||
expect(screen.getByRole('form').dataset.pasteElement).toBe('MY_FORM'); | ||
expect(screen.getByRole('group', {name: 'Settings'}).dataset.pasteElement).toBe('MY_FORM_SECTION'); | ||
expect(screen.getByTestId('section-heading').dataset.pasteElement).toBe('MY_FORM_SECTION_HEADING'); | ||
expect(screen.getByText('Two column content').dataset.pasteElement).toBe('MY_FORM_CONTROL_TWO_COLUMN'); | ||
expect(screen.getByText('Control content').dataset.pasteElement).toBe('MY_FORM_CONTROL'); | ||
expect(screen.getByText('Actions content').dataset.pasteElement).toBe('MY_FORM_ACTIONS'); | ||
}); | ||
|
||
it('should add custom styling', () => { | ||
render( | ||
<CustomizationProvider | ||
theme={TestTheme} | ||
elements={{ | ||
FORM: {rowGap: 'space20'}, | ||
FORM_ACTIONS: {justifyContent: 'center'}, | ||
FORM_CONTROL: {flexGrow: 'unset'}, | ||
FORM_CONTROL_TWO_COLUMN: {columnGap: 'space20'}, | ||
FORM_SECTION: {borderWidth: 'borderWidth10', borderStyle: 'solid', borderColor: 'colorBorder'}, | ||
FORM_SECTION_DESCRIPTION: {fontWeight: 'fontWeightBold'}, | ||
FORM_SECTION_HEADING: {backgroundColor: 'colorBackgroundErrorWeakest'}, | ||
}} | ||
> | ||
<Form aria-label="My Form"> | ||
<FormSection id="foo"> | ||
<FormSectionHeading data-testid="section-heading">Settings</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc">These are the settings</FormSectionDescription> | ||
</FormSection> | ||
<FormControlTwoColumn data-testid="two-col">Two column content</FormControlTwoColumn> | ||
<FormControl>Control content</FormControl> | ||
<FormActions>Actions content</FormActions> | ||
</Form> | ||
</CustomizationProvider> | ||
); | ||
|
||
expect(screen.getByRole('form')).toHaveStyleRule('row-gap', '0.25rem'); | ||
expect(screen.getByRole('group', {name: 'Settings'})).toHaveStyleRule('border-style', 'solid'); | ||
expect(screen.getByTestId('section-heading')).toHaveStyleRule('background-color', 'rgb(254, 236, 236)'); | ||
expect(screen.getByText('Two column content')).toHaveStyleRule('column-gap', '0.25rem'); | ||
expect(screen.getByText('Control content')).toHaveStyleRule('flex-grow', 'unset'); | ||
expect(screen.getByText('Actions content')).toHaveStyleRule('justify-content', 'center'); | ||
}); | ||
|
||
it('should add custom styling to a custom named form', () => { | ||
render( | ||
<CustomizationProvider | ||
theme={TestTheme} | ||
elements={{ | ||
MY_FORM: {rowGap: 'space20'}, | ||
MY_FORM_ACTIONS: {justifyContent: 'center'}, | ||
MY_FORM_CONTROL: {flexGrow: 'unset'}, | ||
MY_FORM_CONTROL_TWO_COLUMN: {columnGap: 'space20'}, | ||
MY_FORM_SECTION: {borderWidth: 'borderWidth10', borderStyle: 'solid', borderColor: 'colorBorder'}, | ||
MY_FORM_SECTION_DESCRIPTION: {fontWeight: 'fontWeightBold'}, | ||
MY_FORM_SECTION_HEADING: {backgroundColor: 'colorBackgroundErrorWeakest'}, | ||
}} | ||
> | ||
<Form aria-label="My Form" element="MY_FORM"> | ||
<FormSection id="foo" element="MY_FORM_SECTION"> | ||
<FormSectionHeading data-testid="section-heading" element="MY_FORM_SECTION_HEADING"> | ||
Settings | ||
</FormSectionHeading> | ||
<FormSectionDescription data-testid="section-desc" element="MY_FORM_SECTION_DESCRIPTION"> | ||
These are the settings | ||
</FormSectionDescription> | ||
</FormSection> | ||
<FormControlTwoColumn element="MY_FORM_CONTROL_TWO_COLUMN">Two column content</FormControlTwoColumn> | ||
<FormControl element="MY_FORM_CONTROL">Control content</FormControl> | ||
<FormActions element="MY_FORM_ACTIONS">Actions content</FormActions> | ||
</Form> | ||
</CustomizationProvider> | ||
); | ||
|
||
expect(screen.getByRole('form')).toHaveStyleRule('row-gap', '0.25rem'); | ||
expect(screen.getByRole('group', {name: 'Settings'})).toHaveStyleRule('border-style', 'solid'); | ||
expect(screen.getByTestId('section-heading')).toHaveStyleRule('background-color', 'rgb(254, 236, 236)'); | ||
expect(screen.getByText('Two column content')).toHaveStyleRule('column-gap', '0.25rem'); | ||
expect(screen.getByText('Control content')).toHaveStyleRule('flex-grow', 'unset'); | ||
expect(screen.getByText('Actions content')).toHaveStyleRule('justify-content', 'center'); | ||
}); | ||
}); |
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 @@ | ||
const {build} = require('../../../../tools/build/esbuild'); | ||
|
||
build(require('./package.json')); |
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,58 @@ | ||
{ | ||
"name": "@twilio-paste/form", | ||
"version": "0.0.0", | ||
"category": "layout", | ||
"status": "production", | ||
"description": "A form groups related form elements that allow users to input information or configure options.", | ||
"author": "Twilio Inc.", | ||
"license": "MIT", | ||
"main:dev": "src/index.tsx", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "yarn clean && NODE_ENV=production node build.js && tsc", | ||
"build:js": "NODE_ENV=development node build.js", | ||
"build:props": "typedoc --tsconfig ./tsconfig.json --json ./dist/prop-types.json", | ||
"clean": "rm -rf ./dist", | ||
"tsc": "tsc" | ||
}, | ||
"peerDependencies": { | ||
"@twilio-paste/animation-library": "^0.3.2", | ||
"@twilio-paste/box": "^7.0.0", | ||
"@twilio-paste/customization": "^5.0.0", | ||
"@twilio-paste/design-tokens": "^8.0.0", | ||
"@twilio-paste/heading": "^8.0.0", | ||
"@twilio-paste/style-props": "^6.0.0", | ||
"@twilio-paste/styling-library": "^1.0.0", | ||
"@twilio-paste/theme": "^8.0.0", | ||
"@twilio-paste/types": "^3.1.1", | ||
"@twilio-paste/uid-library": "^0.2.6", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.8.6 || ^17.0.2", | ||
"react-dom": "^16.8.6 || ^17.0.2" | ||
}, | ||
"devDependencies": { | ||
"@twilio-paste/animation-library": "^0.3.2", | ||
"@twilio-paste/box": "^7.0.0", | ||
"@twilio-paste/customization": "^5.0.0", | ||
"@twilio-paste/design-tokens": "^8.0.0", | ||
"@twilio-paste/heading": "^8.0.0", | ||
"@twilio-paste/style-props": "^6.0.0", | ||
"@twilio-paste/styling-library": "^1.0.0", | ||
"@twilio-paste/theme": "^8.0.0", | ||
"@twilio-paste/types": "^3.1.1", | ||
"@twilio-paste/uid-library": "^0.2.6", | ||
"prop-types": "^15.7.2", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2", | ||
"typescript": "^4.9.4" | ||
} | ||
} |
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,36 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; | ||
import type {BoxProps, BoxStyleProps} from '@twilio-paste/box'; | ||
import {isMaxWidthTokenProp} from '@twilio-paste/style-props'; | ||
|
||
export interface FormProps extends Omit<React.ComponentPropsWithRef<'form'>, 'children'> { | ||
element?: BoxProps['element']; | ||
children: React.ReactNode; | ||
maxWidth?: BoxStyleProps['maxWidth']; | ||
} | ||
|
||
export const Form = React.forwardRef<HTMLDivElement, FormProps>( | ||
({element = 'FORM', maxWidth, children, ...props}, ref) => ( | ||
<Box | ||
as="form" | ||
ref={ref} | ||
element={element} | ||
maxWidth={maxWidth} | ||
display="flex" | ||
flexDirection="column" | ||
rowGap="space80" | ||
{...safelySpreadBoxProps(props)} | ||
> | ||
{children} | ||
</Box> | ||
) | ||
); | ||
|
||
Form.displayName = 'Form'; | ||
|
||
Form.propTypes = { | ||
children: PropTypes.node, | ||
element: PropTypes.string, | ||
maxWidth: isMaxWidthTokenProp, | ||
}; |
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,32 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; | ||
import type {BoxProps} from '@twilio-paste/box'; | ||
|
||
export interface FormActionsProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> { | ||
element?: BoxProps['element']; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const FormActions = React.forwardRef<HTMLDivElement, FormActionsProps>( | ||
({children, element = 'FORM_ACTIONS', ...props}, ref) => ( | ||
<Box | ||
ref={ref} | ||
element={element} | ||
{...safelySpreadBoxProps(props)} | ||
display="flex" | ||
flexDirection="row" | ||
columnGap="space40" | ||
marginTop="space60" | ||
> | ||
{children} | ||
</Box> | ||
) | ||
); | ||
|
||
FormActions.displayName = 'FormActions'; | ||
|
||
FormActions.propTypes = { | ||
children: PropTypes.node, | ||
element: PropTypes.string, | ||
}; |
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 * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; | ||
import type {BoxProps} from '@twilio-paste/box'; | ||
|
||
export interface FormControlProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> { | ||
element?: BoxProps['element']; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>( | ||
({children, element = 'FORM_CONTROL', ...props}, ref) => ( | ||
<Box ref={ref} flexGrow={1} element={element} {...safelySpreadBoxProps(props)}> | ||
{children} | ||
</Box> | ||
) | ||
); | ||
|
||
FormControl.displayName = 'FormControl'; | ||
|
||
FormControl.propTypes = { | ||
children: PropTypes.node, | ||
element: PropTypes.string, | ||
}; |
31 changes: 31 additions & 0 deletions
31
packages/paste-core/components/form/src/FormControlTwoColumn.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,31 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; | ||
import type {BoxProps} from '@twilio-paste/box'; | ||
|
||
export interface FormControlTwoColumnProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> { | ||
element?: BoxProps['element']; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const FormControlTwoColumn = React.forwardRef<HTMLLegendElement, FormControlTwoColumnProps>( | ||
({children, element = 'FORM_CONTROL_TWO_COLUMN', ...props}, ref) => ( | ||
<Box | ||
display="grid" | ||
gridTemplateColumns="1fr 1fr" | ||
columnGap="space50" | ||
ref={ref} | ||
element={element} | ||
{...safelySpreadBoxProps(props)} | ||
> | ||
{children} | ||
</Box> | ||
) | ||
); | ||
|
||
FormControlTwoColumn.displayName = 'FormControlTwoColumn'; | ||
|
||
FormControlTwoColumn.propTypes = { | ||
children: PropTypes.node, | ||
element: PropTypes.string, | ||
}; |
Oops, something went wrong.