Skip to content

feat: Support placeholders in S2 components #8692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .storybook-s2/docs/Migrating.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export function Migrating() {
<H3>ColorField</H3>
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>Remove <Code>isQuiet</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>placeholder</Code> (it has been removed due to accessibility issues)</li>
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>validationState="valid"</Code> (it is no longer supported in Spectrum 2)</li>
</ul>
Expand All @@ -155,7 +154,6 @@ export function Migrating() {
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>Change <Code>menuWidth</Code> value from a <Code>DimensionValue</Code> to a pixel value</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>isQuiet</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>placeholder</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>validationState="valid"</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Update <Code>Item</Code> to be a <Code>ComboBoxItem</Code></li>
Expand Down Expand Up @@ -338,7 +336,6 @@ export function Migrating() {

<H3>SearchField</H3>
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>Remove <Code>placeholder</Code> (it has been removed due to accessibility issues)</li>
<li className={style({font: 'body', marginY: 8})}>[PENDING] Comment out icon (it has not been implemented yet)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>isQuiet</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
Expand Down Expand Up @@ -405,7 +402,6 @@ export function Migrating() {
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>[PENDING] Comment out <Code>icon</Code> (it has not been implemented yet)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>isQuiet</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>placeholder</Code> (it has been removed due to accessibility issues)</li>
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>validationState="valid"</Code> (it is no longer supported in Spectrum 2)</li>
</ul>
Expand All @@ -414,7 +410,6 @@ export function Migrating() {
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>[PENDING] Comment out <Code>icon</Code> (it has not been implemented yet)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>isQuiet</Code> (it is no longer supported in Spectrum 2)</li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>placeholder</Code> (it has been removed due to accessibility issues)</li>
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
<li className={style({font: 'body', marginY: 8})}>Remove <Code>validationState="valid"</Code> (it is no longer supported in Spectrum 2)</li>
</ul>
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Example: Story = {
People
</DisclosureTitle>
<DisclosurePanel>
<TextField label="Name" styles={style({maxWidth: 176})} />
<TextField label="Name" styles={style({maxWidth: 176})} placeholder="Enter your name" />
</DisclosurePanel>
</Disclosure>
</Accordion>
Expand Down Expand Up @@ -107,7 +107,7 @@ export const WithDisabledDisclosure: Story = {
People
</DisclosureTitle>
<DisclosurePanel>
<TextField label="Name" />
<TextField label="Name" placeholder="Enter your name" />
</DisclosurePanel>
</Disclosure>
</Accordion>
Expand Down Expand Up @@ -152,7 +152,7 @@ export const WithActionButton: Story = {
<ActionButton><NewIcon aria-label="new icon" /></ActionButton>
</DisclosureHeader>
<DisclosurePanel>
<TextField label="Name" styles={style({maxWidth: 176})} />
<TextField label="Name" styles={style({maxWidth: 176})} placeholder="Enter your name" />
</DisclosurePanel>
</Disclosure>
</Accordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const Template = ({combos, ...args}: ColorFieldProps & {combos: any[]}): ReactEl
key = 'default';
}
return (
<ColorField data-testid={fullComboName} defaultValue="#e21" label={key} description="test description" errorMessage="test error" {...c} {...args} />
<ColorField data-testid={fullComboName} defaultValue="#e21" label={key} description="test description" errorMessage="test error" placeholder="######" {...c} {...args} />
);
})}
</div>
Expand Down
22 changes: 11 additions & 11 deletions packages/@react-spectrum/s2/chromatic/Forms.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ type Story = StoryObj<typeof Form>;
export const Example: Story = {
render: (args) => (
<Form {...args}>
<TextField label="First Name" name="firstName" />
<TextField label="Last Name" name="firstName" />
<TextField label="Email" name="email" type="email" description="Enter an email" />
<TextField label="First Name" name="firstName" placeholder="John" />
<TextField label="Last Name" name="lastName" placeholder="Doe" />
<TextField label="Email" name="email" type="email" description="Enter an email" placeholder="[email protected]" />
<CheckboxGroup label="Favorite sports">
<Checkbox value="soccer">Soccer</Checkbox>
<Checkbox value="baseball">Baseball</Checkbox>
Expand All @@ -75,10 +75,10 @@ export const Example: Story = {
<Radio value="dog">Dog</Radio>
<Radio value="plant" isDisabled>Plant</Radio>
</RadioGroup>
<TextField label="City" name="city" description="A long description to test help text wrapping." />
<TextField label="A long label to test wrapping behavior" name="long" />
<TextField label="City" name="city" description="A long description to test help text wrapping." placeholder="Some city" />
<TextField label="A long label to test wrapping behavior" name="long" placeholder="looooooooooong" />
<SearchField label="Search" name="search" />
<TextArea label="Comment" name="comment" />
<TextArea label="Comment" name="comment" placeholder="Enter your comment here" />
<Switch>Wi-Fi</Switch>
<Checkbox>I agree to the terms</Checkbox>
<Slider label="Cookies" defaultValue={30} />
Expand All @@ -91,9 +91,9 @@ export const Example: Story = {
export const MixedForm: Story = {
render: (args) => (
<Form {...args}>
<TextField label="First Name" name="firstName" />
<TextField label="Last Name" name="firstName" />
<TextField label="Email" name="email" type="email" description="Enter an email" />
<TextField label="First Name" name="firstName" placeholder="John" />
<TextField label="Last Name" name="lastName" placeholder="Doe" />
<TextField label="Email" name="email" type="email" description="Enter an email" placeholder="[email protected]" />
<CheckboxGroup aria-label="Favorite sports">
<Checkbox value="soccer">Soccer</Checkbox>
<Checkbox value="baseball">Baseball</Checkbox>
Expand Down Expand Up @@ -143,7 +143,7 @@ const CustomLabelsExampleRender = (args: FormProps): ReactElement => {
<ToggleButton>
Enable color
</ToggleButton>
<ColorField aria-label="Fill color" styles={style({width: 144})} />
<ColorField aria-label="Fill color" styles={style({width: 144})} placeholder="######" />
<ColorSlider channel="alpha" defaultValue="#000" />
</div>
<Divider size="S" />
Expand All @@ -152,7 +152,7 @@ const CustomLabelsExampleRender = (args: FormProps): ReactElement => {
<ToggleButton>
Enable search
</ToggleButton>
<TextField aria-label="Query" styles={style({width: 144})} />
<TextField aria-label="Query" styles={style({width: 144})} placeholder="Search here" />
<ComboBox aria-label="Search terms" styles={style({width: 144})}>
<ComboBoxItem>search term 1</ComboBoxItem>
<ComboBoxItem>search term 2</ComboBoxItem>
Expand Down
16 changes: 11 additions & 5 deletions packages/@react-spectrum/s2/chromatic/TextField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ type Story = StoryObj<typeof TextField>;
export const Example: Story = {
render: (args) => <TextField {...args} />,
args: {
label: 'Name'
label: 'Name',
placeholder: 'Enter your name'
}
};

Expand All @@ -57,6 +58,7 @@ export const ContextualHelpExample: Story = {
),
args: {
label: 'Segment',
placeholder: 'Enter your name',
contextualHelp: (
<ContextualHelp>
<Heading>What is a segment?</Heading>
Expand All @@ -80,14 +82,16 @@ export const ContextualHelpExample: Story = {
export const TextAreaExample: StoryObj<typeof TextArea> = {
render: (args) => <TextArea {...args} />,
args: {
label: 'Comment'
label: 'Comment',
placeholder: 'Enter your name'
}
};

export const CustomWidth: Story = {
render: (args) => <TextField {...args} styles={style({width: 384})} />,
args: {
label: 'Name'
label: 'Name',
placeholder: 'Enter your name'
},
parameters: {
docs: {
Expand All @@ -99,7 +103,8 @@ export const CustomWidth: Story = {
export const SmallWidth: Story = {
render: (args) => <TextField {...args} styles={style({width: 48})} />,
args: {
label: 'Name'
label: 'Name',
placeholder: 'Enter your name'
},
parameters: {
docs: {
Expand All @@ -111,7 +116,8 @@ export const SmallWidth: Story = {
export const UNSAFEWidth: Story = {
render: (args) => <TextField {...args} UNSAFE_style={{width: 384}} />,
args: {
label: 'Name'
label: 'Name',
placeholder: 'Enter your name'
},
parameters: {
docs: {
Expand Down
8 changes: 6 additions & 2 deletions packages/@react-spectrum/s2/src/ColorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
import {
ColorField as AriaColorField,
ColorFieldProps as AriaColorFieldProps,
ContextValue
ContextValue,
InputProps
} from 'react-aria-components';
import {createContext, forwardRef, Ref, useContext, useImperativeHandle, useRef} from 'react';
import {createFocusableRef} from '@react-spectrum/utils';
Expand All @@ -23,9 +24,10 @@ import {FormContext, useFormProps} from './Form';
import {GlobalDOMAttributes, HelpTextProps, SpectrumLabelableProps} from '@react-types/shared';
import {style} from '../style' with {type: 'macro'};
import {TextFieldRef} from '@react-types/textfield';
import {usePlaceholderWarning} from './placeholder-utils';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps {
export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps, Pick<InputProps, 'placeholder'> {
/**
* The size of the color field.
*
Expand Down Expand Up @@ -71,6 +73,8 @@ export const ColorField = forwardRef(function ColorField(props: ColorFieldProps,
}
}));

usePlaceholderWarning(props.placeholder, 'ColorField', inputRef);

return (
<AriaColorField
{...fieldProps}
Expand Down
4 changes: 3 additions & 1 deletion packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ComboBoxStateContext,
ContextValue,
InputContext,
InputProps,
ListBox,
ListBoxItem,
ListBoxItemProps,
Expand Down Expand Up @@ -83,7 +84,8 @@ export interface ComboBoxProps<T extends object> extends
HelpTextProps,
Pick<ListBoxProps<T>, 'items' | 'dependencies'>,
Pick<AriaPopoverProps, 'shouldFlip'>,
Pick<AsyncLoadable, 'onLoadMore'> {
Pick<AsyncLoadable, 'onLoadMore'>,
Pick<InputProps, 'placeholder'> {
/** The contents of the collection. */
children: ReactNode | ((item: T) => ReactNode),
/**
Expand Down
5 changes: 4 additions & 1 deletion packages/@react-spectrum/s2/src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ export const Input = forwardRef(function Input(props: InputProps, ref: Forwarded
className={UNSAFE_className + mergeStyles(style({
padding: 0,
backgroundColor: 'transparent',
color: 'inherit',
color: {
default: 'inherit',
'::placeholder': 'gray-600'
},
fontFamily: 'inherit',
fontSize: 'inherit',
fontWeight: 'inherit',
Expand Down
5 changes: 3 additions & 2 deletions packages/@react-spectrum/s2/src/NumberField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ButtonRenderProps,
ContextValue,
InputContext,
InputProps,
useContextProps
} from 'react-aria-components';
import {baseColor, space, style} from '../style' with {type: 'macro'};
Expand All @@ -40,7 +41,8 @@ export interface NumberFieldProps extends
Omit<AriaNumberFieldProps, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>,
StyleProps,
SpectrumLabelableProps,
HelpTextProps {
HelpTextProps,
Pick<InputProps, 'placeholder'> {
/**
* Whether to hide the increment and decrement buttons.
* @default false
Expand Down Expand Up @@ -173,7 +175,6 @@ export const NumberField = forwardRef(function NumberField(props: NumberFieldPro
}
}));


return (
<AriaNumberField
ref={domRef}
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-spectrum/s2/src/SearchField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SearchField as AriaSearchField,
SearchFieldProps as AriaSearchFieldProps,
ContextValue,
InputProps,
Provider
} from 'react-aria-components';
import {baseColor, fontRelative, style} from '../style' with {type: 'macro'};
Expand All @@ -31,7 +32,7 @@ import SearchIcon from '../s2wf-icons/S2_Icon_Search_20_N.svg';
import {TextFieldRef} from '@react-types/textfield';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface SearchFieldProps extends Omit<AriaSearchFieldProps, 'className' | 'style' | 'children' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps {
export interface SearchFieldProps extends Omit<AriaSearchFieldProps, 'className' | 'style' | 'children' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps, Pick<InputProps, 'placeholder'> {
/**
* The size of the SearchField.
*
Expand Down
20 changes: 14 additions & 6 deletions packages/@react-spectrum/s2/src/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,23 @@ import {
composeRenderProps,
ContextValue,
InputContext,
InputProps,
useSlottedContext
} from 'react-aria-components';
import {centerPadding, controlSize, field, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {createContext, forwardRef, ReactNode, Ref, useContext, useImperativeHandle, useRef} from 'react';
import {createFocusableRef} from '@react-spectrum/utils';
import {FieldErrorIcon, FieldGroup, FieldLabel, HelpText, Input} from './Field';
import {FormContext, useFormProps} from './Form';
import {GlobalDOMAttributes, HelpTextProps, SpectrumLabelableProps} from '@react-types/shared';
import {GlobalDOMAttributes, HelpTextProps, RefObject, SpectrumLabelableProps} from '@react-types/shared';
import {mergeRefs} from '@react-aria/utils';
import {style} from '../style' with {type: 'macro'};
import {StyleString} from '../style/types';
import {TextFieldRef} from '@react-types/textfield';
import {usePlaceholderWarning} from './placeholder-utils';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface TextFieldProps extends Omit<AriaTextFieldProps, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps {
export interface TextFieldProps extends Omit<AriaTextFieldProps, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, StyleProps, SpectrumLabelableProps, HelpTextProps, Pick<InputProps, 'placeholder'> {
/**
* The size of the text field.
*
Expand Down Expand Up @@ -101,6 +103,8 @@ export const TextFieldBase = forwardRef(function TextFieldBase(props: TextFieldP
...textFieldProps
} = props;

usePlaceholderWarning(props.placeholder, 'TextField/Area', inputRef);

// Expose imperative interface for ref
useImperativeHandle(ref, () => ({
...createFocusableRef(domRef, inputRef),
Expand Down Expand Up @@ -159,7 +163,7 @@ export const TextFieldBase = forwardRef(function TextFieldBase(props: TextFieldP

function TextAreaInput() {
// Force re-render when value changes so we update the height.
useSlottedContext(AriaTextAreaContext) ?? {};
let {placeholder} = useSlottedContext(AriaTextAreaContext) ?? {};
let onHeightChange = (input: HTMLTextAreaElement) => {
// TODO: only do this if an explicit height is not given?
if (input) {
Expand All @@ -180,20 +184,24 @@ function TextAreaInput() {
input.style.alignSelf = prevAlignment;
}
};
let {ref} = useSlottedContext(InputContext) ?? {};

return (
<AriaTextArea
ref={onHeightChange}
ref={mergeRefs(onHeightChange, ref as RefObject<HTMLTextAreaElement | null>)}
// Workaround for baseline alignment bug in Safari.
// https://bugs.webkit.org/show_bug.cgi?id=142968
placeholder=" "
placeholder={placeholder ?? ' '}
className={style({
paddingX: 0,
paddingY: centerPadding(),
minHeight: controlSize(),
boxSizing: 'border-box',
backgroundColor: 'transparent',
color: 'inherit',
color: {
default: 'inherit',
'::placeholder': 'gray-600'
},
fontFamily: 'inherit',
fontSize: 'inherit',
fontWeight: 'inherit',
Expand Down
Loading