Skip to content

Commit 6a502f7

Browse files
feat: Add character count to TextArea Field component (#157)
* feat: Add character count to TextArea Field component * docs: add w-full to new character count UI states for textarea field Co-authored-by: Tony Ward <8069555+ynotdraw@users.noreply.github.com> * changeset: Added changeset for TextArea Field --------- Co-authored-by: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
1 parent 642fcf7 commit 6a502f7

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

.changeset/lazy-items-shake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@crowdstrike/ember-toucan-core': patch
3+
---
4+
5+
Added: Character Count for Textarea Field

docs/components/textarea-field/index.md

+44
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,50 @@ Target the error block via `data-error`.
242242
/>
243243
</div>
244244

245+
### TextareaField with character count
246+
247+
<div class='mb-4 w-64'>
248+
<Form::Fields::Textarea
249+
class="w-full"
250+
@label='Label'
251+
@hint='With hint text'
252+
>
253+
<:secondary as |secondary|>
254+
<secondary.CharacterCount @max={{255}} />
255+
</:secondary>
256+
</Form::Fields::Textarea>
257+
</div>
258+
259+
### TextareaField with character count with a single error
260+
261+
<div class='mb-4 w-64'>
262+
<Form::Fields::Textarea
263+
class="w-full"
264+
@label='Label'
265+
@hint='With hint text'
266+
@error="With error"
267+
>
268+
<:secondary as |secondary|>
269+
<secondary.CharacterCount @max={{255}} />
270+
</:secondary>
271+
</Form::Fields::Textarea>
272+
</div>
273+
274+
### TextareaField with character count with multiple errors
275+
276+
<div class='mb-4 w-64'>
277+
<Form::Fields::Textarea
278+
class="w-full"
279+
@label='Label'
280+
@hint='With hint text'
281+
@error={{(array 'With error 1' 'With error 2' 'With error 3')}}
282+
>
283+
<:secondary as |secondary|>
284+
<secondary.CharacterCount @max={{255}} />
285+
</:secondary>
286+
</Form::Fields::Textarea>
287+
</div>
288+
245289
### TextareaField with isReadOnly
246290

247291
<div class='mb-4 w-64'>

packages/ember-toucan-core/src/components/form/fields/textarea.hbs

+26-1
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,36 @@
4646
@value={{@value}}
4747
@onChange={{@onChange}}
4848
@hasError={{this.hasError}}
49+
{{on "input" this.handleCount}}
4950
...attributes
5051
/>
5152
</field.Control>
5253

53-
{{#if @error}}
54+
{{#if (has-block "secondary")}}
55+
<div class="flex justify-between">
56+
{{#if @error}}
57+
<field.Error
58+
class="flex-3"
59+
id={{field.errorId}}
60+
@error={{@error}}
61+
data-error
62+
/>
63+
{{/if}}
64+
65+
<div
66+
class="type-xs-tight text-body-and-labels mt-1.5 flex-1 text-right"
67+
>
68+
{{yield
69+
(hash
70+
CharacterCount=(component
71+
(ensure-safe-component this.CharacterCount) current=this.count
72+
)
73+
)
74+
to="secondary"
75+
}}
76+
</div>
77+
</div>
78+
{{else if @error}}
5479
<field.Error id={{field.errorId}} @error={{@error}} data-error />
5580
{{/if}}
5681
</Form::Field>

packages/ember-toucan-core/src/components/form/fields/textarea.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import Component from '@glimmer/component';
2+
import { tracked } from '@glimmer/tracking';
3+
import { assert } from '@ember/debug';
4+
import { action } from '@ember/object';
25

36
import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
7+
import CharacterCount from '../../../components/form/controls/character-count';
48

59
import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
610
import type { ErrorMessage } from '../../../-private/types';
711
import type { ToucanFormTextareaControlComponentSignature } from '../controls/textarea';
12+
import type { WithBoundArgs } from '@glint/template';
813

914
export interface ToucanFormTextareaFieldComponentSignature {
1015
Element: HTMLTextAreaElement;
@@ -52,10 +57,18 @@ export interface ToucanFormTextareaFieldComponentSignature {
5257
Blocks: {
5358
label: [];
5459
hint: [];
60+
secondary: [
61+
{
62+
CharacterCount: WithBoundArgs<typeof CharacterCount, 'current'>;
63+
}
64+
];
5565
};
5666
}
5767

5868
export default class ToucanFormTextareaFieldComponent extends Component<ToucanFormTextareaFieldComponentSignature> {
69+
@tracked count = this.args.value?.length ?? 0;
70+
71+
CharacterCount = CharacterCount;
5972
assertBlockOrArgumentExists = ({
6073
blockExists,
6174
argName,
@@ -71,6 +84,15 @@ export default class ToucanFormTextareaFieldComponent extends Component<ToucanFo
7184
super(owner, args);
7285
}
7386

87+
@action
88+
handleCount(event: Event | InputEvent): void {
89+
assert(
90+
'Expected HTMLTextAreaElement',
91+
event.target instanceof HTMLTextAreaElement
92+
);
93+
this.count = event.target?.value.length ?? 0;
94+
}
95+
7496
get hasError() {
7597
return Boolean(this.args?.error);
7698
}

test-app/tests/integration/components/textarea-field-test.gts

+17
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,21 @@ module('Integration | Component | Fields | Textarea', function (hooks) {
222222
</TextareaField>
223223
</template>);
224224
});
225+
226+
test('it renders a `<:secondary>` block that tracks the textarea value length', async function(assert) {
227+
await render(<template>
228+
<TextareaField @label="Label" @value="Hello" data-textarea>
229+
<:secondary as |secondary|>
230+
<secondary.CharacterCount @max={{255}} data-character />
231+
</:secondary>
232+
</TextareaField>
233+
</template>);
234+
235+
assert.dom('[data-character]').hasText('5 / 255');
236+
237+
await fillIn('[data-textarea]', 'Hello Hello');
238+
239+
assert.dom('[data-character]').hasText('11 / 255');
240+
241+
});
225242
});

0 commit comments

Comments
 (0)