mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-15 02:11:05 +00:00
feat(components): add TextInput
This commit is contained in:
parent
bf502f904f
commit
e786e0e78f
8 changed files with 362 additions and 11 deletions
|
@ -45,6 +45,9 @@ Currently, the following components are supported:
|
||||||
- TagSkeleton
|
- TagSkeleton
|
||||||
- TextArea
|
- TextArea
|
||||||
- TextAreaSkeleton
|
- TextAreaSkeleton
|
||||||
|
- TextInput
|
||||||
|
- TextInputSkeleton
|
||||||
|
- PasswordInput
|
||||||
- Toggle
|
- Toggle
|
||||||
- ToggleSkeleton
|
- ToggleSkeleton
|
||||||
- ToggleSmall
|
- ToggleSmall
|
||||||
|
|
112
src/components/TextInput/PasswordInput.svelte
Normal file
112
src/components/TextInput/PasswordInput.svelte
Normal 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>
|
17
src/components/TextInput/TextInput.Skeleton.svelte
Normal file
17
src/components/TextInput/TextInput.Skeleton.svelte
Normal 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>
|
42
src/components/TextInput/TextInput.Story.svelte
Normal file
42
src/components/TextInput/TextInput.Story.svelte
Normal 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>
|
89
src/components/TextInput/TextInput.stories.js
Normal file
89
src/components/TextInput/TextInput.stories.js
Normal 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' } });
|
79
src/components/TextInput/TextInput.svelte
Normal file
79
src/components/TextInput/TextInput.svelte
Normal 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>
|
5
src/components/TextInput/index.js
Normal file
5
src/components/TextInput/index.js
Normal 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';
|
|
@ -16,6 +16,7 @@ import SkeletonPlaceholder from './components/SkeletonPlaceholder';
|
||||||
import SkeletonText from './components/SkeletonText';
|
import SkeletonText from './components/SkeletonText';
|
||||||
import Tag, { TagSkeleton } from './components/Tag';
|
import Tag, { TagSkeleton } from './components/Tag';
|
||||||
import TextArea, { TextAreaSkeleton } from './components/TextArea';
|
import TextArea, { TextAreaSkeleton } from './components/TextArea';
|
||||||
|
import TextInput, { TextInputSkeleton, PasswordInput } from './components/TextInput';
|
||||||
import Toggle, { ToggleSkeleton } from './components/Toggle';
|
import Toggle, { ToggleSkeleton } from './components/Toggle';
|
||||||
import ToggleSmall, { ToggleSmallSkeleton } from './components/ToggleSmall';
|
import ToggleSmall, { ToggleSmallSkeleton } from './components/ToggleSmall';
|
||||||
import TooltipDefinition from './components/TooltipDefinition';
|
import TooltipDefinition from './components/TooltipDefinition';
|
||||||
|
@ -52,6 +53,9 @@ export {
|
||||||
TagSkeleton,
|
TagSkeleton,
|
||||||
TextArea,
|
TextArea,
|
||||||
TextAreaSkeleton,
|
TextAreaSkeleton,
|
||||||
|
TextInput,
|
||||||
|
TextInputSkeleton,
|
||||||
|
PasswordInput,
|
||||||
Toggle,
|
Toggle,
|
||||||
ToggleSkeleton,
|
ToggleSkeleton,
|
||||||
ToggleSmall,
|
ToggleSmall,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue