-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(TextInputFloating): new component (#874)
* wip * wip * lint * Create TextInputFloating.test.tsx * style lint * update prefix and suffix styles * div wrapper * safari workaround * Update FormControls.stories.mdx * Update FormTheming.stories.mdx * Create TextInputFloating.Playground.stories.tsx * visual tests
- Loading branch information
1 parent
20c3fbb
commit a19211b
Showing
9 changed files
with
1,700 additions
and
76 deletions.
There are no files selected for viewing
295 changes: 295 additions & 0 deletions
295
src/components/TextInputFloating/TextInputFloating.Overview.stories.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. name@email.com" | ||
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). |
88 changes: 88 additions & 0 deletions
88
src/components/TextInputFloating/TextInputFloating.Playground.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ', | ||
}; |
Oops, something went wrong.