Skip to content
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

feat(TextInputFloating): new floating label text input component #874

Merged
merged 12 commits into from
Feb 29, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
import { useState } from 'react';
import { action } from '@storybook/addon-actions';
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { TextInputFloating } from './TextInputFloating';
import { Box } from '../Box/Box';
import { Icon } from '../Icon/Icon';

<Meta
title="Components/TextInputFloating/Overview"
component={TextInputFloating}
parameters={{
controls: { hideNoControlsWarning: true },
}}
/>

# TextInputFloating

Use `TextInputFloating` to create a floating label input field. It is a controlled component, which means that you need to provide a value and an `onChange` handler to update the value.

<Canvas withSource="open">
<Story name="Default">
{() => {
const [value, setValue] = useState('');
const [valuePw, setValuePw] = useState('');
return (
<Box gap="md">
<TextInputFloating
id="emailInput"
value={value}
label="Email"
placeholder="Enter your email address"
onChange={event => setValue(event.target.value)}
/>
<TextInputFloating
id="passwordInput"
value={valuePw}
label="Password"
type="password"
placeholder="Enter your password"
onChange={event => setValuePw(event.target.value)}
helpText="Password must be at least 8 characters long"
/>
</Box>
);
}}
</Story>
</Canvas>

## Props

<ArgsTable of={TextInputFloating} />

### Required

Use the `isRequired` prop to set the `required` and `aria-required` on the underlying input element.

<Canvas>
<Story name="Required">
{() => {
const [value, setValue] = useState('');
return (
<TextInputFloating
id="requiredInput"
value={value}
label="Required Input"
placeholder="Enter your email address"
onChange={event => setValue(event.target.value)}
isRequired
/>
);
}}
</Story>
</Canvas>

Customize the required indicator with the `requiredIndicator` prop.

<Canvas>
<Story name="Custom Required Indicator">
{() => {
const [value, setValue] = useState('');
return (
<TextInputFloating
id="customRequiredInput"
value={value}
label="Required Input"
onChange={event => setValue(event.target.value)}
isRequired
requiredIndicator=" (required)"
/>
);
}}
</Story>
</Canvas>

### Help Text

Use `helpText` to provide additional information about the input field.

<Canvas>
<Story name="Help Text">
{() => {
const [value, setValue] = useState('');
return (
<TextInputFloating
id="helpTextStory"
value={value}
label="Inverter Manufacturer"
onChange={event => setValue(event.target.value)}
helpText="Must be from the approved vendor list"
/>
);
}}
</Story>
</Canvas>

### Validation Error

Use the `error` prop to mark the input as invalid. `error` accepts a `boolean`, `string`, or `node`.

<Canvas>
<Story name="Error">
{() => {
const [value, setValue] = useState('');
const [value2, setValue2] = useState('Invalid Value');
return (
<Box gap="md">
<TextInputFloating
id="error1"
value={value}
label="email"
error="Required Input"
onChange={event => setValue(event.target.value)}
isRequired
/>
<TextInputFloating
id="error2"
value={value2}
label="email"
error="Must be a valid email address"
helpText="e.g. [email protected]"
onChange={event => setValue2(event.target.value)}
/>
</Box>
);
}}
</Story>
</Canvas>

### Disabled

Add `isDisabled`to give it a grayed out appearance, remove pointer events, and prevent focusing.

<Canvas>
<Story name="Disabled">
{() => {
const [value, setValue] = useState('');
const [value2, setValue2] = useState('Value');
return (
<Box gap="md">
<TextInputFloating
id="disabled"
value={value}
label="email"
onChange={event => setValue(event.target.value)}
isDisabled
/>
<TextInputFloating
id="disabledValue"
value={value2}
label="Disabled with Value"
onChange={event => setValue2(event.target.value)}
isDisabled
/>
</Box>
);
}}
</Story>
</Canvas>

### Clearable

Use the `onClear` prop to display a clear icon (x) when the input has a value. `onClear` will fire
a callback function when the clear icon is clicked, which can then be handled to clear the value.

<Canvas>
<Story name="Clearable">
{() => {
const [value, setValue] = useState('clear me');
return (
<TextInputFloating
id="clearableTextInput"
value={value}
label="Clearable Input"
onChange={event => setValue(event.target.value)}
onClear={event => setValue('')}
isRequired
/>
);
}}
</Story>
</Canvas>

### Prefix and Suffix

All that is required to render a basic version of the TextInput is a unique `id`, `label`, `value`, and an onchange event handler passed to the `onChange` prop.

<Canvas>
<Story name="With Prefix and Suffix">
{() => {
const [prefixValue0, setPrefixValue0] = useState('');
const [prefixValue1, setPrefixValue1] = useState('palmettosolar');
const [prefixValue2, setPrefixValue2] = useState('2.51');
const [prefixValue3, setPrefixValue3] = useState('');
const [prefixValue4, setPrefixValue4] = useState('Pre-populated Value');
return (
<Box gap="md">
<TextInputFloating
id="prefixSuffix0"
value={prefixValue0}
label="website address"
onChange={event => setPrefixValue0(event.target.value)}
prefix="https://"
placeholder="Enter your website address"
/>
<TextInputFloating
id="prefixSuffix1"
value={prefixValue1}
label="Social Media Handle"
onChange={event => setPrefixValue1(event.target.value)}
prefix="@"
/>
<TextInputFloating
id="prefixSuffix2"
value={prefixValue2}
label="Price Per Watt"
onChange={event => setPrefixValue2(event.target.value)}
prefix="$"
suffix="/watt"
/>
<TextInputFloating
id="prefixSuffix3"
value={prefixValue3}
label="Suffix Without Value"
onChange={event => setPrefixValue3(event.target.value)}
suffix={<Icon name="book" />}
/>
<TextInputFloating
id="prefixSuffix4"
value={prefixValue4}
label="Suffix with Clear"
placeholder="Contact name"
onChange={event => setPrefixValue4(event.target.value)}
onClear={event => setPrefixValue4('')}
suffix={<Icon name="search" />}
/>
</Box>
);
}}
</Story>
</Canvas>

### Sizes

There are only two sizes for TextInputFloating, `md` and `lg`, with `md` being the default.

<Canvas>
<Story name="Sizes">
{() => {
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
return (
<Box gap="md">
<TextInputFloating
id="mdTextInput"
value={value1}
label="Medium Input"
size="md"
onChange={event => setValue1(event.target.value)}
/>
<TextInputFloating
id="lgTextInput"
value={value2}
label="Large Input"
size="lg"
onChange={event => setValue2(event.target.value)}
/>
</Box>
);
}}
</Story>
</Canvas>

## Component Design Tokens

This component shares component design tokens with all form controls. For a complete list of tokens, see the [Theming Form Controls documentation](/docs/theming-form-controls--custom-theme-form).
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import { Meta, Story } from '@storybook/react/types-6-0';
import { TextInputFloating, TextInputFloatingProps } from './TextInputFloating';

export default {
title: 'Components/TextInputFloating/Playground',
component: TextInputFloating,
argTypes: {
autoComplete: {
control: 'boolean',
},
id: {
control: 'text',
},
label: {
control: 'text',
},
name: {
control: 'text',
},
value: {
control: 'text',
},
autoFocus: {
control: 'boolean',
},
error: {
control: 'text',
},
helpText: {
control: 'text',
},
hideLabel: {
control: 'boolean',
},
isClearable: {
control: 'boolean',
},
isDisabled: {
control: 'boolean',
},
isRequired: {
control: 'boolean',
},
className: {
control: 'text',
},
placeholder: {
control: 'text',
},
prefix: {
control: 'text',
},
suffix: {
control: 'text',
},
maxLength: {
control: 'number',
},
size: {
control: {
type: 'radio',
options: ['md', 'lg'],
},
},
requiredIndicator: {
control: 'text',
},
type: {
control: {
type: 'select',
options: ['text', 'password', 'email', 'tel', 'url', 'search'],
},
},
},
} as Meta;

const Template: Story<TextInputFloatingProps> = ({ ...args }) => (
<TextInputFloating {...args} />
);

export const Playground = Template.bind({});
Playground.args = {
id: 'playgroundTextInputFloating ',
label: 'Playground TextInputFloating ',
helpText: 'Helpful text',
name: 'playgroundTextInputFloating ',
};
Loading
Loading