Skip to content

Commit a3d3984

Browse files
authored
feat(InputField)!: introduce 2.0 component (#1898)
- this updates the separate sub-components as well as ancillary components (FieldNote, Input) - updates to variant types and mixins - export updates and added stories
1 parent f3fc767 commit a3d3984

16 files changed

+1107
-1
lines changed

src/components/Button/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { Button as default } from './Button';
22
export type { ButtonProps } from './Button';
3+
export { Button as ButtonV2 } from './Button-v2';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@import '../../design-tokens/mixins.css';
2+
3+
/*------------------------------------*\
4+
# FIELD NOTE
5+
\*------------------------------------*/
6+
7+
/**
8+
* Fieldnote
9+
*/
10+
.field-note {
11+
font: var(--eds-theme-typography-body-sm);
12+
13+
color: var(--eds-theme-color-text-utility-default-secondary);
14+
}
15+
16+
/**
17+
* Fieldnote icon
18+
*/
19+
.field-note__icon {
20+
position: relative;
21+
margin-right: 0.25rem;
22+
}
23+
24+
/**
25+
* Disabled variant
26+
*/
27+
.field-note--disabled {
28+
/* TODO-AH: figure out field note treatment and tokens */
29+
color: var(--eds-theme-color-text-utility-disabled-primary);
30+
}
31+
32+
/**
33+
* Error variant
34+
*/
35+
.field-note--error {
36+
color: var(--eds-theme-color-text-utility-critical);
37+
38+
& > .field-note__icon {
39+
/* TODO-AH: using the text token for this icon since they match and no icon tokens existed in the sheet */
40+
color: var(--eds-theme-color-text-utility-critical);
41+
}
42+
}
43+
44+
45+
.field-note--warning {
46+
color: var(--eds-theme-color-text-utility-warning);
47+
48+
& > .field-note__icon {
49+
/* TODO-AH: using the text token for this icon since they match and no icon tokens existed in the sheet */
50+
color: var(--eds-theme-color-text-utility-warning);
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { StoryObj, Meta } from '@storybook/react';
2+
import React from 'react';
3+
4+
import { FieldNote } from './FieldNote-v2';
5+
import { LinkV2 as Link } from '../Link';
6+
import Text from '../Text';
7+
8+
export default {
9+
title: 'Components/V2/FieldNote',
10+
component: FieldNote,
11+
parameters: {
12+
badges: ['intro-1.0', 'current-2.0'],
13+
},
14+
} as Meta<Args>;
15+
16+
type Args = React.ComponentProps<typeof FieldNote>;
17+
18+
export const Default: StoryObj<Args> = {
19+
args: {
20+
children: 'This is a fieldnote.',
21+
id: 'field-1',
22+
},
23+
};
24+
25+
export const WithErrorIcon: StoryObj<Args> = {
26+
args: {
27+
children: 'This is a fieldnote.',
28+
id: 'field-1',
29+
icon: 'person-add',
30+
isError: true,
31+
},
32+
};
33+
34+
export const WithIcon: StoryObj<Args> = {
35+
args: {
36+
children: 'This is a fieldnote.',
37+
id: 'field-1',
38+
icon: 'person-add',
39+
},
40+
};
41+
42+
export const WithText: StoryObj<Args> = {
43+
args: {
44+
children: (
45+
<div className="max-w-xl">
46+
<Text className="mb-6">Here is a field note that involves:</Text>
47+
<ul className="ml-4 list-disc">
48+
<li>Multiple lines</li>
49+
<li>Arbitrary HTML text</li>
50+
<li>
51+
Even <Link href="#">text links</Link>
52+
</li>
53+
</ul>
54+
</div>
55+
),
56+
id: 'field-1',
57+
},
58+
};
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import clsx from 'clsx';
2+
import type { ReactNode } from 'react';
3+
import React from 'react';
4+
import Icon, { type IconName } from '../Icon';
5+
import styles from './FieldNote-v2.module.css';
6+
7+
export interface Props {
8+
// Component API
9+
/**
10+
* Child node(s) that can be nested inside component
11+
*/
12+
children?: ReactNode;
13+
/**
14+
* CSS class names that can be appended to the component.
15+
*/
16+
className?: string;
17+
/**
18+
* HTML id for the component
19+
*/
20+
id?: string;
21+
// Design API
22+
/**
23+
* Toggles disabled styling of the field note.
24+
*/
25+
disabled?: boolean;
26+
/**
27+
* Icon to use when an "icon" variant of the avatar. Default is "dangerous"
28+
*/
29+
icon?: IconName;
30+
/**
31+
* Whether there is an error state for the field note text (and icon)
32+
*/
33+
isError?: boolean;
34+
/**
35+
* Whether there is a warning state for the field note text (and icon)
36+
*/
37+
isWarning?: boolean;
38+
}
39+
40+
/**
41+
* `import {FieldNote} from "@chanzuckerberg/eds";`
42+
*
43+
* Fieldnote component wraps text to describe other components.
44+
*/
45+
export const FieldNote = ({
46+
children,
47+
className,
48+
id,
49+
disabled,
50+
icon,
51+
isError,
52+
isWarning,
53+
...other
54+
}: Props) => {
55+
const componentClassName = clsx(
56+
styles['field-note'],
57+
disabled && styles['field-note--disabled'],
58+
isError && styles['field-note--error'],
59+
isWarning && styles['field-note--warning'],
60+
className,
61+
);
62+
63+
let iconToUse = icon;
64+
if (isError) {
65+
iconToUse = 'dangerous';
66+
} else if (isWarning) {
67+
iconToUse = 'warning';
68+
} else if (icon) {
69+
iconToUse = icon;
70+
}
71+
72+
return (
73+
<div className={componentClassName} id={id} {...other}>
74+
{(isError || isWarning || iconToUse) && (
75+
<Icon
76+
className={styles['field-note__icon']}
77+
name={iconToUse}
78+
purpose="informative"
79+
size="1rem"
80+
title={isError ? 'error' : 'warning'}
81+
/>
82+
)}
83+
{children}
84+
</div>
85+
);
86+
};
87+
88+
FieldNote.displayName = 'FieldNote';

src/components/FieldNote/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { FieldNote as default } from './FieldNote';
2+
export { FieldNote as FieldNoteV2 } from './FieldNote-v2';
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@import '../../design-tokens/mixins.css';
2+
3+
/*------------------------------------*\
4+
    # INPUT
5+
\*------------------------------------*/
6+
7+
/**
8+
* Default input styles
9+
*/
10+
.input {
11+
@mixin inputStylesV2;
12+
}

src/components/Input/Input-v2.tsx

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import clsx from 'clsx';
2+
import type { ChangeEventHandler } from 'react';
3+
import React, { forwardRef } from 'react';
4+
import styles from './Input-v2.module.css';
5+
6+
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
7+
/**
8+
* Aria-label to provide an accesible name for the text input if no visible label is provided.
9+
*/
10+
'aria-label'?: string;
11+
/**
12+
* CSS class names that can be appended to the component.
13+
*/
14+
className?: string;
15+
/**
16+
* Disables the input and prevents editing the contents
17+
*/
18+
disabled?: boolean;
19+
/**
20+
* HTML id for the component
21+
*/
22+
id?: string;
23+
/**
24+
* Gives a hint as to the type of data needed for text input
25+
*/
26+
inputMode?:
27+
| 'text'
28+
| 'email'
29+
| 'url'
30+
| 'search'
31+
| 'tel'
32+
| 'none'
33+
| 'numeric'
34+
| 'decimal';
35+
/**
36+
* Maximum number the input can take.
37+
*/
38+
max?: number | string;
39+
/**
40+
* Minimum number the input can take.
41+
*/
42+
min?: number | string;
43+
/**
44+
* HTML name attribute for the input
45+
*/
46+
name?: string;
47+
/**
48+
* Function that fires when field value has changed
49+
*/
50+
onChange?: ChangeEventHandler<HTMLInputElement>;
51+
/**
52+
* Placeholder attribute for input. Note: placeholder should be used sparingly
53+
*/
54+
placeholder?: string;
55+
/**
56+
* Toggles the form control's interactivity. When `readOnly` is set to `true`, the form control is not interactive
57+
*/
58+
readOnly?: boolean;
59+
/**
60+
* Indicates that field is required for form to be successfully submitted
61+
*/
62+
required?: boolean;
63+
/**
64+
* Title attribute on input
65+
*/
66+
title?: string;
67+
/**
68+
* HTML type attribute, allowing switching between text, password, and other HTML5 input field types
69+
*/
70+
type?: React.HTMLInputTypeAttribute;
71+
/**
72+
* The value of the input
73+
*/
74+
value?: string | number;
75+
/**
76+
* The default value of the input
77+
*/
78+
defaultValue?: string | number;
79+
// Design API
80+
/**
81+
* Error state of the form field
82+
*/
83+
isError?: boolean;
84+
/**
85+
* Whether there is a warning state for the field note text (and icon)
86+
*/
87+
isWarning?: boolean;
88+
};
89+
90+
/**
91+
* Input component for one line of text.
92+
*/
93+
export const Input = forwardRef<HTMLInputElement, InputProps>(
94+
({ className, disabled, id, isError, isWarning, ...other }, ref) => {
95+
const componentClassName = clsx(
96+
styles['input'],
97+
isError && styles['error'],
98+
isWarning && styles['warning'],
99+
className,
100+
);
101+
102+
return (
103+
<input
104+
className={componentClassName}
105+
disabled={disabled}
106+
id={id}
107+
ref={ref}
108+
{...other}
109+
/>
110+
);
111+
},
112+
);
113+
114+
Input.displayName = 'Input';

src/components/Input/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { Input as default } from './Input';
22
export type { InputProps } from './Input';
3+
export { Input as InputV2 } from './Input-v2';

0 commit comments

Comments
 (0)