Skip to content

Commit 6d7ae07

Browse files
PD-1669 Add size variants to text input (#1072)
* update text input to include size variants * prettier fixes * updated website component * update readme again * prettier fixes * added input text size * changeset update * changed size default to default * add padding * remove medium size, add baseFontSize * naming updates, prettier fixes * update error font size * PD-1694 Upgrade `lib` dep in expandable card (#1073) * Update package.json * Create shiny-sloths-joke.md * removed submodules and updated node version in readme (#1075) Co-authored-by: Adam Thompson <[email protected]>
1 parent e54d176 commit 6d7ae07

File tree

7 files changed

+177
-24
lines changed

7 files changed

+177
-24
lines changed

.changeset/fifty-zoos-wait.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@leafygreen-ui/text-input': minor
3+
---
4+
5+
Add sizeVariant prop

packages/text-input/README.md

+18-16
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,24 @@ return (
6868

6969
## Properties
7070

71-
| Prop | Type | Description | Default |
72-
| -------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------- |
73-
| `id` | `string` | id associated with the TextInput component. | |
74-
| `label` | `string` | Text shown in bold above the input element. | |
75-
| `description` | `string` | Text that gives more detail about the requirements for the input. | |
76-
| `optional` | `boolean` | Marks the input as optional | `false` |
77-
| `disabled` | `boolean` | Disabled the input | `false` |
78-
| `onChange` | `function` | The event handler function for the 'onchange' event. Accepts the change event object as its argument and returns nothing. | |
79-
| `placeholder` | `string` | The placeholder text shown in the input field before the user begins typing. | |
80-
| `errorMessage` | `string` | Text that gives more detail about the requirements for the input. | |
81-
| `state` | `'none'`, `'valid'`, `'error'` | Describes the state of the TextInput element before and after the input has been validated | `'none'` |
82-
| `value` | `string` | Sets the HTML `value` attribute. | `''` |
83-
| `className` | `string` | Adds a className to the class attribute. | `''` |
84-
| `type` | `'email'`, `'password'`, `'search'`, `'text'`, `'url'`, `'tel'`, `'number'` | Sets type for TextInput | `'text'` |
85-
| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` |
86-
| ... | native `input` attributes | Any other props will be spread on the root `input` element | |
71+
| Prop | Type | Description | Default |
72+
| -------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------- |
73+
| `id` | `string` | id associated with the TextInput component. | |
74+
| `label` | `string` | Text shown in bold above the input element. | |
75+
| `description` | `string` | Text that gives more detail about the requirements for the input. | |
76+
| `optional` | `boolean` | Marks the input as optional | `false` |
77+
| `disabled` | `boolean` | Disabled the input | `false` |
78+
| `onChange` | `function` | The event handler function for the 'onchange' event. Accepts the change event object as its argument and returns nothing. | |
79+
| `placeholder` | `string` | The placeholder text shown in the input field before the user begins typing. | |
80+
| `errorMessage` | `string` | Text that gives more detail about the requirements for the input. | |
81+
| `state` | `'none'`, `'valid'`, `'error'` | Describes the state of the TextInput element before and after the input has been validated | `'none'` |
82+
| `value` | `string` | Sets the HTML `value` attribute. | `''` |
83+
| `className` | `string` | Adds a className to the class attribute. | `''` |
84+
| `type` | `'email'`, `'password'`, `'search'`, `'text'`, `'url'`, `'tel'`, `'number'` | Sets type for TextInput | `'text'` |
85+
| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` |
86+
| `sizeVariant` | `'xsmall'`, `'small'`, `'default'`, `'large'`, | Sets the side padding, text size, and input height. | `default` |
87+
| `baseFontSize` | `14`, `16` | Determines the base font-size of the component if the sizeVariant prop is set to default | `14` |
88+
| ... | native `input` attributes | Any other props will be spread on the root `input` element | |
8789

8890
### Special Case: Aria Labels
8991

packages/text-input/src/TextInput.spec.tsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@testing-library/react';
88
import userEvent from '@testing-library/user-event';
99
import { axe } from 'jest-axe';
10-
import TextInput, { State } from './TextInput';
10+
import TextInput, { State, SizeVariant } from './TextInput';
1111

1212
const error = 'This is the error message';
1313
const validEmail = '[email protected]';
@@ -222,6 +222,21 @@ describe('packages/text-input', () => {
222222
});
223223
});
224224

225+
describe('when the "sizeVariant" is "large"', () => {
226+
test('check if font-size is 18px', () => {
227+
const { label } = renderTextInput({
228+
value: validEmail,
229+
sizeVariant: SizeVariant.Large,
230+
optional: true,
231+
...defaultProps,
232+
});
233+
234+
expect(label).toHaveStyle({
235+
fontSize: '18px',
236+
});
237+
});
238+
});
239+
225240
/* eslint-disable jest/expect-expect, jest/no-disabled-tests */
226241
describe.skip('types behave as expected', () => {
227242
test('TextInput throws error when neither aria-labelledby or label is supplied', () => {

packages/text-input/src/TextInput.story.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,15 @@ storiesOf('TextInput', module)
4242
Object.values(TextInputType),
4343
TextInputType.Text,
4444
)}
45+
sizeVariant={select(
46+
'Size Variant',
47+
['xsmall', 'small', 'default', 'large'],
48+
'default',
49+
)}
4550
errorMessage={text('Error Message', 'This is an error message')}
4651
darkMode={darkMode}
4752
handleValidation={value => console.log(`handleValidation ${value}`)}
53+
baseFontSize={select('Base Font Size', [14, 16], 14)}
4854
/>
4955
</div>
5056
</LeafyGreenProvider>

packages/text-input/src/TextInput.tsx

+105-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ const Mode = {
3939

4040
type Mode = typeof Mode[keyof typeof Mode];
4141

42+
export const SizeVariant = {
43+
XSmall: 'xsmall',
44+
Small: 'small',
45+
Default: 'default',
46+
Large: 'large',
47+
} as const;
48+
49+
export type SizeVariant = typeof SizeVariant[keyof typeof SizeVariant];
50+
51+
export const BaseFontSize = 14 | 16;
52+
53+
export type BaseFontSize = typeof BaseFontSize;
54+
4255
interface TextInputProps extends HTMLElementProps<'input', HTMLInputElement> {
4356
/**
4457
* id associated with the TextInput component.
@@ -107,6 +120,18 @@ interface TextInputProps extends HTMLElementProps<'input', HTMLInputElement> {
107120
handleValidation?: (value: string) => void;
108121

109122
['aria-labelledby']?: string;
123+
124+
/**
125+
* determines the font size and padding.
126+
*/
127+
128+
sizeVariant?: SizeVariant;
129+
130+
/**
131+
* determines the base font size if sizeVariant is set to default.
132+
*/
133+
134+
baseFontSize?: BaseFontSize;
110135
}
111136

112137
type AriaLabels = 'label' | 'aria-labelledby';
@@ -229,16 +254,26 @@ const interactionRingColor: Record<Mode, Record<'valid' | 'error', string>> = {
229254
},
230255
};
231256

257+
interface SizeSet {
258+
inputHeight: number;
259+
inputText: number;
260+
text: number;
261+
lineHeight: number;
262+
padding: number;
263+
}
264+
232265
function getStatefulInputStyles({
233266
state,
234267
optional,
235268
mode,
236269
disabled,
270+
sizeSet,
237271
}: {
238272
state: State;
239273
optional: boolean;
240274
mode: Mode;
241275
disabled: boolean;
276+
sizeSet: SizeSet;
242277
}) {
243278
switch (state) {
244279
case State.Valid: {
@@ -264,13 +299,47 @@ function getStatefulInputStyles({
264299

265300
default: {
266301
return css`
267-
padding-right: ${optional ? 60 : 12}px;
302+
padding-right: ${optional ? 60 : sizeSet.padding}px;
268303
border-color: ${colorSets[mode].defaultBorder};
269304
`;
270305
}
271306
}
272307
}
273308

309+
function getSizeSets(baseFontSize: BaseFontSize, sizeVariant: SizeVariant) {
310+
const sizeSets: Record<SizeVariant, SizeSet> = {
311+
[SizeVariant.XSmall]: {
312+
inputHeight: 22,
313+
inputText: 12,
314+
text: 14,
315+
lineHeight: 20,
316+
padding: 10,
317+
},
318+
[SizeVariant.Small]: {
319+
inputHeight: 28,
320+
inputText: 14,
321+
text: 14,
322+
lineHeight: 20,
323+
padding: 10,
324+
},
325+
[SizeVariant.Default]: {
326+
inputHeight: 36,
327+
inputText: baseFontSize,
328+
text: baseFontSize,
329+
lineHeight: 20,
330+
padding: 12,
331+
},
332+
[SizeVariant.Large]: {
333+
inputHeight: 48,
334+
inputText: 18,
335+
text: 18,
336+
lineHeight: 22,
337+
padding: 16,
338+
},
339+
};
340+
return sizeSets[sizeVariant];
341+
}
342+
274343
/**
275344
* # TextInput
276345
*
@@ -291,6 +360,7 @@ function getStatefulInputStyles({
291360
* @param props.value The current value of the input field. If a value is passed to this prop, component will be controlled by consumer.
292361
* @param props.className className supplied to the TextInput container.
293362
* @param props.darkMode determines whether or not the component appears in dark mode.
363+
* @param props.sizeVariant determines the size of the text and the height of the input.
294364
*/
295365
const TextInput: React.ComponentType<
296366
React.PropsWithRef<AccessibleTextInputProps>
@@ -310,8 +380,10 @@ const TextInput: React.ComponentType<
310380
value: controlledValue,
311381
className,
312382
darkMode = false,
383+
sizeVariant = SizeVariant.Default,
313384
'aria-labelledby': ariaLabelledby,
314385
handleValidation,
386+
baseFontSize = 14,
315387
...rest
316388
}: AccessibleTextInputProps,
317389
forwardRef: React.Ref<HTMLInputElement>,
@@ -321,6 +393,7 @@ const TextInput: React.ComponentType<
321393
const [uncontrolledValue, setValue] = useState('');
322394
const value = isControlled ? controlledValue : uncontrolledValue;
323395
const id = useIdAllocator({ prefix: 'textinput', id: propsId });
396+
const sizeSet = getSizeSets(baseFontSize, sizeVariant);
324397

325398
// Validation
326399
const validation = useValidation<HTMLInputElement>(handleValidation);
@@ -356,12 +429,27 @@ const TextInput: React.ComponentType<
356429
return (
357430
<div className={cx(textInputStyle, className)}>
358431
{label && (
359-
<Label darkMode={darkMode} htmlFor={id} disabled={disabled}>
432+
<Label
433+
darkMode={darkMode}
434+
htmlFor={id}
435+
disabled={disabled}
436+
className={cx(css`
437+
font-size: ${sizeSet.text}px;
438+
`)}
439+
>
360440
{label}
361441
</Label>
362442
)}
363443
{description && (
364-
<Description darkMode={darkMode}>{description}</Description>
444+
<Description
445+
darkMode={darkMode}
446+
className={cx(css`
447+
font-size: ${sizeSet.text}px;
448+
line-height: ${sizeSet.lineHeight}px;
449+
`)}
450+
>
451+
{description}
452+
</Description>
365453
)}
366454
<div className={inputContainerStyle}>
367455
<InteractionRing
@@ -386,6 +474,9 @@ const TextInput: React.ComponentType<
386474
css`
387475
color: ${colorSets[mode].inputColor};
388476
background-color: ${colorSets[mode].inputBackgroundColor};
477+
font-size: ${sizeSet.inputText}px;
478+
height: ${sizeSet.inputHeight}px;
479+
padding-left: ${sizeSet.padding}px;
389480
390481
&:focus {
391482
border: 1px solid ${colorSets[mode].inputBackgroundColor};
@@ -410,7 +501,13 @@ const TextInput: React.ComponentType<
410501
}
411502
}
412503
`,
413-
getStatefulInputStyles({ state, optional, mode, disabled }),
504+
getStatefulInputStyles({
505+
state,
506+
optional,
507+
mode,
508+
disabled,
509+
sizeSet,
510+
}),
414511
)}
415512
value={value}
416513
required={!optional}
@@ -462,6 +559,8 @@ const TextInput: React.ComponentType<
462559
errorMessageStyle,
463560
css`
464561
color: ${colorSets[mode].errorMessage};
562+
font-size: ${sizeSet.text}px;
563+
line-height: ${sizeSet.lineHeight}px;
465564
`,
466565
)}
467566
>
@@ -487,6 +586,8 @@ TextInput.propTypes = {
487586
state: PropTypes.oneOf(Object.values(State)),
488587
value: PropTypes.string,
489588
className: PropTypes.string,
589+
sizeVariant: PropTypes.oneOf(Object.values(SizeVariant)),
590+
baseFontSize: PropTypes.oneOf([14, 16]),
490591
};
491592

492593
export default TextInput;

packages/text-input/src/index.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
import TextInput, { TextInputType, State } from './TextInput';
2-
export { TextInputType, State };
1+
import TextInput, {
2+
TextInputType,
3+
State,
4+
SizeVariant,
5+
BaseFontSize,
6+
} from './TextInput';
7+
export { TextInputType, State, SizeVariant, BaseFontSize };
38
export default TextInput;

website/pages/component/text-input/example.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React from 'react';
2-
import TextInput, { State, TextInputType } from '@leafygreen-ui/text-input';
2+
import TextInput, {
3+
State,
4+
TextInputType,
5+
SizeVariant,
6+
BaseFontSize,
7+
} from '@leafygreen-ui/text-input';
38
import LiveExample, { KnobsConfigInterface } from 'components/live-example';
49

510
const knobsConfig: KnobsConfigInterface<{
@@ -12,6 +17,8 @@ const knobsConfig: KnobsConfigInterface<{
1217
type: TextInputType;
1318
darkMode: boolean;
1419
errorMessage: string;
20+
sizeVariant: SizeVariant;
21+
baseFontSize: BaseFontSize;
1522
}> = {
1623
label: {
1724
type: 'text',
@@ -50,6 +57,12 @@ const knobsConfig: KnobsConfigInterface<{
5057
default: TextInputType.Text,
5158
label: 'Type',
5259
},
60+
sizeVariant: {
61+
type: 'select',
62+
options: Object.values(SizeVariant),
63+
default: 'default',
64+
label: 'Size Variant',
65+
},
5366
darkMode: {
5467
type: 'boolean',
5568
default: false,
@@ -61,6 +74,12 @@ const knobsConfig: KnobsConfigInterface<{
6174
'The team name that you entered is not unique, please pick another',
6275
label: 'Error Message',
6376
},
77+
baseFontSize: {
78+
type: 'select',
79+
options: [14, 16],
80+
default: 14,
81+
label: 'Base Font Size',
82+
},
6483
};
6584

6685
export default function TextInputLiveExample() {

0 commit comments

Comments
 (0)