diff --git a/.changeset/fifty-zoos-wait.md b/.changeset/fifty-zoos-wait.md new file mode 100644 index 0000000000..26a9e4fcc9 --- /dev/null +++ b/.changeset/fifty-zoos-wait.md @@ -0,0 +1,5 @@ +--- +'@leafygreen-ui/text-input': minor +--- + +Add sizeVariant prop diff --git a/packages/text-input/README.md b/packages/text-input/README.md index 9d4c3b463d..7eec46c620 100644 --- a/packages/text-input/README.md +++ b/packages/text-input/README.md @@ -68,22 +68,24 @@ return ( ## Properties -| Prop | Type | Description | Default | -| -------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------- | -| `id` | `string` | id associated with the TextInput component. | | -| `label` | `string` | Text shown in bold above the input element. | | -| `description` | `string` | Text that gives more detail about the requirements for the input. | | -| `optional` | `boolean` | Marks the input as optional | `false` | -| `disabled` | `boolean` | Disabled the input | `false` | -| `onChange` | `function` | The event handler function for the 'onchange' event. Accepts the change event object as its argument and returns nothing. | | -| `placeholder` | `string` | The placeholder text shown in the input field before the user begins typing. | | -| `errorMessage` | `string` | Text that gives more detail about the requirements for the input. | | -| `state` | `'none'`, `'valid'`, `'error'` | Describes the state of the TextInput element before and after the input has been validated | `'none'` | -| `value` | `string` | Sets the HTML `value` attribute. | `''` | -| `className` | `string` | Adds a className to the class attribute. | `''` | -| `type` | `'email'`, `'password'`, `'search'`, `'text'`, `'url'`, `'tel'`, `'number'` | Sets type for TextInput | `'text'` | -| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` | -| ... | native `input` attributes | Any other props will be spread on the root `input` element | | +| Prop | Type | Description | Default | +| -------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------- | +| `id` | `string` | id associated with the TextInput component. | | +| `label` | `string` | Text shown in bold above the input element. | | +| `description` | `string` | Text that gives more detail about the requirements for the input. | | +| `optional` | `boolean` | Marks the input as optional | `false` | +| `disabled` | `boolean` | Disabled the input | `false` | +| `onChange` | `function` | The event handler function for the 'onchange' event. Accepts the change event object as its argument and returns nothing. | | +| `placeholder` | `string` | The placeholder text shown in the input field before the user begins typing. | | +| `errorMessage` | `string` | Text that gives more detail about the requirements for the input. | | +| `state` | `'none'`, `'valid'`, `'error'` | Describes the state of the TextInput element before and after the input has been validated | `'none'` | +| `value` | `string` | Sets the HTML `value` attribute. | `''` | +| `className` | `string` | Adds a className to the class attribute. | `''` | +| `type` | `'email'`, `'password'`, `'search'`, `'text'`, `'url'`, `'tel'`, `'number'` | Sets type for TextInput | `'text'` | +| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` | +| `sizeVariant` | `'xsmall'`, `'small'`, `'default'`, `'large'`, | Sets the side padding, text size, and input height. | `default` | +| `baseFontSize` | `14`, `16` | Determines the base font-size of the component if the sizeVariant prop is set to default | `14` | +| ... | native `input` attributes | Any other props will be spread on the root `input` element | | ### Special Case: Aria Labels diff --git a/packages/text-input/src/TextInput.spec.tsx b/packages/text-input/src/TextInput.spec.tsx index 6ad9902867..9b8cab7a7d 100644 --- a/packages/text-input/src/TextInput.spec.tsx +++ b/packages/text-input/src/TextInput.spec.tsx @@ -7,7 +7,7 @@ import { } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { axe } from 'jest-axe'; -import TextInput, { State } from './TextInput'; +import TextInput, { State, SizeVariant } from './TextInput'; const error = 'This is the error message'; const validEmail = 'test.email@mongodb.com'; @@ -222,6 +222,21 @@ describe('packages/text-input', () => { }); }); + describe('when the "sizeVariant" is "large"', () => { + test('check if font-size is 18px', () => { + const { label } = renderTextInput({ + value: validEmail, + sizeVariant: SizeVariant.Large, + optional: true, + ...defaultProps, + }); + + expect(label).toHaveStyle({ + fontSize: '18px', + }); + }); + }); + /* eslint-disable jest/expect-expect, jest/no-disabled-tests */ describe.skip('types behave as expected', () => { test('TextInput throws error when neither aria-labelledby or label is supplied', () => { diff --git a/packages/text-input/src/TextInput.story.tsx b/packages/text-input/src/TextInput.story.tsx index 4b50428f1f..a3ca395741 100644 --- a/packages/text-input/src/TextInput.story.tsx +++ b/packages/text-input/src/TextInput.story.tsx @@ -42,9 +42,15 @@ storiesOf('TextInput', module) Object.values(TextInputType), TextInputType.Text, )} + sizeVariant={select( + 'Size Variant', + ['xsmall', 'small', 'default', 'large'], + 'default', + )} errorMessage={text('Error Message', 'This is an error message')} darkMode={darkMode} handleValidation={value => console.log(`handleValidation ${value}`)} + baseFontSize={select('Base Font Size', [14, 16], 14)} /> diff --git a/packages/text-input/src/TextInput.tsx b/packages/text-input/src/TextInput.tsx index a66b6a9c6c..75d0c9a634 100644 --- a/packages/text-input/src/TextInput.tsx +++ b/packages/text-input/src/TextInput.tsx @@ -39,6 +39,19 @@ const Mode = { type Mode = typeof Mode[keyof typeof Mode]; +export const SizeVariant = { + XSmall: 'xsmall', + Small: 'small', + Default: 'default', + Large: 'large', +} as const; + +export type SizeVariant = typeof SizeVariant[keyof typeof SizeVariant]; + +export const BaseFontSize = 14 | 16; + +export type BaseFontSize = typeof BaseFontSize; + interface TextInputProps extends HTMLElementProps<'input', HTMLInputElement> { /** * id associated with the TextInput component. @@ -107,6 +120,18 @@ interface TextInputProps extends HTMLElementProps<'input', HTMLInputElement> { handleValidation?: (value: string) => void; ['aria-labelledby']?: string; + + /** + * determines the font size and padding. + */ + + sizeVariant?: SizeVariant; + + /** + * determines the base font size if sizeVariant is set to default. + */ + + baseFontSize?: BaseFontSize; } type AriaLabels = 'label' | 'aria-labelledby'; @@ -229,16 +254,26 @@ const interactionRingColor: Record> = { }, }; +interface SizeSet { + inputHeight: number; + inputText: number; + text: number; + lineHeight: number; + padding: number; +} + function getStatefulInputStyles({ state, optional, mode, disabled, + sizeSet, }: { state: State; optional: boolean; mode: Mode; disabled: boolean; + sizeSet: SizeSet; }) { switch (state) { case State.Valid: { @@ -264,13 +299,47 @@ function getStatefulInputStyles({ default: { return css` - padding-right: ${optional ? 60 : 12}px; + padding-right: ${optional ? 60 : sizeSet.padding}px; border-color: ${colorSets[mode].defaultBorder}; `; } } } +function getSizeSets(baseFontSize: BaseFontSize, sizeVariant: SizeVariant) { + const sizeSets: Record = { + [SizeVariant.XSmall]: { + inputHeight: 22, + inputText: 12, + text: 14, + lineHeight: 20, + padding: 10, + }, + [SizeVariant.Small]: { + inputHeight: 28, + inputText: 14, + text: 14, + lineHeight: 20, + padding: 10, + }, + [SizeVariant.Default]: { + inputHeight: 36, + inputText: baseFontSize, + text: baseFontSize, + lineHeight: 20, + padding: 12, + }, + [SizeVariant.Large]: { + inputHeight: 48, + inputText: 18, + text: 18, + lineHeight: 22, + padding: 16, + }, + }; + return sizeSets[sizeVariant]; +} + /** * # TextInput * @@ -291,6 +360,7 @@ function getStatefulInputStyles({ * @param props.value The current value of the input field. If a value is passed to this prop, component will be controlled by consumer. * @param props.className className supplied to the TextInput container. * @param props.darkMode determines whether or not the component appears in dark mode. + * @param props.sizeVariant determines the size of the text and the height of the input. */ const TextInput: React.ComponentType< React.PropsWithRef @@ -310,8 +380,10 @@ const TextInput: React.ComponentType< value: controlledValue, className, darkMode = false, + sizeVariant = SizeVariant.Default, 'aria-labelledby': ariaLabelledby, handleValidation, + baseFontSize = 14, ...rest }: AccessibleTextInputProps, forwardRef: React.Ref, @@ -321,6 +393,7 @@ const TextInput: React.ComponentType< const [uncontrolledValue, setValue] = useState(''); const value = isControlled ? controlledValue : uncontrolledValue; const id = useIdAllocator({ prefix: 'textinput', id: propsId }); + const sizeSet = getSizeSets(baseFontSize, sizeVariant); // Validation const validation = useValidation(handleValidation); @@ -356,12 +429,27 @@ const TextInput: React.ComponentType< return (
{label && ( -