Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: text input and textarea character limit counter #2745

Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
61a8349
docs(text-input): add character counter configs
emyarod May 13, 2019
05806a8
feat(text-input): add character count logic
emyarod May 13, 2019
7bf2957
fix(text-input): add character counter styles
emyarod May 14, 2019
6bc85bb
refactor(text-area): use prefix variable
emyarod May 14, 2019
72e98b6
refactor(text-area): remove unused classes
emyarod May 14, 2019
38a7d2d
fix(text-input): use correct selector option
emyarod May 14, 2019
b646363
refactor(text-input): avoid settimeout
emyarod May 14, 2019
036bd9f
feat(text-area): add character counter
emyarod May 14, 2019
141933f
refactor(text-input): use prefix
emyarod May 14, 2019
57e8d31
test(text-input): add character counter test
emyarod May 14, 2019
80e8f81
test(text-area): add spec file
emyarod May 14, 2019
f93da05
feat(TextInput): add character counter
emyarod May 14, 2019
0d20004
feat(TextArea): add character counter
emyarod May 14, 2019
50b03d5
test: update a11y-html.json
emyarod May 14, 2019
106cc70
docs: update proptypes
emyarod May 15, 2019
4086ab5
refactor: use render prop for char counter
emyarod May 15, 2019
4a95de4
test: add char counter tests
emyarod May 15, 2019
5ef06b7
chore: prettier
emyarod May 15, 2019
0e3ebbc
test: manually assign event object data for FF66
emyarod May 15, 2019
47375cb
fix: resolve character counter margin and overlap
emyarod May 17, 2019
516dc9a
docs(PasswordInput): remove char counter knob
emyarod May 17, 2019
8a717dc
fix: reduce helper text and char counter gap
emyarod May 17, 2019
e042a1f
fix(text-input): make input wrapper div full width
emyarod May 21, 2019
3aa014c
fix(text-area): add right margin to textarea charcounter
emyarod May 21, 2019
e592c22
Merge branch 'master' into 1672-text-input-character-limit-counter
shixiedesign May 21, 2019
6690dcd
Merge branch 'master' into 1672-text-input-character-limit-counter
asudoh May 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/components/src/components/text-area/_text-area.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -82,6 +86,7 @@
.#{$prefix}--text-area__wrapper {
position: relative;
display: flex;
width: 100%;
}

.#{$prefix}--text-area__invalid-icon {
Expand All @@ -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
//-----------------------------
Expand Down
17 changes: 17 additions & 0 deletions packages/components/src/components/text-area/text-area.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
],
};
92 changes: 66 additions & 26 deletions packages/components/src/components/text-area/text-area.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,87 @@
LICENSE file in the root directory of this source tree.
-->

<div class="{{@root.prefix}}--form-item">
<label for="text-area-2" class="{{@root.prefix}}--label">Text Area label</label>
<div class="{{@root.prefix}}--text-area__wrapper">
<textarea id="text-area-2"
class="{{@root.prefix}}--text-area {{@root.prefix}}--text-area--v2{{#if light}} {{@root.prefix}}--text-area--light{{/if}}"
rows="4" cols="50" placeholder="Placeholder text."></textarea>
<div data-text-area class="{{prefix}}--form-item{{#if charCounter}} {{prefix}}--text-area__container{{/if}}">
{{#if charCounter}}
<div class="{{prefix}}--text-area__character-counter-title">
<label for="text-area-2" class="{{prefix}}--label">Text Area label</label>
<span class="{{prefix}}--text-area--character-counter">
<span class="{{prefix}}--text-area--character-counter--length">0</span>/<span
class="{{prefix}}--text-area--character-counter--maxlength">{{maxLength}}</span>
</span>
</div>
{{else}}
<label for="text-area-2" class="{{prefix}}--label">Text Area label</label>
{{/if}}
<div class="{{prefix}}--text-area__wrapper">
<textarea id="text-area-2" class="{{prefix}}--text-area {{#if light}} {{prefix}}--text-area--light{{/if}}" rows="4"
cols="50" placeholder="Placeholder text." {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}></textarea>
</div>
</div>

<div class="{{@root.prefix}}--form-item">
<label for="text-area-3" class="{{@root.prefix}}--label">Text Area label</label>
<div class="{{@root.prefix}}--text-area__wrapper" data-invalid>
{{ carbon-icon 'WarningFilled16' class=(add @root.prefix '--text-area__invalid-icon')}}
<div data-text-area class="{{prefix}}--form-item{{#if charCounter}} {{prefix}}--text-area__container{{/if}}">
{{#if charCounter}}
<div class="{{prefix}}--text-area__character-counter-title">
<label for="text-area-3" class="{{prefix}}--label">Text Area label</label>
<span class="{{prefix}}--text-area--character-counter">
<span class="{{prefix}}--text-area--character-counter--length">0</span>/<span
class="{{prefix}}--text-area--character-counter--maxlength">{{maxLength}}</span>
</span>
</div>
{{else}}
<label for="text-area-3" class="{{prefix}}--label">Text Area label</label>
{{/if}}
<div class="{{prefix}}--text-area__wrapper" data-invalid>
{{ carbon-icon 'WarningFilled16' class=(add prefix '--text-area__invalid-icon')}}
<textarea id="text-area-3"
class="{{@root.prefix}}--text-area {{@root.prefix}}--text-area--invalid {{@root.prefix}}--text-area--v2{{#if light}} {{@root.prefix}}--text-area--light{{/if}}"
rows="4" cols="50" placeholder="Placeholder text."></textarea>
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}}></textarea>
</div>
<div class="{{@root.prefix}}--form-requirement">
<div class="{{prefix}}--form-requirement">
Validation message here
</div>
</div>

<div class="{{@root.prefix}}--form-item">
<label for="text-area-4" class="{{@root.prefix}}--label">Text Area label</label>
<div class="{{@root.prefix}}--form__helper-text">
<div data-text-area class="{{prefix}}--form-item{{#if charCounter}} {{prefix}}--text-area__container{{/if}}">
{{#if charCounter}}
<div class="{{prefix}}--text-area__character-counter-title">
<label for="text-area-4" class="{{prefix}}--label">Text Area label</label>
<span class="{{prefix}}--text-area--character-counter">
<span class="{{prefix}}--text-area--character-counter--length">0</span>/<span
class="{{prefix}}--text-area--character-counter--maxlength">{{maxLength}}</span>
</span>
</div>
{{else}}
<label for="text-area-4" class="{{prefix}}--label">Text Area label</label>
{{/if}}
<div class="{{prefix}}--form__helper-text">
Optional helper text goes here
</div>
<div class="{{@root.prefix}}--text-area__wrapper">
<textarea id="text-area-4"
class="{{@root.prefix}}--text-area {{@root.prefix}}--text-area--v2{{#if light}} {{@root.prefix}}--text-area--light{{/if}}"
rows="4" cols="50" placeholder="Placeholder text."></textarea>
<div class="{{prefix}}--text-area__wrapper">
<textarea id="text-area-4" class="{{prefix}}--text-area {{#if light}} {{prefix}}--text-area--light{{/if}}" rows="4"
cols="50" placeholder="Placeholder text." {{#if charCounter}} maxlength="{{maxLength}}" {{/if}}></textarea>
</div>
</div>

<div class="bx--form-item">
<label for="text-area-5" class="bx--label bx--label--disabled">Text Area label</label>
<div class="{{@root.prefix}}--form__helper-text {{@root.prefix}}--form__helper-text--disabled">
<div data-text-area class="{{prefix}}--form-item{{#if charCounter}} {{prefix}}--text-area__container{{/if}}">
{{#if charCounter}}
<div class="{{prefix}}--text-area__character-counter-title">
<label for="text-area-5" class="{{prefix}}--label {{prefix}}--label--disabled">Text Area label</label>
<span class="{{prefix}}--text-area--character-counter {{prefix}}--text-area--character-counter--disabled">
<span class="{{prefix}}--text-area--character-counter--length">0</span>/<span
class="{{prefix}}--text-area--character-counter--maxlength">{{maxLength}}</span>
</span>
</div>
{{else}}
<label for="text-area-5" class="{{prefix}}--label {{prefix}}--label--disabled">Text Area label</label>
{{/if}}
<div class="{{prefix}}--form__helper-text {{prefix}}--form__helper-text--disabled">
Optional helper text goes here
</div>
<div class="{{@root.prefix}}--text-area__wrapper">
<textarea id="text-area-5" class="bx--text-area bx--text-area--v2{{#if light}} bx--text-area--light{{/if}}" rows="4"
cols="50" placeholder="Placeholder text." disabled></textarea>
<div class="{{prefix}}--text-area__wrapper">
<textarea id="text-area-5" class="{{prefix}}--text-area {{#if light}} {{prefix}}--text-area--light{{/if}}" rows="4"
cols="50" placeholder="Placeholder text." disabled{{#if charCounter}} maxlength="{{maxLength}}"
{{/if}}></textarea>
</div>
</div>
74 changes: 74 additions & 0 deletions packages/components/src/components/text-area/text-area.js
Original file line number Diff line number Diff line change
@@ -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();
}
29 changes: 29 additions & 0 deletions packages/components/src/components/text-input/_text-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions packages/components/src/components/text-input/text-input.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
},
},
],
};
Loading