Skip to content

Commit cc868fb

Browse files
authored
Merge pull request #156 from CrowdStrike/feature-toucan-form-named-blocks
toucan-form: Expose all named blocks
2 parents fdd9626 + 20d433f commit cc868fb

16 files changed

+995
-99
lines changed

.changeset/light-olives-notice.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
'@crowdstrike/ember-toucan-form': patch
3+
---
4+
5+
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.
6+
7+
```hbs
8+
<ToucanForm @data={{data}} as |form|>
9+
<form.Textarea @name='comment'>
10+
<:label>Label</:label>
11+
<:hint>Hint</:hint>
12+
</form.Textarea>
13+
</ToucanForm>
14+
```
15+
16+
```hbs
17+
<ToucanForm @data={{data}} as |form|>
18+
<form.Textarea @label='Label' @name='comment'>
19+
<:hint>Hint</:hint>
20+
</form.Textarea>
21+
</ToucanForm>
22+
```
23+
24+
```hbs
25+
<ToucanForm @data={{data}} as |form|>
26+
<form.Textarea @hint='Hint' @name='comment'>
27+
<:label>Label</:label>
28+
</form.Textarea>
29+
</ToucanForm>
30+
```
31+
32+
Or you can continue to use the arguments if you're only working with strings!
33+
34+
```hbs
35+
<ToucanForm @data={{data}} as |form|>
36+
<form.Textarea @label='Label' @hint='Hint' @name='comment' />
37+
</ToucanForm>
38+
```
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,83 @@
1+
{{!
2+
Regarding Conditionals
3+
4+
This looks really messy, but Form::Fields::Checkbox exposes named blocks; HOWEVER,
5+
we cannot conditionally render named blocks due to https://github.com/emberjs/rfcs/issues/735.
6+
7+
We *can* conditionally render components though, based on the blocks and argument combinations
8+
users provide us. This is very brittle, but until https://github.com/emberjs/rfcs/issues/735
9+
is resolved and a solution is found, this appears to be the only way to truly expose
10+
conditional named blocks.
11+
12+
---
13+
14+
Regarding glint-expect-error
15+
16+
"@onChange" of the checkbox only expects a boolean typed value, but field.setValue is generic,
17+
accepting anything that DATA[KEY] could be. Similar case with "@isChecked", but there casting to
18+
a boolean is easy.
19+
}}
120
<@form.Field @name={{@name}} as |field|>
2-
<Form::Fields::Checkbox
3-
@label={{@label}}
4-
@hint={{@hint}}
5-
@error={{this.mapErrors field.rawErrors}}
6-
@isChecked={{this.assertBoolean field.value}}
7-
{{! The issue here is that onChange only expects a string typed value, but field.setValue is generic, accepting anything that DATA[KEY] could be. }}
8-
{{! @glint-expect-error }}
9-
@onChange={{field.setValue}}
10-
@isDisabled={{@isDisabled}}
11-
@isReadOnly={{@isReadOnly}}
12-
@rootTestSelector={{@rootTestSelector}}
13-
name={{@name}}
14-
...attributes
15-
/>
21+
{{#if (this.hasOnlyLabelBlock (has-block 'label') (has-block 'hint'))}}
22+
<Form::Fields::Checkbox
23+
@hint={{@hint}}
24+
@error={{this.mapErrors field.rawErrors}}
25+
@isChecked={{this.assertBoolean field.value}}
26+
{{! @glint-expect-error }}
27+
@onChange={{field.setValue}}
28+
@isDisabled={{@isDisabled}}
29+
@isReadOnly={{@isReadOnly}}
30+
@rootTestSelector={{@rootTestSelector}}
31+
name={{@name}}
32+
...attributes
33+
>
34+
<:label>{{yield to='label'}}</:label>
35+
</Form::Fields::Checkbox>
36+
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
37+
}}
38+
<Form::Fields::Checkbox
39+
@error={{this.mapErrors field.rawErrors}}
40+
@isChecked={{this.assertBoolean field.value}}
41+
{{! @glint-expect-error }}
42+
@onChange={{field.setValue}}
43+
@isDisabled={{@isDisabled}}
44+
@isReadOnly={{@isReadOnly}}
45+
@rootTestSelector={{@rootTestSelector}}
46+
name={{@name}}
47+
...attributes
48+
>
49+
<:label>{{yield to='label'}}</:label>
50+
<:hint>{{yield to='hint'}}</:hint>
51+
</Form::Fields::Checkbox>
52+
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
53+
<Form::Fields::Checkbox
54+
@label={{@label}}
55+
@error={{this.mapErrors field.rawErrors}}
56+
@isChecked={{this.assertBoolean field.value}}
57+
{{! @glint-expect-error }}
58+
@onChange={{field.setValue}}
59+
@isDisabled={{@isDisabled}}
60+
@isReadOnly={{@isReadOnly}}
61+
@rootTestSelector={{@rootTestSelector}}
62+
name={{@name}}
63+
...attributes
64+
>
65+
<:hint>{{yield to='hint'}}</:hint>
66+
</Form::Fields::Checkbox>
67+
{{else}}
68+
{{! Argument-only case }}
69+
<Form::Fields::Checkbox
70+
@label={{@label}}
71+
@hint={{@hint}}
72+
@error={{this.mapErrors field.rawErrors}}
73+
@isChecked={{this.assertBoolean field.value}}
74+
{{! @glint-expect-error }}
75+
@onChange={{field.setValue}}
76+
@isDisabled={{@isDisabled}}
77+
@isReadOnly={{@isReadOnly}}
78+
@rootTestSelector={{@rootTestSelector}}
79+
name={{@name}}
80+
...attributes
81+
/>
82+
{{/if}}
1683
</@form.Field>

packages/ember-toucan-form/src/-private/checkbox-field.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,20 @@ export interface ToucanFormCheckboxFieldComponentSignature<
2525
*/
2626
form: HeadlessFormBlock<DATA>;
2727
};
28-
Blocks: {
29-
default: [];
30-
};
28+
Blocks: BaseCheckboxFieldSignature['Blocks'];
3129
}
3230

3331
export default class ToucanFormTextareaFieldComponent<
3432
DATA extends UserData,
3533
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
3634
> extends Component<ToucanFormCheckboxFieldComponentSignature<DATA, KEY>> {
35+
hasOnlyLabelBlock = (hasLabel: boolean, hasHint: boolean) =>
36+
hasLabel && !hasHint;
37+
hasHintAndLabelBlocks = (hasLabel: boolean, hasHint: boolean) =>
38+
hasLabel && hasHint;
39+
hasLabelArgAndHintBlock = (hasLabel: string | undefined, hasHint: boolean) =>
40+
hasLabel && hasHint;
41+
3742
mapErrors = (errors?: ValidationError[]) => {
3843
if (!errors) {
3944
return;
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,95 @@
1+
{{!
2+
Regarding Conditionals
3+
4+
This looks really messy, but Form::Fields::CheckboxGroup exposes named blocks; HOWEVER,
5+
we cannot conditionally render named blocks due to https://github.com/emberjs/rfcs/issues/735.
6+
7+
We *can* conditionally render components though, based on the blocks and argument combinations
8+
users provide us. This is very brittle, but until https://github.com/emberjs/rfcs/issues/735
9+
is resolved and a solution is found, this appears to be the only way to truly expose
10+
conditional named blocks.
11+
12+
---
13+
14+
Regarding glint-expect-error
15+
16+
"@onChange" of the checkbox-group only expects an array of strings typed value, but field.setValue is generic,
17+
accepting anything that DATA[KEY] could be. Similar case with "@isChecked", but there casting to
18+
an array of strings is easy.
19+
}}
120
<@form.Field @name={{@name}} as |field|>
2-
<Form::Fields::CheckboxGroup
3-
@label={{@label}}
4-
@hint={{@hint}}
5-
@error={{this.mapErrors field.rawErrors}}
6-
@value={{this.assertArrayOfStrings field.value}}
7-
{{! 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. }}
8-
{{! @glint-expect-error }}
9-
@onChange={{field.setValue}}
10-
@isDisabled={{@isDisabled}}
11-
@isReadOnly={{@isReadOnly}}
12-
@rootTestSelector={{@rootTestSelector}}
13-
@name={{@name}}
14-
...attributes
15-
as |group|
16-
>
17-
{{yield (hash CheckboxField=group.CheckboxField)}}
18-
</Form::Fields::CheckboxGroup>
21+
{{#if (this.hasOnlyLabelBlock (has-block 'label') (has-block 'hint'))}}
22+
<Form::Fields::CheckboxGroup
23+
@hint={{@hint}}
24+
@error={{this.mapErrors field.rawErrors}}
25+
@value={{this.assertArrayOfStrings field.value}}
26+
{{! @glint-expect-error }}
27+
@onChange={{field.setValue}}
28+
@isDisabled={{@isDisabled}}
29+
@isReadOnly={{@isReadOnly}}
30+
@rootTestSelector={{@rootTestSelector}}
31+
@name={{@name}}
32+
...attributes
33+
>
34+
<:label>{{yield to='label'}}</:label>
35+
<:default as |group|>
36+
{{yield (hash CheckboxField=group.CheckboxField) to='default'}}
37+
</:default>
38+
</Form::Fields::CheckboxGroup>
39+
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
40+
}}
41+
<Form::Fields::CheckboxGroup
42+
@error={{this.mapErrors field.rawErrors}}
43+
@value={{this.assertArrayOfStrings field.value}}
44+
{{! @glint-expect-error }}
45+
@onChange={{field.setValue}}
46+
@isDisabled={{@isDisabled}}
47+
@isReadOnly={{@isReadOnly}}
48+
@rootTestSelector={{@rootTestSelector}}
49+
@name={{@name}}
50+
...attributes
51+
>
52+
<:label>{{yield to='label'}}</:label>
53+
<:hint>{{yield to='hint'}}</:hint>
54+
<:default as |group|>
55+
{{yield (hash CheckboxField=group.CheckboxField)}}
56+
</:default>
57+
</Form::Fields::CheckboxGroup>
58+
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
59+
<Form::Fields::CheckboxGroup
60+
@label={{@label}}
61+
@error={{this.mapErrors field.rawErrors}}
62+
@value={{this.assertArrayOfStrings field.value}}
63+
{{! @glint-expect-error }}
64+
@onChange={{field.setValue}}
65+
@isDisabled={{@isDisabled}}
66+
@isReadOnly={{@isReadOnly}}
67+
@rootTestSelector={{@rootTestSelector}}
68+
@name={{@name}}
69+
...attributes
70+
>
71+
<:hint>{{yield to='hint'}}</:hint>
72+
<:default as |group|>
73+
{{yield (hash CheckboxField=group.CheckboxField)}}
74+
</:default>
75+
</Form::Fields::CheckboxGroup>
76+
{{else}}
77+
{{! Argument-only case }}
78+
<Form::Fields::CheckboxGroup
79+
@label={{@label}}
80+
@hint={{@hint}}
81+
@error={{this.mapErrors field.rawErrors}}
82+
@value={{this.assertArrayOfStrings field.value}}
83+
{{! @glint-expect-error }}
84+
@onChange={{field.setValue}}
85+
@isDisabled={{@isDisabled}}
86+
@isReadOnly={{@isReadOnly}}
87+
@rootTestSelector={{@rootTestSelector}}
88+
@name={{@name}}
89+
...attributes
90+
as |group|
91+
>
92+
{{yield (hash CheckboxField=group.CheckboxField)}}
93+
</Form::Fields::CheckboxGroup>
94+
{{/if}}
1995
</@form.Field>

packages/ember-toucan-form/src/-private/checkbox-group-field.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@ export interface ToucanFormCheckboxGroupFieldComponentSignature<
2525
*/
2626
form: HeadlessFormBlock<DATA>;
2727
};
28-
Blocks: {
29-
default: [
30-
{
31-
CheckboxField: BaseCheckboxGroupFieldSignature['Blocks']['default'][0]['CheckboxField'];
32-
}
33-
];
34-
};
28+
Blocks: BaseCheckboxGroupFieldSignature['Blocks'];
3529
}
3630

3731
export default class ToucanFormTextareaFieldComponent<
3832
DATA extends UserData,
3933
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
4034
> extends Component<ToucanFormCheckboxGroupFieldComponentSignature<DATA, KEY>> {
35+
hasOnlyLabelBlock = (hasLabel: boolean, hasHint: boolean) =>
36+
hasLabel && !hasHint;
37+
hasHintAndLabelBlocks = (hasLabel: boolean, hasHint: boolean) =>
38+
hasLabel && hasHint;
39+
hasLabelArgAndHintBlock = (hasLabel: string | undefined, hasHint: boolean) =>
40+
hasLabel && hasHint;
41+
4142
mapErrors = (errors?: ValidationError[]) => {
4243
if (!errors) {
4344
return;

0 commit comments

Comments
 (0)