feat(text-area)!: integrate TextArea with v11 (#1967)

This commit is contained in:
Eric Liu 2024-04-29 21:50:33 -07:00 committed by GitHub
commit d7f0ed1947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 181 additions and 80 deletions

View file

@ -4110,6 +4110,7 @@ None.
| cols | No | <code>let</code> | No | <code>number</code> | <code>undefined</code> | Specify the number of cols | | cols | No | <code>let</code> | No | <code>number</code> | <code>undefined</code> | Specify the number of cols |
| rows | No | <code>let</code> | No | <code>number</code> | <code>4</code> | Specify the number of rows | | rows | No | <code>let</code> | No | <code>number</code> | <code>4</code> | Specify the number of rows |
| maxCount | No | <code>let</code> | No | <code>number</code> | <code>undefined</code> | Specify the max character count | | maxCount | No | <code>let</code> | No | <code>number</code> | <code>undefined</code> | Specify the max character count |
| counterMode | No | <code>let</code> | No | <code>"character" &#124; "word"</code> | <code>"character"</code> | Specify the counter mode |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant | | light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the input | | disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the input |
| readonly | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use the read-only variant | | readonly | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use the read-only variant |
@ -4118,23 +4119,23 @@ None.
| hideLabel | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to visually hide the label text | | hideLabel | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to visually hide the label text |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state | | invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the text for the invalid state | | invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the text for the invalid state |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the textarea element | | id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the textarea element |
| name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the input | | name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the input |
### Slots ### Slots
| Slot name | Default | Props | Fallback | | Slot name | Default | Props | Fallback |
| :-------- | :------ | :---- | :----------------------- | | :---------- | :------ | :---- | :------------------------- |
| labelText | No | -- | <code>{labelText}</code> | | invalidText | No | -- | <code>{invalidText}</code> |
| labelText | No | -- | <code>{labelText}</code> |
| warnText | No | -- | <code>{warnText}</code> |
### Events ### Events
| Event name | Type | Detail | | Event name | Type | Detail |
| :--------- | :-------- | :----- | | :--------- | :-------- | :----- |
| click | forwarded | -- |
| mouseover | forwarded | -- |
| mouseenter | forwarded | -- |
| mouseleave | forwarded | -- |
| change | forwarded | -- | | change | forwarded | -- |
| input | forwarded | -- | | input | forwarded | -- |
| keydown | forwarded | -- | | keydown | forwarded | -- |
@ -4157,12 +4158,7 @@ None.
### Events ### Events
| Event name | Type | Detail | None.
| :--------- | :-------- | :----- |
| click | forwarded | -- |
| mouseover | forwarded | -- |
| mouseenter | forwarded | -- |
| mouseleave | forwarded | -- |
## `TextInput` ## `TextInput`

View file

@ -12562,6 +12562,18 @@
"constant": false, "constant": false,
"reactive": false "reactive": false
}, },
{
"name": "counterMode",
"kind": "let",
"description": "Specify the counter mode",
"type": "\"character\" | \"word\"",
"value": "\"character\"",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{ {
"name": "light", "name": "light",
"kind": "let", "kind": "let",
@ -12658,6 +12670,30 @@
"constant": false, "constant": false,
"reactive": false "reactive": false
}, },
{
"name": "warn",
"kind": "let",
"description": "Set to `true` to indicate an warning state",
"type": "boolean",
"value": "false",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "warnText",
"kind": "let",
"description": "Specify the warning state text",
"type": "string",
"value": "\"\"",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{ {
"name": "id", "name": "id",
"kind": "let", "kind": "let",
@ -12696,18 +12732,26 @@
], ],
"moduleExports": [], "moduleExports": [],
"slots": [ "slots": [
{
"name": "invalidText",
"default": false,
"fallback": "{invalidText}",
"slot_props": "{}"
},
{ {
"name": "labelText", "name": "labelText",
"default": false, "default": false,
"fallback": "{labelText}", "fallback": "{labelText}",
"slot_props": "{}" "slot_props": "{}"
},
{
"name": "warnText",
"default": false,
"fallback": "{warnText}",
"slot_props": "{}"
} }
], ],
"events": [ "events": [
{ "type": "forwarded", "name": "click", "element": "div" },
{ "type": "forwarded", "name": "mouseover", "element": "div" },
{ "type": "forwarded", "name": "mouseenter", "element": "div" },
{ "type": "forwarded", "name": "mouseleave", "element": "div" },
{ "type": "forwarded", "name": "change", "element": "textarea" }, { "type": "forwarded", "name": "change", "element": "textarea" },
{ "type": "forwarded", "name": "input", "element": "textarea" }, { "type": "forwarded", "name": "input", "element": "textarea" },
{ "type": "forwarded", "name": "keydown", "element": "textarea" }, { "type": "forwarded", "name": "keydown", "element": "textarea" },
@ -12738,14 +12782,8 @@
], ],
"moduleExports": [], "moduleExports": [],
"slots": [], "slots": [],
"events": [ "events": [],
{ "type": "forwarded", "name": "click", "element": "div" }, "typedefs": []
{ "type": "forwarded", "name": "mouseover", "element": "div" },
{ "type": "forwarded", "name": "mouseenter", "element": "div" },
{ "type": "forwarded", "name": "mouseleave", "element": "div" }
],
"typedefs": [],
"rest_props": { "type": "Element", "name": "div" }
}, },
{ {
"moduleName": "TextInput", "moduleName": "TextInput",

View file

@ -11,14 +11,20 @@ components: ["TextArea", "TextAreaSkeleton"]
<TextArea labelText="App description" placeholder="Enter a description..." /> <TextArea labelText="App description" placeholder="Enter a description..." />
## Maximum character count ## Counter (character)
Specify the max character count using the `maxCount` prop. A character counter will be displayed to the right of the label. Specify a number using the `maxCount` prop to show a character counter. The default mode is `"charcacter"`.
You can always use the native `maxlength` attribute if you'd prefer that a counter not be shown. The `maxlength` attribute is set to `maxCount` in this mode. You can override this by specifying `maxlength={undefined}`.
<TextArea labelText="App description" placeholder="Enter a description..." maxCount={100} /> <TextArea labelText="App description" placeholder="Enter a description..." maxCount={100} />
## Counter (word)
Specify `counterMode="word"` for the counter to count words instead of characters.
<TextArea labelText="App description" placeholder="Enter a description..." maxCount={100} counterMode="word" />
## With helper text ## With helper text
<TextArea labelText="App description" helperText="A rich description helps us better recommend related products and services" placeholder="Enter a description..." /> <TextArea labelText="App description" helperText="A rich description helps us better recommend related products and services" placeholder="Enter a description..." />
@ -31,14 +37,32 @@ You can always use the native `maxlength` attribute if you'd prefer that a count
<TextArea light labelText="App description" placeholder="Enter a description..." /> <TextArea light labelText="App description" placeholder="Enter a description..." />
## Read-only variant
<TextArea readonly labelText="App description" placeholder="Enter a description..." />
## Custom rows ## Custom rows
Specify `rows` to adjust the height of the textarea.
By default, `rows` is set to `4`.
<TextArea rows={10} labelText="App description" placeholder="Enter a description..." /> <TextArea rows={10} labelText="App description" placeholder="Enter a description..." />
## Custom cols
If `cols` is a number, the `textarea` will not be resizeable.
<TextArea cols={50} labelText="App description" placeholder="Enter a description..." />
## Invalid state ## Invalid state
<TextArea invalid invalidText="Only plain text characters are allowed" labelText="App description" placeholder="Enter a description..." /> <TextArea invalid invalidText="Only plain text characters are allowed" labelText="App description" placeholder="Enter a description..." />
## Warning state
<TextArea warn warnText="The app description requires additional approval." labelText="App description" placeholder="Enter a description..." />
## Disabled state ## Disabled state
<TextArea disabled labelText="App description" placeholder="Enter a description..." /> <TextArea disabled labelText="App description" placeholder="Enter a description..." />

View file

@ -1,4 +1,6 @@
<script> <script>
// @ts-check
/** Specify the textarea value */ /** Specify the textarea value */
export let value = ""; export let value = "";
@ -20,6 +22,12 @@
*/ */
export let maxCount = undefined; export let maxCount = undefined;
/**
* Specify the counter mode
* @type {"character" | "word"}
*/
export let counterMode = "character";
/** Set to `true` to enable the light variant */ /** Set to `true` to enable the light variant */
export let light = false; export let light = false;
@ -44,6 +52,12 @@
/** Specify the text for the invalid state */ /** Specify the text for the invalid state */
export let invalidText = ""; export let invalidText = "";
/** Set to `true` to indicate an warning state */
export let warn = false;
/** Specify the warning state text */
export let warnText = "";
/** Set an id for the textarea element */ /** Set an id for the textarea element */
export let id = "ccs-" + Math.random().toString(36); export let id = "ccs-" + Math.random().toString(36);
@ -57,22 +71,26 @@
export let ref = null; export let ref = null;
import WarningFilled from "../icons/WarningFilled.svelte"; import WarningFilled from "../icons/WarningFilled.svelte";
import WarningAltFilled from "../icons/WarningAltFilled.svelte";
/** @type {(value: string) => number}*/
function getTextCount(value) {
if (counterMode === "character") {
return value.length;
} else {
return value.match(/\w+/g)?.length || 0;
}
}
$: error = invalid && !readonly;
$: errorId = `error-${id}`; $: errorId = `error-${id}`;
$: helperId = `helper-${id}`;
$: warnId = `warn-${id}`;
</script> </script>
<!-- svelte-ignore a11y-mouse-events-have-key-events --> <div class:bx--form-item="{true}">
<!-- svelte-ignore a11y-click-events-have-key-events --> <div class:bx--text-area__label-wrapper="{true}">
<!-- svelte-ignore a11y-no-static-element-interactions --> {#if (labelText || $$slots.labelText) && !hideLabel}
<div
on:click
on:mouseover
on:mouseenter
on:mouseleave
class:bx--form-item="{true}"
>
{#if (labelText || $$slots.labelText) && !hideLabel}
<div class:bx--text-area__label-wrapper="{true}">
<label <label
for="{id}" for="{id}"
class:bx--label="{true}" class:bx--label="{true}"
@ -83,25 +101,37 @@
{labelText} {labelText}
</slot> </slot>
</label> </label>
{#if maxCount} {/if}
<div class:bx--label="{true}" class:bx--label--disabled="{disabled}"> {#if maxCount}
{value.length}/{maxCount} <div class:bx--label="{true}" class:bx--label--disabled="{disabled}">
</div> {getTextCount(value)}/{maxCount}
{/if} </div>
</div> {/if}
{/if} </div>
<div <div
class:bx--text-area__wrapper="{true}" class:bx--text-area__wrapper="{true}"
class:bx--text-area__wrapper--readonly="{readonly}"
class:bx--text-area__wrapper--warn="{warn}"
data-invalid="{invalid || undefined}" data-invalid="{invalid || undefined}"
> >
{#if invalid} {#if invalid}
<WarningFilled class="bx--text-area__invalid-icon" /> <WarningFilled class="bx--text-area__invalid-icon" />
{:else if warn}
<WarningAltFilled
class="bx--text-area__invalid-icon bx--text-area__invalid-icon--warning"
/>
{/if} {/if}
<textarea <textarea
bind:this="{ref}" bind:this="{ref}"
bind:value bind:value="{value}"
aria-invalid="{invalid || undefined}" aria-invalid="{invalid || undefined}"
aria-describedby="{invalid ? errorId : undefined}" aria-describedby="{error
? errorId
: warn
? warnId
: helperText
? helperId
: undefined}"
disabled="{disabled}" disabled="{disabled}"
id="{id}" id="{id}"
name="{name}" name="{name}"
@ -112,8 +142,12 @@
class:bx--text-area="{true}" class:bx--text-area="{true}"
class:bx--text-area--light="{light}" class:bx--text-area--light="{light}"
class:bx--text-area--invalid="{invalid}" class:bx--text-area--invalid="{invalid}"
maxlength="{maxCount ?? undefined}" class:bx--text-area--warn="{warn}"
style="{cols ? '' : 'width: 100%;'}" maxlength="{typeof maxCount === 'number' && counterMode === 'character'
? maxCount
: undefined}"
style:width="{cols ? undefined : "100%"}"
style:resize="{cols ? "none" : undefined}"
{...$$restProps} {...$$restProps}
on:change on:change
on:input on:input
@ -125,13 +159,25 @@
</div> </div>
{#if !invalid && helperText} {#if !invalid && helperText}
<div <div
id="{helperId}"
class:bx--form__helper-text="{true}" class:bx--form__helper-text="{true}"
class:bx--form__helper-text--disabled="{disabled}" class:bx--form__helper-text--disabled="{disabled}"
> >
{helperText} {helperText}
</div> </div>
{/if} {/if}
{#if invalid} {#if !readonly}
<div id="{errorId}" class:bx--form-requirement="{true}">{invalidText}</div> {#if invalid}
<div id="{errorId}" class:bx--form-requirement="{true}">
<slot name="invalidText">
{invalidText}
</slot>
</div>
{/if}
{#if !invalid && warn}
<div class:bx--form-requirement="{true}" id="{warnId}">
<slot name="warnText">{warnText}</slot>
</div>
{/if}
{/if} {/if}
</div> </div>

View file

@ -1,18 +1,11 @@
<script> <script>
// @ts-check
/** Set to `true` to visually hide the label text */ /** Set to `true` to visually hide the label text */
export let hideLabel = false; export let hideLabel = false;
</script> </script>
<!-- svelte-ignore a11y-mouse-events-have-key-events --> <div class:bx--form-item="{true}">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class:bx--form-item="{true}"
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
>
{#if !hideLabel} {#if !hideLabel}
<span class:bx--label="{true}" class:bx--skeleton="{true}"></span> <span class:bx--label="{true}" class:bx--skeleton="{true}"></span>
{/if} {/if}

View file

@ -34,6 +34,12 @@ export interface TextAreaProps extends RestProps {
*/ */
maxCount?: number; maxCount?: number;
/**
* Specify the counter mode
* @default "character"
*/
counterMode?: "character" | "word";
/** /**
* Set to `true` to enable the light variant * Set to `true` to enable the light variant
* @default false * @default false
@ -82,6 +88,18 @@ export interface TextAreaProps extends RestProps {
*/ */
invalidText?: string; invalidText?: string;
/**
* Set to `true` to indicate an warning state
* @default false
*/
warn?: boolean;
/**
* Specify the warning state text
* @default ""
*/
warnText?: string;
/** /**
* Set an id for the textarea element * Set an id for the textarea element
* @default "ccs-" + Math.random().toString(36) * @default "ccs-" + Math.random().toString(36)
@ -106,10 +124,6 @@ export interface TextAreaProps extends RestProps {
export default class TextArea extends SvelteComponentTyped< export default class TextArea extends SvelteComponentTyped<
TextAreaProps, TextAreaProps,
{ {
click: WindowEventMap["click"];
mouseover: WindowEventMap["mouseover"];
mouseenter: WindowEventMap["mouseenter"];
mouseleave: WindowEventMap["mouseleave"];
change: WindowEventMap["change"]; change: WindowEventMap["change"];
input: WindowEventMap["input"]; input: WindowEventMap["input"];
keydown: WindowEventMap["keydown"]; keydown: WindowEventMap["keydown"];
@ -118,5 +132,5 @@ export default class TextArea extends SvelteComponentTyped<
blur: WindowEventMap["blur"]; blur: WindowEventMap["blur"];
paste: DocumentAndElementEventHandlersEventMap["paste"]; paste: DocumentAndElementEventHandlersEventMap["paste"];
}, },
{ labelText: {} } { invalidText: {}; labelText: {}; warnText: {} }
> {} > {}

View file

@ -1,25 +1,15 @@
import type { SvelteComponentTyped } from "svelte"; import type { SvelteComponentTyped } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";
type RestProps = SvelteHTMLElements["div"]; export interface TextAreaSkeletonProps {
export interface TextAreaSkeletonProps extends RestProps {
/** /**
* Set to `true` to visually hide the label text * Set to `true` to visually hide the label text
* @default false * @default false
*/ */
hideLabel?: boolean; hideLabel?: boolean;
[key: `data-${string}`]: any;
} }
export default class TextAreaSkeleton extends SvelteComponentTyped< export default class TextAreaSkeleton extends SvelteComponentTyped<
TextAreaSkeletonProps, TextAreaSkeletonProps,
{ Record<string, any>,
click: WindowEventMap["click"];
mouseover: WindowEventMap["mouseover"];
mouseenter: WindowEventMap["mouseenter"];
mouseleave: WindowEventMap["mouseleave"];
},
{} {}
> {} > {}