Skip to content

Commit

Permalink
Merge pull request #149 from CrowdStrike/toucan-form-expose-named-blocks
Browse files Browse the repository at this point in the history
toucan-form: Expose named blocks for textarea
  • Loading branch information
ynotdraw authored Apr 26, 2023
2 parents 8405d06 + 0ac0249 commit 728fbbf
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 17 deletions.
95 changes: 81 additions & 14 deletions packages/ember-toucan-form/src/-private/textarea-field.hbs
Original file line number Diff line number Diff line change
@@ -1,16 +1,83 @@
{{!
Regarding Conditionals
This looks really messy, but Form::Fields::Textarea exposes named blocks; HOWEVER,
we cannot conditionally render named blocks due to https://github.com/emberjs/rfcs/issues/735.
We *can* conditionally render components though, based on the blocks and argument combinations
users provide us. This is very brittle, but until https://github.com/emberjs/rfcs/issues/735
is resolved and a solution is found, this appears to be the only way to truly expose
conditional named blocks.
---
Regarding glint-expect-error
"@onChange" of the textarea only expects a string typed value, but field.setValue is generic,
accepting anything that DATA[KEY] could be. Similar case with "@value", but there casting to
a string is easy.
}}
<@form.Field @name={{@name}} as |field|>
<Form::Fields::Textarea
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertString field.value}}
{{! The issue here is that onChange of textarea only expects a string typed value, but field.setValue is generic, accepting anything that DATA[KEY] could be. Similar case with @value, but there casting to a string is easy. }}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
/>
{{#if (this.hasOnlyLabelBlock (has-block 'label') (has-block 'hint'))}}
<Form::Fields::Textarea
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertString field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
>
<:label>{{yield to='label'}}</:label>
</Form::Fields::Textarea>
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
}}
<Form::Fields::Textarea
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertString field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
>
<:label>{{yield to='label'}}</:label>
<:hint>{{yield to='hint'}}</:hint>
</Form::Fields::Textarea>
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
<Form::Fields::Textarea
@label={{@label}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertString field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
>
<:hint>{{yield to='hint'}}</:hint>
</Form::Fields::Textarea>
{{else}}
{{! Argument-only case }}
<Form::Fields::Textarea
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertString field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
/>
{{/if}}
</@form.Field>
11 changes: 8 additions & 3 deletions packages/ember-toucan-form/src/-private/textarea-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ export interface ToucanFormTextareaFieldComponentSignature<
*/
form: HeadlessFormBlock<DATA>;
};
Blocks: {
default: [];
};
Blocks: BaseTextareaFieldSignature['Blocks'];
}

export default class ToucanFormTextareaFieldComponent<
DATA extends UserData,
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
> extends Component<ToucanFormTextareaFieldComponentSignature<DATA, KEY>> {
hasOnlyLabelBlock = (hasLabel: boolean, hasHint: boolean) =>
hasLabel && !hasHint;
hasHintAndLabelBlocks = (hasLabel: boolean, hasHint: boolean) =>
hasLabel && hasHint;
hasLabelArgAndHintBlock = (hasLabel: string | undefined, hasHint: boolean) =>
hasLabel && hasHint;

mapErrors = (errors?: ValidationError[]) => {
if (!errors) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,77 @@ module('Integration | Component | ToucanForm | Textarea', function (hooks) {

assert.dom('[data-textarea]').hasAttribute('readonly');
});

test('it renders `@label` and `@hint` component arguments', async function (assert) {
const data: TestData = {
text: 'multi-line text',
};

await render(<template>
<ToucanForm @data={{data}} as |form|>
<form.Textarea @label="Label" @hint="Hint" @name="text" />
</ToucanForm>
</template>);

assert.dom('[data-label]').exists();
assert.dom('[data-hint]').exists();
});

test('it renders a `:label` named block with a `@hint` argument', async function (assert) {
const data: TestData = {
text: 'multi-line text',
};

await render(<template>
<ToucanForm @data={{data}} as |form|>
<form.Textarea @name="text" @hint="Hint">
<:label><span data-label-block>Label</span></:label>
</form.Textarea>
</ToucanForm>
</template>);

assert.dom('[data-label-block]').exists();

// NOTE: `data-hint` comes from `@hint`.
assert.dom('[data-hint]').exists();
assert.dom('[data-hint]').hasText('Hint');
});

test('it renders a `:hint` named block with a `@label` argument', async function (assert) {
const data: TestData = {
text: 'multi-line text',
};

await render(<template>
<ToucanForm @data={{data}} as |form|>
<form.Textarea @label="Label" @name="text">
<:hint><span data-hint-block>Hint</span></:hint>
</form.Textarea>
</ToucanForm>
</template>);

// NOTE: `data-label` comes from `@label`.
assert.dom('[data-label]').exists();
assert.dom('[data-label]').hasText('Label');

assert.dom('[data-hint-block]').exists();
});

test('it renders both a `:label` and `:hint` named block', async function (assert) {
const data: TestData = {
text: 'multi-line text',
};

await render(<template>
<ToucanForm @data={{data}} as |form|>
<form.Textarea @label="Label" @name="text">
<:label><span data-label-block>Label</span></:label>
<:hint><span data-hint-block>Hint</span></:hint>
</form.Textarea>
</ToucanForm>
</template>);

assert.dom('[data-label-block]').exists();
assert.dom('[data-hint-block]').exists();
});
});

0 comments on commit 728fbbf

Please sign in to comment.