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

toucan-form: Expose all named blocks #156

Merged
merged 13 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions .changeset/light-olives-notice.md
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@crowdstrike/ember-toucan-form': patch
---

Exposed named blocks from the underlying `toucan-core` components. This allows users to add custom content in `:hint` or `:label` named blocks. You can combine the arguments and named blocks as well! Below are some examples.

```hbs
<ToucanForm @data={{data}} as |form|>
<form.Textarea @name='comment'>
<:label>Label</:label>
<:hint>Hint</:hint>
</form.Textarea>
</ToucanForm>
```

```hbs
<ToucanForm @data={{data}} as |form|>
<form.Textarea @label='Label' @name='comment'>
<:hint>Hint</:hint>
</form.Textarea>
</ToucanForm>
```

```hbs
<ToucanForm @data={{data}} as |form|>
<form.Textarea @hint='Hint' @name='comment'>
<:label>Label</:label>
</form.Textarea>
</ToucanForm>
```

Or you can continue to use the arguments if you're only working with strings!

```hbs
<ToucanForm @data={{data}} as |form|>
<form.Textarea @label='Label' @hint='Hint' @name='comment' />
</ToucanForm>
```
95 changes: 81 additions & 14 deletions packages/ember-toucan-form/src/-private/checkbox-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::Checkbox 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 checkbox only expects a boolean typed value, but field.setValue is generic,
accepting anything that DATA[KEY] could be. Similar case with "@isChecked", but there casting to
a boolean is easy.
}}
<@form.Field @name={{@name}} as |field|>
<Form::Fields::Checkbox
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@isChecked={{this.assertBoolean field.value}}
{{! The issue here is that onChange only expects a string typed value, but field.setValue is generic, accepting anything that DATA[KEY] could be. }}
{{! @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::Checkbox
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@isChecked={{this.assertBoolean field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
>
<:label>{{yield to='label'}}</:label>
</Form::Fields::Checkbox>
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
}}
<Form::Fields::Checkbox
@error={{this.mapErrors field.rawErrors}}
@isChecked={{this.assertBoolean 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::Checkbox>
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
<Form::Fields::Checkbox
@label={{@label}}
@error={{this.mapErrors field.rawErrors}}
@isChecked={{this.assertBoolean field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
name={{@name}}
...attributes
>
<:hint>{{yield to='hint'}}</:hint>
</Form::Fields::Checkbox>
{{else}}
{{! Argument-only case }}
<Form::Fields::Checkbox
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@isChecked={{this.assertBoolean 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/checkbox-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ export interface ToucanFormCheckboxFieldComponentSignature<
*/
form: HeadlessFormBlock<DATA>;
};
Blocks: {
default: [];
};
Blocks: BaseCheckboxFieldSignature['Blocks'];
}

export default class ToucanFormTextareaFieldComponent<
DATA extends UserData,
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
> extends Component<ToucanFormCheckboxFieldComponentSignature<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
110 changes: 93 additions & 17 deletions packages/ember-toucan-form/src/-private/checkbox-group-field.hbs
Original file line number Diff line number Diff line change
@@ -1,19 +1,95 @@
{{!
Regarding Conditionals

This looks really messy, but Form::Fields::CheckboxGroup 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 checkbox-group only expects an array of strings typed value, but field.setValue is generic,
accepting anything that DATA[KEY] could be. Similar case with "@isChecked", but there casting to
an array of strings is easy.
}}
<@form.Field @name={{@name}} as |field|>
<Form::Fields::CheckboxGroup
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertArrayOfStrings field.value}}
{{! The issue here is that onChange 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 is easy. }}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
@name={{@name}}
...attributes
as |group|
>
{{yield (hash CheckboxField=group.CheckboxField)}}
</Form::Fields::CheckboxGroup>
{{#if (this.hasOnlyLabelBlock (has-block 'label') (has-block 'hint'))}}
<Form::Fields::CheckboxGroup
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertArrayOfStrings field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
@name={{@name}}
...attributes
>
<:label>{{yield to='label'}}</:label>
<:default as |group|>
{{yield (hash CheckboxField=group.CheckboxField) to='default'}}
</:default>
</Form::Fields::CheckboxGroup>
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
}}
<Form::Fields::CheckboxGroup
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertArrayOfStrings 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>
<:default as |group|>
{{yield (hash CheckboxField=group.CheckboxField)}}
</:default>
</Form::Fields::CheckboxGroup>
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
<Form::Fields::CheckboxGroup
@label={{@label}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertArrayOfStrings field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
@name={{@name}}
...attributes
>
<:hint>{{yield to='hint'}}</:hint>
<:default as |group|>
{{yield (hash CheckboxField=group.CheckboxField)}}
</:default>
</Form::Fields::CheckboxGroup>
{{else}}
{{! Argument-only case }}
<Form::Fields::CheckboxGroup
@label={{@label}}
@hint={{@hint}}
@error={{this.mapErrors field.rawErrors}}
@value={{this.assertArrayOfStrings field.value}}
{{! @glint-expect-error }}
@onChange={{field.setValue}}
@isDisabled={{@isDisabled}}
@isReadOnly={{@isReadOnly}}
@rootTestSelector={{@rootTestSelector}}
@name={{@name}}
...attributes
as |group|
>
{{yield (hash CheckboxField=group.CheckboxField)}}
</Form::Fields::CheckboxGroup>
{{/if}}
</@form.Field>
15 changes: 8 additions & 7 deletions packages/ember-toucan-form/src/-private/checkbox-group-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ export interface ToucanFormCheckboxGroupFieldComponentSignature<
*/
form: HeadlessFormBlock<DATA>;
};
Blocks: {
default: [
{
CheckboxField: BaseCheckboxGroupFieldSignature['Blocks']['default'][0]['CheckboxField'];
}
];
};
Blocks: BaseCheckboxGroupFieldSignature['Blocks'];
}

export default class ToucanFormTextareaFieldComponent<
DATA extends UserData,
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
> extends Component<ToucanFormCheckboxGroupFieldComponentSignature<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
Loading