diff --git a/packages/components/src/components/text-area/_text-area.scss b/packages/components/src/components/text-area/_text-area.scss index 74945f5eef57..686d96ee7132 100644 --- a/packages/components/src/components/text-area/_text-area.scss +++ b/packages/components/src/components/text-area/_text-area.scss @@ -20,6 +20,10 @@ /// @access private /// @group text-area @mixin text-area { + .#{$prefix}--text-area__container { + position: relative; + } + .#{$prefix}--text-area { @include reset; @include type-style('body-long-01'); @@ -82,6 +86,7 @@ .#{$prefix}--text-area__wrapper { position: relative; display: flex; + width: 100%; } .#{$prefix}--text-area__invalid-icon { @@ -91,6 +96,34 @@ fill: $support-01; } + // ----------------- + // Character counter + // ----------------- + .#{$prefix}--text-area__character-counter-title { + display: flex; + justify-content: space-between; + width: 100%; + } + + .#{$prefix}--text-area__character-counter-title .#{$prefix}--label { + margin-right: rem(16px); + } + + .#{$prefix}--text-area__character-counter-title + + .#{$prefix}--form__helper-text { + margin-top: rem(-6px); + } + + .#{$prefix}--text-area--character-counter { + margin-bottom: $carbon--spacing-03; + @include type-style('label-01'); + color: $text-02; + } + + .#{$prefix}--text-area--character-counter--disabled { + color: $disabled-02; + } + //----------------------------- // Disabled //----------------------------- diff --git a/packages/components/src/components/text-area/text-area.config.js b/packages/components/src/components/text-area/text-area.config.js index edb7aebf1f41..c2d14a394b86 100644 --- a/packages/components/src/components/text-area/text-area.config.js +++ b/packages/components/src/components/text-area/text-area.config.js @@ -29,5 +29,22 @@ module.exports = { light: true, }, }, + { + name: 'character-counter', + label: 'Text area with character counter', + context: { + charCounter: true, + maxLength: 3500, + }, + }, + { + name: 'character-counter--light', + label: 'Text area with character counter (light)', + context: { + charCounter: true, + maxLength: 3500, + light: true, + }, + }, ], }; diff --git a/packages/components/src/components/text-area/text-area.hbs b/packages/components/src/components/text-area/text-area.hbs index 1758a67b14d9..cf1d5060bf5f 100644 --- a/packages/components/src/components/text-area/text-area.hbs +++ b/packages/components/src/components/text-area/text-area.hbs @@ -5,47 +5,87 @@ LICENSE file in the root directory of this source tree. --> -
- -
- +
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + + {{/if}} +
+
-
- -
- {{ carbon-icon 'WarningFilled16' class=(add @root.prefix '--text-area__invalid-icon')}} +
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + + {{/if}} +
+ {{ carbon-icon 'WarningFilled16' class=(add prefix '--text-area__invalid-icon')}} + class="{{prefix}}--text-area {{prefix}}--text-area--invalid {{#if light}} {{prefix}}--text-area--light{{/if}}" + rows="4" cols="50" placeholder="Placeholder text." {{#if charCounter}} maxlength="{{maxLength}}" + {{/if}}>
-
+
Validation message here
-
- -
+
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + + {{/if}} +
Optional helper text goes here
-
- +
+
-
- -
+
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + + {{/if}} +
Optional helper text goes here
-
- +
+
diff --git a/packages/components/src/components/text-area/text-area.js b/packages/components/src/components/text-area/text-area.js new file mode 100644 index 000000000000..9bbc135ee6b7 --- /dev/null +++ b/packages/components/src/components/text-area/text-area.js @@ -0,0 +1,74 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import settings from '../../globals/js/settings'; +import mixin from '../../globals/js/misc/mixin'; +import createComponent from '../../globals/js/mixins/create-component'; +import initComponentBySearch from '../../globals/js/mixins/init-component-by-search'; +import handles from '../../globals/js/mixins/handles'; +import on from '../../globals/js/misc/on'; + +export default class TextArea extends mixin( + createComponent, + initComponentBySearch, + handles +) { + /** + * Text Area. + * @extends CreateComponent + * @extends InitComponentBySearch + * @extends Handles + * @param {HTMLElement} element - The textarea element + */ + constructor(element, options) { + super(element, options); + this.manage( + on(this.element, 'input', event => { + if (event.target.maxLength < 0) { + return; + } + this._handleInput({ element, length: event.target.value.length }); + }) + ); + } + + /** + * Updates the character counter + * @param {Object} obj - The elements that can change in the component + * @param {HTMLElement} obj.element - The textarea element + * @param {HTMLElement} obj.length - The length of the textarea value + */ + _handleInput = ({ element, length }) => { + element.querySelector( + this.options.selectorCharCounter + ).textContent = length; + }; + + /** + * The component options. + * + * If `options` is specified in the constructor, + * {@linkcode TextArea.create .create()}, + * or {@linkcode TextArea.init .init()}, + * properties in this object are overriden for the instance being + * created and how {@linkcode TextArea.init .init()} works. + * @property {string} selectorInit The CSS selector to find textarea UIs. + */ + static get options() { + const { prefix } = settings; + return { + selectorInit: '[data-text-area]', + selectorCharCounter: `.${prefix}--text-area--character-counter--length`, + }; + } + + /** + * The map associating DOM element and textarea UI instance. + * @type {WeakMap} + */ + static components /* #__PURE_CLASS_PROPERTY__ */ = new WeakMap(); +} diff --git a/packages/components/src/components/text-input/_text-input.scss b/packages/components/src/components/text-input/_text-input.scss index 9cefb6e41184..fe726bc2c54e 100644 --- a/packages/components/src/components/text-input/_text-input.scss +++ b/packages/components/src/components/text-input/_text-input.scss @@ -59,12 +59,41 @@ background-color: $field-02; } + // ----------------- + // Character counter + // ----------------- + .#{$prefix}--text-input__character-counter-title { + display: flex; + justify-content: space-between; + width: 100%; + } + + .#{$prefix}--text-input__character-counter-title + + .#{$prefix}--form__helper-text { + margin-top: rem(-6px); + } + + .#{$prefix}--text-input__character-counter-title .#{$prefix}--label { + margin-right: rem(16px); + } + + .#{$prefix}--text-input--character-counter { + margin-bottom: $carbon--spacing-03; + @include type-style('label-01'); + color: $text-02; + } + + .#{$prefix}--text-input--character-counter--disabled { + color: $disabled-02; + } + //----------------------------- // Disabled & Error icon spacing //----------------------------- .#{$prefix}--text-input__field-wrapper { position: relative; display: flex; + width: 100%; align-items: center; .#{$prefix}--text-input__invalid-icon { diff --git a/packages/components/src/components/text-input/text-input.config.js b/packages/components/src/components/text-input/text-input.config.js index ffef1f08f482..1d0a318a9377 100644 --- a/packages/components/src/components/text-input/text-input.config.js +++ b/packages/components/src/components/text-input/text-input.config.js @@ -30,6 +30,23 @@ module.exports = { light: true, }, }, + { + name: 'character-counter', + label: 'Text Input with character counter', + context: { + charCounter: true, + maxLength: 100, + }, + }, + { + name: 'character-counter--light', + label: 'Text Input with character counter (light)', + context: { + charCounter: true, + maxLength: 100, + light: true, + }, + }, { name: 'password', label: 'Password Input', @@ -45,5 +62,24 @@ module.exports = { password: true, }, }, + { + name: 'password--character-counter', + label: 'Password Input with character counter', + context: { + charCounter: true, + maxLength: 100, + password: true, + }, + }, + { + name: 'password--light--character-counter', + label: 'Password Input with character counter (Light)', + context: { + charCounter: true, + maxLength: 100, + light: true, + password: true, + }, + }, ], }; diff --git a/packages/components/src/components/text-input/text-input.hbs b/packages/components/src/components/text-input/text-input.hbs index 9b4fe9b1dcba..c1793824435f 100644 --- a/packages/components/src/components/text-input/text-input.hbs +++ b/packages/components/src/components/text-input/text-input.hbs @@ -5,17 +5,29 @@ LICENSE file in the root directory of this source tree. --> -
+
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + {{/if}}
{{#unless password}} + class="{{prefix}}--text-input{{#if light}} {{prefix}}--text-input--light{{/if}}" placeholder="Placeholder text" + {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}> {{else}} + placeholder="Placeholder text" data-toggle-password-visibility{{#if charCounter}} maxlength="{{maxLength}}" + {{/if}}>
-
- +
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + + {{/if}}
{{ carbon-icon 'WarningFilled16' class=(add prefix '--text-input__invalid-icon')}} {{#unless password}} + placeholder="Placeholder text" {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}> {{else}} + placeholder="Placeholder text" {{#if charCounter}} maxlength="{{maxLength}}" {{/if}} + data-toggle-password-visibility>
-
+
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + {{/if}}
Optional helper text goes here
{{#unless password}} + class="{{prefix}}--text-input{{#if light}} {{prefix}}--text-input--light{{/if}}" placeholder="Placeholder text" + {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}> {{else}} + placeholder="Placeholder text" {{#if charCounter}} maxlength="{{maxLength}}" {{/if}} + data-toggle-password-visibility>
-
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + {{/if}}
Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)
{{#unless password}} + class="{{prefix}}--text-input{{#if light}} {{prefix}}--text-input--light{{/if}}" placeholder="Placeholder text" + {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}> {{else}} + placeholder="Placeholder text" {{#if charCounter}} maxlength="{{maxLength}}" {{/if}} + data-toggle-password-visibility>
-
+
+ {{#if charCounter}} +
+ + + 0/{{maxLength}} + +
+ {{else}} + {{/if}}
Optional helper text goes here
@@ -111,11 +168,12 @@ {{#unless password}} + {{#if charCounter}} maxlength="{{maxLength}}" {{/if}} disabled> {{else}} + placeholder="Placeholder text" {{#if charCounter}} maxlength="{{maxLength}}" {{/if}} + data-toggle-password-visibility disabled>