feat(components): add TextInput

This commit is contained in:
Eric Liu 2019-12-15 15:25:48 -08:00
commit e786e0e78f
8 changed files with 362 additions and 11 deletions

View file

@ -18,16 +18,16 @@ Currently, the following components are supported:
- Accordion
- AccordionItem
- AccordionSkeleton
- AccordionSkeleton
- Breadcrumb
- BreadcrumbItem
- BreadcrumbSkeleton
- BreadcrumbSkeleton
- Button
- ButtonSkeleton
- ButtonSkeleton
- Checkbox
- CheckboxSkeleton
- CheckboxSkeleton
- CodeSnippet
- CodeSnippetSkeleton
- CodeSnippetSkeleton
- Copy
- CopyButton
- InlineLoading
@ -36,19 +36,22 @@ Currently, the following components are supported:
- ListItem
- OrderedList
- RadioButton
- RadioButtonSkeleton
- RadioButtonSkeleton
- Search
- SearchSkeleton
- SearchSkeleton
- SkeletonPlaceholder
- SkeletonText
- Tag
- TagSkeleton
- TagSkeleton
- TextArea
- TextAreaSkeleton
- TextAreaSkeleton
- TextInput
- TextInputSkeleton
- PasswordInput
- Toggle
- ToggleSkeleton
- ToggleSkeleton
- ToggleSmall
- ToggleSmallSkeleton
- ToggleSmallSkeleton
- TooltipDefinition
- TooltipIcon
- UnorderedList

View file

@ -0,0 +1,112 @@
<script>
let className = undefined;
export { className as class };
export let disabled = false;
export let id = Math.random();
export let labelText = '';
export let placeholder = '';
export let type = 'password';
export let value = '';
export let invalid = false;
export let invalidText = '';
export let helperText = '';
export let hideLabel = false;
export let light = false;
export let tooltipPosition = 'bottom';
export let tooltipAlignment = 'center';
export let hidePasswordLabel = 'Hide password';
export let showPasswordLabel = 'Show password';
export let props = {};
import { createEventDispatcher } from 'svelte';
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16';
import View16 from 'carbon-icons-svelte/lib/View16';
import ViewOff16 from 'carbon-icons-svelte/lib/ViewOff16';
import { cx } from '../../lib';
const dispatch = createEventDispatcher();
const errorId = `${id}-error`;
const passwordIsVisible = type === 'text';
const _labelClass = cx(
'--label',
hideLabel && '--visually-hidden',
disabled && '--label--disabled'
);
const _helperTextClass = cx('--form__helper-text', disabled && '--form__helper-text--disabled');
const _textInputClass = cx(
'--text-input',
'--password-input',
light && '--text-input--light',
invalid && '--text-input--invalid',
className
);
const _passwordVisibilityToggleClass = cx(
'--text-input--password__visibility__toggle',
'--btn--icon-only',
'--tooltip__trigger',
'--tooltip--a11y',
tooltipPosition && `--tooltip--${tooltipPosition}`,
tooltipAlignment && `--tooltip--align-${tooltipAlignment}`
);
</script>
<div class={cx('--form-item', '--text-input-wrapper', '--password-input-wrapper')}>
{#if labelText}
<label for={id} class={_labelClass}>{labelText}</label>
{/if}
{#if helperText}
<div class={_helperTextClass}>{helperText}</div>
{/if}
<div class={cx('--text-input__field-wrapper')} data-invalid={invalid || undefined}>
{#if invalid}
<WarningFilled16 class={cx('--text-input__invalid-icon')} />
{/if}
<input
{...props}
class={_textInputClass}
on:click={event => {
if (!disabled) {
dispatch('click', event);
}
}}
on:change={event => {
if (!disabled) {
dispatch('change', event);
}
}}
on:input={event => {
value = event.target.value;
if (!disabled) {
dispatch('input', event);
}
}}
data-invalid={invalid || undefined}
aria-invalid={invalid || undefined}
aria-describedby={invalid ? errorId : undefined}
{id}
{placeholder}
{type}
{value}
{disabled} />
<button
type="button"
class={_passwordVisibilityToggleClass}
on:click={() => {
type = type === 'password' ? 'text' : 'password';
}}>
<span class={cx('--assistive-text')}>
{#if passwordIsVisible}{hidePasswordLabel}{:else}{showPasswordLabel}{/if}
</span>
{#if passwordIsVisible}
<ViewOff16 class={cx('--icon-visibility-off')} />
{:else}
<View16 class={cx('--icon-visibility-on')} />
{/if}
</button>
</div>
{#if invalid}
<div class={cx('--form-requirement')} id={errorId}>{invalidText}</div>
{/if}
</div>

View file

@ -0,0 +1,17 @@
<script>
let className = undefined;
export { className as class };
export let hideLabel = false;
export let props = {};
import { cx } from '../../lib';
const _class = cx('--form-item', className);
</script>
<div {...props} class={_class}>
{#if !hideLabel}
<span class={cx('--label', '--skeleton')} />
{/if}
<div class={cx('--skeleton', '--text-area')} />
</div>

View file

@ -0,0 +1,42 @@
<script>
export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte';
import TextInput from './TextInput.svelte';
import PasswordInput from './PasswordInput.svelte';
import TextInputSkeleton from './TextInput.Skeleton.svelte';
let value = '';
let type = 'password';
</script>
<Layout>
{value}
{#if story === 'skeleton'}
<div aria-label="loading text input" aria-live="assertive" role="status" tabindex="0">
<TextInputSkeleton />
<br />
<TextInputSkeleton hideLabel />
</div>
{:else if story === 'password-visibility'}
<PasswordInput {...$$props} aria-level="" />
{:else if story === 'controlled'}
<PasswordInput {...$$props} {type} />
<div>
<button
on:click={() => {
type = 'text';
}}>
Show password
</button>
<button
on:click={() => {
type = 'password';
}}>
Hide password
</button>
</div>
{:else}
<TextInput {...$$props} bind:value />
{/if}
</Layout>

View file

@ -0,0 +1,89 @@
import { withKnobs, boolean, text, select } from '@storybook/addon-knobs';
import Component from './TextInput.Story.svelte';
export default { title: 'TextInput', decorators: [withKnobs] };
export const Default = () => ({
Component,
props: {
disabled: boolean('Disabled (disabled)', false),
light: boolean('Light variant (light)', false),
hideLabel: boolean('No label (hideLabel)', false),
labelText: text('Label text (labelText)', 'Text Input label'),
invalid: boolean('Show form validation UI (invalid)', false),
invalidText: text('Content of form validation UI (invalidText)', 'A valid value is required'),
helperText: text('Helper text (helperText)', 'Optional helper text.'),
placeholder: text('Placeholder text (placeholder)', 'Placeholder text.'),
id: 'text-input'
}
});
export const TogglePasswordVisibility = () => ({
Component,
props: {
story: 'password-visibility',
disabled: boolean('Disabled (disabled)', false),
light: boolean('Light variant (light)', false),
hideLabel: boolean('No label (hideLabel)', false),
labelText: text('Label text (labelText)', 'Text Input label'),
invalid: boolean('Show form validation UI (invalid)', false),
invalidText: text('Content of form validation UI (invalidText)', 'A valid value is required'),
helperText: text('Helper text (helperText)', 'Optional helper text.'),
placeholder: text('Placeholder text (placeholder)', 'Placeholder text.'),
id: 'text-input',
tooltipPosition: select(
'Tooltip position (tooltipPosition)',
['top', 'right', 'bottom', 'left'],
'bottom'
),
tooltipAlignment: select(
'Tooltip alignment (tooltipAlignment)',
['start', 'center', 'end'],
'center'
),
hidePasswordLabel: text(
'"Hide password" tooltip label for password visibility toggle (hidePasswordLabel)',
'Hide password'
),
showPasswordLabel: text(
'"Show password" tooltip label for password visibility toggle (showPasswordLabel)',
'Show password'
)
}
});
export const ControlledTogglePasswordVisibility = () => ({
Component,
props: {
story: 'controlled',
disabled: boolean('Disabled (disabled)', false),
light: boolean('Light variant (light)', false),
hideLabel: boolean('No label (hideLabel)', false),
labelText: text('Label text (labelText)', 'Text Input label'),
invalid: boolean('Show form validation UI (invalid)', false),
invalidText: text('Content of form validation UI (invalidText)', 'A valid value is required'),
helperText: text('Helper text (helperText)', 'Optional helper text.'),
placeholder: text('Placeholder text (placeholder)', 'Placeholder text.'),
id: 'text-input',
tooltipPosition: select(
'Tooltip position (tooltipPosition)',
['top', 'right', 'bottom', 'left'],
'bottom'
),
tooltipAlignment: select(
'Tooltip alignment (tooltipAlignment)',
['start', 'center', 'end'],
'center'
),
hidePasswordLabel: text(
'"Hide password" tooltip label for password visibility toggle (hidePasswordLabel)',
'Hide password'
),
showPasswordLabel: text(
'"Show password" tooltip label for password visibility toggle (showPasswordLabel)',
'Show password'
)
}
});
export const Skeleton = () => ({ Component, props: { story: 'skeleton' } });

View file

@ -0,0 +1,79 @@
<script>
let className = undefined;
export { className as class };
export let disabled = false;
export let id = Math.random();
export let labelText = '';
export let placeholder = '';
export let type = '';
export let value = '';
export let invalid = false;
export let invalidText = '';
export let helperText = '';
export let hideLabel = false;
export let light = false;
export let props = {};
import { createEventDispatcher } from 'svelte';
import WarningFilled16 from 'carbon-icons-svelte/lib/WarningFilled16';
import { cx } from '../../lib';
const dispatch = createEventDispatcher();
const errorId = `${id}-error`;
const _labelClass = cx(
'--label',
hideLabel && '--visually-hidden',
disabled && '--label--disabled'
);
const _helperTextClass = cx('--form__helper-text', disabled && '--form__helper-text--disabled');
const _textInputClass = cx(
'--text-input',
light && '--text-input--light',
invalid && '--text-input--invalid',
className
);
</script>
<div class={cx('--form-item', '--text-input-wrapper')}>
{#if labelText}
<label for={id} class={_labelClass}>{labelText}</label>
{/if}
{#if helperText}
<div class={_helperTextClass}>{helperText}</div>
{/if}
<div class={cx('--text-input__field-wrapper')} data-invalid={invalid || undefined}>
{#if invalid}
<WarningFilled16 class={cx('--text-input__invalid-icon')} />
{/if}
<input
{...props}
class={_textInputClass}
on:click={event => {
if (!disabled) {
dispatch('click', event);
}
}}
on:change={event => {
if (!disabled) {
dispatch('change', event);
}
}}
on:input={event => {
value = event.target.value;
if (!disabled) {
dispatch('input', event);
}
}}
data-invalid={invalid || undefined}
aria-invalid={invalid || undefined}
aria-describedby={invalid ? errorId : undefined}
{id}
{placeholder}
{type}
{value}
{disabled} />
</div>
{#if invalid}
<div class={cx('--form-requirement')} id={errorId}>{invalidText}</div>
{/if}
</div>

View file

@ -0,0 +1,5 @@
import TextInput from './TextInput.svelte';
export default TextInput;
export { default as TextInputSkeleton } from './TextInput.Skeleton.svelte';
export { default as PasswordInput } from './PasswordInput.svelte';

View file

@ -16,6 +16,7 @@ import SkeletonPlaceholder from './components/SkeletonPlaceholder';
import SkeletonText from './components/SkeletonText';
import Tag, { TagSkeleton } from './components/Tag';
import TextArea, { TextAreaSkeleton } from './components/TextArea';
import TextInput, { TextInputSkeleton, PasswordInput } from './components/TextInput';
import Toggle, { ToggleSkeleton } from './components/Toggle';
import ToggleSmall, { ToggleSmallSkeleton } from './components/ToggleSmall';
import TooltipDefinition from './components/TooltipDefinition';
@ -52,6 +53,9 @@ export {
TagSkeleton,
TextArea,
TextAreaSkeleton,
TextInput,
TextInputSkeleton,
PasswordInput,
Toggle,
ToggleSkeleton,
ToggleSmall,