-
Notifications
You must be signed in to change notification settings - Fork 6
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
#67: Add radio + radiogroupfield component #69
Conversation
|
Preview URLsGH Env: preview |
eq = (value, selectedValue) => { | ||
return value === selectedValue; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Local helper functions are a game changer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely! 💯
Will even get easier to use with template-imports, as you don't need to assign them to the backing class, you can define the function in the module, or just import it form elsewhere. 🎉
Historically in Ember we needed a name for this primitive, so we have "helper". But actually we now could just call them "a function". There is nothing special anymore (except for when you need a class-based helper though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-wise this looks good! But...
I have a bit of a problem or question wrt to what we define as the "field" here. At least the radio field does not map to what a radio field is in headless-form. Which does not mean the one is "right" and the other is "wrong", but at least we should see if/how we can align this maybe and fill the gap...
Take this example, where we have a form with three fields (in the sense that I have been using that term): name, email and gender. So "field" is basically an element of the form that the user can assign (enter or select) a value to. And it has a @name
that will map to the form data object. What is inherently special about a radio group field here is that a single selection (here "gender") is represented by multiple radio inputs, unlike all the other controls where this is a 1:1 mapping. You could represent the gender selection as well using a single <select>
control. But in this sense I would see the group of multiple radio buttons as the "field". Having a selection with a single radio button makes no sense after all.
This is different to what we have here, as here a "radio field" represents a single radio button (and related label and stuff).
So one thing is the naming here, which we probably can change easily. But also I wonder if we need something that represents that "radio group" as a whole (the PR title even mentions "radiogroupfield", while I don't see that here, right?). So something that matches what field.Radio
does in headless-form. Off the top of my head, that one could:
- yield a contextual
.RadioField
component (the one we already have here) - accept a single
@onChange
and curry that to the yielded.RadioField
- accept a
@value
which is not the value of the single radio button, but basically the user-selected value. This would match what e.g. anInputField
would have, as that also has@value
representing the entered value. We don't have an equivalent to that here yet AFAICT, right? - apply the
eq
logic ootb, as it has the selected@value
as well as the value of each individual radio button (yielded RadioField) - apply the same
name
to each radio button.
Regarding the last point of name
, this opens another can of worms. So first, assigning a name
to each form control is not only best-practice for semantic form markup (as that is how forms work without JS), it is also a requirement (or strong recommendation at least) for using this with headless-form (you probably remember those discussions around triggering validations and captureEvent
in case the control has no name
).
For radio groups that is especially important, as the common name basically defines what the group is, so you get the mutually exclusive behaviour of a radio group. With our JS-based solution, we can actually get around that, as we do those eq
checks in JS, but still I would consider it strongly recommended to have a name applied. I believe that could also actually fix the screen reader problem you were seeing, as when the radio buttons have no name they share, then there is no way for the user agent to determine what the group is they belong to!
So, with a plain input, we can just pass the name
as an attribute as in <InputField name="foo"/>
, but this wouldn't work for a radio group as in <RadioGroup name="foo">
, because we would need to spread that name to all individual radio inputs in that group, and you cannot do that with HTML attributes (there is only a singe ...attributes
). headless-form uses an @name
argument, which it can "curry" to all radio inputs. Maybe we have to introduce that argument here also? (and for consistency across all other fields)
eq = (value, selectedValue) => { | ||
return value === selectedValue; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely! 💯
Will even get easier to use with template-imports, as you don't need to assign them to the backing class, you can define the function in the module, or just import it form elsewhere. 🎉
Historically in Ember we needed a name for this primitive, so we have "helper". But actually we now could just call them "a function". There is nothing special anymore (except for when you need a class-based helper though)
@simonihmig Thanks a bunch for your thoughts! I definitely agree here. I was thinking at first that this could be the beginnings of the Based on above, your comments, and thinking out loud maybe we can do this:
So:
Some rough pseudocode maybe: {{!-- RadioGroup / RadioGroupField --}}
<Form::Fieldset
@label={{@label}}
@hint={{@hint}}
@error={{@error}}
>
{{yield (hash RadioField=(ensure-safe-component this.RadioField) name=@name onChange=this.handleChange isSelected={{this.checkRadioGroupValueVersusThisRadiosValue)}}
</Form::Fieldset> And then usage of above would be something like: <RadioGroup @label="Label" @name="options" @value="option-1" @onChange={{this.radioGroupChange}} as |group|>
<group.RadioField @label="Opt 1" @value="option-1" />
<group.RadioField @label="Opt 2" @value="option-2" />
<group.RadioField @label="Opt 3" @value="option-3" />
</RadioGroup> What do you think? Whatever we decide here, I'll also apply to the checkbox group. It's a little different but has similar issues! |
16882aa
to
8e4deb4
Compare
Looks perfect to me! Except for...
... naming things. 🙈 So I am tending more towards also giving this a ...Field name, given I see a field as mentioned already as an element of the form that a user can assign a value to, which also means it has a label and maybe more. Which still matches what we have here, right? Related to that: what about hints and errors, they will show up primarily for the So in that regard also, the |
So true, ugh. ChatGPT to solve naming when!? 😂
Yeah! So we do have a case where an individual RadioField will have a Label + Hint text today. It sits with the radio itself like this: But also at the So we kind of have two labels + two hints:
As for errors though, based on current designs, that should live only in |
"@glint/core": "^0.9.6", | ||
"@glint/environment-ember-loose": "^0.9.6", | ||
"@glint/environment-ember-template-imports": "^0.9.6", | ||
"@glint/template": "^0.9.6", | ||
"@glint/core": "^1.0.0-beta.3", | ||
"@glint/environment-ember-loose": "^1.0.0-beta.3", | ||
"@glint/environment-ember-template-imports": "^1.0.0-beta.3", | ||
"@glint/template": "^1.0.0-beta.3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out these were clobbering the builds. See https://github.com/CrowdStrike/ember-toucan-core/actions/runs/4375107381/jobs/7655418852 for the errors we get without updating these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you figure this out that it needed updating?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stumbled around for a bit longer than I care to admit ( 🙈 ), BUT!
test-app lint: [lint:types] ../node_modules/.pnpm/@glint+template@0.9.7_@glimmer+component@1.1.2/node_modules/@glint/template/-private/integration.d.ts:21:64
I noticed 0.9.7
in the error log from the link above. So I did a search of @glint/template
and noticed docs-app
was the only one not on beta-3. Updated it and it all worked!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to know!
Looks like the cloudflare action is failing because of cloudflare/pages-action#66 👀 Maybe I should update to |
I just did that an hour or so ago on ember-resources, can confirm it works. 🎉 |
Thanks @NullVoxPopuli ! 🙇 |
438fa19
to
80c4334
Compare
Okay this should be all good to review now! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful! 😍
One note to leave here though: it seems CrowdStrike/ember-headless-form#22 also applies here to some extend, right? Like at least the point of where to apply aria-invalid
is also not solved here AFAICT, right?
For aria-describedby
, this is now handled by the fieldset component here, so aria-describedby
is attached to the <fieldset>
in case of errors, correct?
@value='option-4' | ||
@hint='Extra information about the radio' | ||
/> | ||
</Form::RadioGroupField> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that API looks 🤩
/** | ||
* This component argument is used to determine if the underlying radio is checked. | ||
* When `selectedValue` and `value` are equal, the radio will have the checked attribute applied. | ||
*/ | ||
selectedValue?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name=@name | ||
onChange=this.handleInput | ||
selectedValue=@value | ||
isDisabled=@isDisabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also nice to have by having a group, to disable all radio buttons at once! 🚀
export interface ToucanFormRadioGroupFieldComponentSignature { | ||
Element: HTMLFieldSetElement; | ||
Args: { | ||
error?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't have to be in this PR, but just remembered @nicolechung mentioning this in one of her reviews of some headless PR: would be nice to have some JSDoc comments here explaining the args in a short sentence. As this is what people will see when having Glint enabled. And probably they will more likely look only at that, and not consult the actual docs, as we all know how docs get (not) read... 😅
Created: #73
import RadioGroupField from '@crowdstrike/ember-toucan-core/components/form/radio-group-field'; | ||
import { setupRenderingTest } from 'test-app/tests/helpers'; | ||
|
||
module('Integration | Component | RadioGroupField', function (hooks) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice test coverage here!
onChange=this.handleInput | ||
selectedValue=@value | ||
isDisabled=@isDisabled | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Shouldn't we also yield the selectedValue
in case the user wants to format that in some way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVM - read the thread above!
Yup yup, that's correct. Whatever @noahgelmancs finds there I'll apply here as well!
Yup that's correct! Will need to verify that is okay as well! |
🚀 Description
This PR introduces a
Radio
control component, aRadioField
component, and aRadioGroupField
component. It follows the same pattern asCheckbox
+CheckboxField
: #50Closes #67
🔬 How to Test
📸 Images/Videos of Functionality
a11y test
NOTE: It's saying 14 radio buttons as I am rendering all available UI states below so it's counting all of those 🙈
Screen.Recording.2023-03-07.at.2.30.46.PM.mov