Skip to content

Commit

Permalink
feat(radio): support required attribute (#212)
Browse files Browse the repository at this point in the history
* feat(radio): support required attribute

Co-authored-by: Galina Edinakova <[email protected]>
Co-authored-by: Diyan Dimitrov <[email protected]>
  • Loading branch information
3 people authored Jan 27, 2022
1 parent b4edaba commit c4e68a2
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Dark Themes
- Slider component
- Range Slider component
- Support `required` property in Radio component.

### Changed

Expand Down
15 changes: 14 additions & 1 deletion src/components/radio-group/radio-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,27 @@ describe('Radio Group Component', () => {
expect(radio3).to.be.calledWith('igcFocus');
expect(radio3).to.be.calledWith('igcChange');
});

it('should set required attribute correctly', async () => {
expect(Array.from(radios).every((r) => r.required)).to.be.false;
expect(radios[0].required).to.be.true;

radios[1].checked = true;
await elementUpdated(group);

expect(radios[0].required).to.be.false;
expect(radios[1].required).to.be.true;
});
});

const values = ['orange', 'apple', 'mango'];
const createRadioGroupComponent = (
template = html`<igc-radio-group>
${values.map(
(value) =>
html`<igc-radio name="fruit" value=${value}>${value}</igc-radio>`
html`<igc-radio name="fruit" required value=${value}
>${value}</igc-radio
>`
)}
</igc-radio-group>`
) => {
Expand Down
30 changes: 23 additions & 7 deletions src/components/radio-group/radio-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ export default class IgcRadioGroupComponent extends LitElement {

public static override styles = styles;

@queryAssignedElements({ flatten: true, selector: 'igc-radio' })
private _slottedRadios!: Array<IgcRadioComponent>;

private get radios() {
return this._slottedRadios.filter((radio) => !radio.disabled);
}
@queryAssignedElements({
flatten: true,
selector: 'igc-radio:not([disabled])',
})
private radios!: Array<IgcRadioComponent>;

private get isLTR(): boolean {
const styles = window.getComputedStyle(this);
Expand All @@ -23,11 +22,28 @@ export default class IgcRadioGroupComponent extends LitElement {
constructor() {
super();
this.addEventListener('keydown', this.handleKeydown);
this.addEventListener('igcChange', this.updateRequiredState);
}

@property({ reflect: true })
public alignment: 'vertical' | 'horizontal' = 'vertical';

private updateRequiredState() {
const hasRequired = this.radios.some((r) => r.required);

if (hasRequired) {
this.radios.forEach((r) => (r.required = false));

const hasChecked = this.radios.some((r) => r.checked);

if (hasChecked) {
this.radios.filter((r) => r.checked)[0].required = true;
} else {
this.radios[0].required = true;
}
}
}

private handleKeydown = (event: KeyboardEvent) => {
const { key } = event;

Expand Down Expand Up @@ -61,7 +77,7 @@ export default class IgcRadioGroupComponent extends LitElement {
};

protected override render() {
return html`<slot></slot>`;
return html`<slot @slotchange=${this.updateRequiredState}></slot>`;
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/components/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ describe('Radio Component', () => {
expect(radio.invalid).to.equal(false);
expect(radio.disabled).to.equal(false);
expect(input.disabled).to.equal(false);
expect(radio.required).to.equal(false);
expect(input.required).to.equal(false);
expect(radio.labelPosition).to.equal('after');
});

Expand Down Expand Up @@ -158,6 +160,19 @@ describe('Radio Component', () => {
);
});

it('sets the required property successfully', async () => {
radio.required = true;
expect(radio.required).to.be.true;
await elementUpdated(radio);
expect(input).dom.to.equal(`<input required />`, DIFF_OPTIONS);

radio.required = false;
expect(radio.required).to.be.false;
await elementUpdated(radio);

expect(input).dom.to.equal(`<input />`, DIFF_OPTIONS);
});

it('should emit focus/blur events when methods are called', () => {
const eventSpy = sinon.spy(radio, 'emitEvent');
radio.focus();
Expand Down
20 changes: 17 additions & 3 deletions src/components/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export default class IgcRadioComponent extends EventEmitterMixin<
@property()
public value!: string;

/** Makes the control a required field. */
@property({ type: Boolean, reflect: true })
public required = false;

/** The checked state of the control. */
@property({ type: Boolean })
@blazorTwoWayBind('igcChange', 'detail')
Expand Down Expand Up @@ -133,16 +137,25 @@ export default class IgcRadioComponent extends EventEmitterMixin<
this.input.focus();
this._tabIndex = 0;
this.emitEvent('igcChange', { detail: this.checked });
} else {
if (this.required) {
this.required = false;
this.getAllInGroup()[0].required = true;
}
}
}

protected getSiblings() {
return this.getAllInGroup().filter(
(radio) => radio.name === this.name && radio !== this
);
}

protected getAllInGroup() {
const group = this.closest('igc-radio-group');
if (!group) return [];

return Array.from<IgcRadioComponent>(
group.querySelectorAll('igc-radio')
).filter((radio) => radio.name === this.name && radio !== this);
return Array.from<IgcRadioComponent>(group.querySelectorAll('igc-radio'));
}

protected override render() {
Expand All @@ -157,6 +170,7 @@ export default class IgcRadioComponent extends EventEmitterMixin<
type="radio"
name="${ifDefined(this.name)}"
value="${ifDefined(this.value)}"
.required="${this.required}"
.disabled="${this.disabled}"
.checked="${live(this.checked)}"
tabindex=${this._tabIndex}
Expand Down
5 changes: 4 additions & 1 deletion stories/form.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ The cat was playing<br> in the garden.</textarea
<label>Gender:</label>
<igc-radio-group id="gender">
${radios.map(
(v) => html`<igc-radio name="gender" value=${v}>${v}</igc-radio> `
(v) =>
html`<igc-radio name="gender" required value=${v}
>${v}</igc-radio
>`
)}
</igc-radio-group>
</div>
Expand Down
10 changes: 9 additions & 1 deletion stories/radio.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const metadata = {
description: 'The value attribute of the control.',
control: 'text',
},
required: {
type: 'boolean',
description: 'Makes the control a required field.',
control: 'boolean',
defaultValue: false,
},
checked: {
type: 'boolean',
description: 'The checked state of the control.',
Expand Down Expand Up @@ -55,6 +61,7 @@ export default metadata;
interface ArgTypes {
name: string;
value: string;
required: boolean;
checked: boolean;
disabled: boolean;
invalid: boolean;
Expand All @@ -64,13 +71,14 @@ interface ArgTypes {
// endregion

const Template: Story<ArgTypes, Context> = (
{ labelPosition, checked, disabled }: ArgTypes,
{ labelPosition, checked, disabled, required }: ArgTypes,
{ globals: { direction } }: Context
) => html`
<igc-radio
label-position="${ifDefined(labelPosition)}"
.disabled="${disabled}"
.checked="${checked}"
.required=${required}
dir=${ifDefined(direction)}
>
Label
Expand Down

0 comments on commit c4e68a2

Please sign in to comment.