Skip to content

Commit 3450fbb

Browse files
feat(pie-radio): DSW-2307 add basic component functionality (#1859)
* feat(pie-radio): DSW-2307 add basic component functionality * Writing some tests * Changes from self-review * Changes from peer review * Make value prop required * Update tests and storybook with value prop being required * Changes from peer review
1 parent 6d08793 commit 3450fbb

File tree

8 files changed

+411
-33
lines changed

8 files changed

+411
-33
lines changed

.changeset/famous-oranges-explain.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@justeattakeaway/pie-radio": minor
3+
"pie-storybook": patch
4+
---
5+
6+
[Added] - checked, defaultChecked, disabled, name, required and value props
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,80 @@
11
import { html } from 'lit';
2+
import { ifDefined } from 'lit/directives/if-defined.js';
23
import { type Meta } from '@storybook/web-components';
34

45
import '@justeattakeaway/pie-radio';
5-
import { type RadioProps } from '@justeattakeaway/pie-radio';
6+
import { type RadioProps as RadioBaseProps, defaultProps } from '@justeattakeaway/pie-radio';
67

7-
import { createStory } from '../utilities';
8+
import { type SlottedComponentProps } from '../types';
89

10+
import { createStory, sanitizeAndRenderHTML } from '../utilities';
11+
12+
type RadioProps = SlottedComponentProps<RadioBaseProps>;
913
type RadioStoryMeta = Meta<RadioProps>;
1014

11-
const defaultArgs: RadioProps = {};
15+
const defaultArgs: RadioProps = {
16+
...defaultProps,
17+
slot: 'Label',
18+
value: 'value',
19+
};
1220

1321
const radioStoryMeta: RadioStoryMeta = {
1422
title: 'Radio',
1523
component: 'pie-radio',
16-
argTypes: {},
24+
argTypes: {
25+
checked: {
26+
description: 'The checked state of the radio.',
27+
control: 'boolean',
28+
defaultValue: {
29+
summary: defaultArgs.checked,
30+
},
31+
},
32+
33+
defaultChecked: {
34+
description: 'The default checked state of the radio (not necessarily the same as the current checked state). Used when the radio is part of a form that is reset.',
35+
control: 'boolean',
36+
defaultValue: {
37+
summary: defaultArgs.defaultChecked,
38+
},
39+
},
40+
41+
disabled: {
42+
description: 'Same as the HTML disabled attribute - indicates whether or not the radio is disabled.',
43+
control: 'boolean',
44+
defaultValue: {
45+
summary: defaultArgs.disabled,
46+
},
47+
},
48+
49+
name: {
50+
description: 'The name of the radio (used as a key/value pair with `value`). This is required in order to work properly with forms.',
51+
control: 'text',
52+
defaultValue: {
53+
summary: defaultArgs.name,
54+
},
55+
},
56+
57+
required: {
58+
description: 'Same as native required attribute. If any radio button in a same-named group of radio buttons has the required attribute, a radio button in that group must be checked, although it doesn\'t have to be the one with the attribute applied.',
59+
control: 'boolean',
60+
defaultValue: {
61+
summary: defaultArgs.required,
62+
},
63+
},
64+
65+
slot: {
66+
description: 'Content to set as the radio label.',
67+
control: 'text',
68+
},
69+
70+
value: {
71+
description: 'The value of the radio (used as a key/value pair in HTML forms with `name`).',
72+
control: 'text',
73+
defaultValue: {
74+
summary: defaultArgs.value,
75+
},
76+
},
77+
},
1778
args: defaultArgs,
1879
parameters: {
1980
design: {
@@ -25,10 +86,18 @@ const radioStoryMeta: RadioStoryMeta = {
2586

2687
export default radioStoryMeta;
2788

28-
// TODO: remove the eslint-disable rule when props are added
29-
// eslint-disable-next-line no-empty-pattern
30-
const Template = ({}: RadioProps) => html`
31-
<pie-radio></pie-radio>
89+
const Template = ({
90+
checked, disabled, defaultChecked, name, required, slot, value,
91+
}: RadioProps) => html`
92+
<pie-radio
93+
?checked="${checked}"
94+
?disabled="${disabled}"
95+
?defaultChecked="${defaultChecked}"
96+
?required="${required}"
97+
name="${ifDefined(name)}"
98+
.value="${value}">
99+
${sanitizeAndRenderHTML(slot)}
100+
</pie-radio>
32101
`;
33102

34103
export const Default = createStory<RadioProps>(Template, defaultArgs)();
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import React from 'react';
2-
/**
3-
* TODO: Verify if ReactBaseType can be set as a more specific React interface
4-
* Use the React IntrinsicElements interface to find how to map standard HTML elements to existing React Interfaces
5-
* Example: an HTML button maps to `React.ButtonHTMLAttributes<HTMLButtonElement>`
6-
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0bb210867d16170c4a08d9ce5d132817651a0f80/types/react/index.d.ts#L2829
7-
*/
8-
export type ReactBaseType = React.HTMLAttributes<HTMLElement>
1+
import type React from 'react';
2+
3+
export type ReactBaseType = React.InputHTMLAttributes<HTMLInputElement>;
+44-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1-
// TODO - please remove the eslint disable comment below when you add props to this interface
2-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
3-
export interface RadioProps {}
1+
import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2+
3+
export interface RadioProps {
4+
/**
5+
* The checked state of the radio.
6+
*/
7+
checked?: boolean;
8+
9+
/**
10+
* The default checked state of the radio (not necessarily the same as the current checked state).
11+
* Used when the radio is part of a form that is reset.
12+
*/
13+
defaultChecked?: boolean;
14+
15+
/**
16+
* Same as the HTML disabled attribute - indicates whether or not the radio is disabled.
17+
*/
18+
disabled?: boolean;
19+
20+
/**
21+
* The name of the radio (used as a key/value pair with `value`). This is required in order to work properly with forms.
22+
*/
23+
name?: string;
24+
25+
/**
26+
* Same as native required attribute. If any radio button in a same-named group of radio buttons has the required attribute,
27+
* a radio button in that group must be checked, although it doesn't have to be the one with the attribute applied.
28+
*/
29+
required?: boolean;
30+
31+
/**
32+
* The value of the radio (used as a key/value pair in HTML forms with `name`).
33+
*/
34+
value: string;
35+
}
36+
37+
export type DefaultProps = ComponentDefaultProps<RadioProps, keyof Omit<RadioProps, 'name' | 'value'>>;
38+
39+
export const defaultProps: DefaultProps = {
40+
checked: false,
41+
defaultChecked: false,
42+
disabled: false,
43+
required: false,
44+
};

packages/components/pie-radio/src/index.ts

+55-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { LitElement, html, unsafeCSS } from 'lit';
2-
import { RtlMixin, defineCustomElement } from '@justeattakeaway/pie-webc-core';
2+
import { property, query } from 'lit/decorators.js';
3+
import { ifDefined } from 'lit/directives/if-defined.js';
4+
import { live } from 'lit/directives/live.js';
5+
import {
6+
defineCustomElement, FormControlMixin, requiredProperty, RtlMixin,
7+
} from '@justeattakeaway/pie-webc-core';
38

49
import styles from './radio.scss?inline';
5-
import { RadioProps } from './defs';
10+
import { type RadioProps, defaultProps } from './defs';
611

712
// Valid values available to consumers
813
export * from './defs';
@@ -12,9 +17,55 @@ const componentSelector = 'pie-radio';
1217
/**
1318
* @tagname pie-radio
1419
*/
15-
export class PieRadio extends RtlMixin(LitElement) implements RadioProps {
20+
export class PieRadio extends FormControlMixin(RtlMixin(LitElement)) implements RadioProps {
21+
@property({ type: Boolean })
22+
public checked = defaultProps.checked;
23+
24+
@property({ type: Boolean })
25+
public defaultChecked = defaultProps.defaultChecked;
26+
27+
@property({ type: Boolean })
28+
public disabled = defaultProps.disabled;
29+
30+
@property({ type: String })
31+
public name: RadioProps['name'];
32+
33+
@property({ type: Boolean })
34+
public required = defaultProps.required;
35+
36+
@property({ type: String })
37+
@requiredProperty(componentSelector)
38+
public value!: RadioProps['value'];
39+
40+
@query('input[type="radio"]')
41+
private radio!: HTMLInputElement;
42+
43+
/**
44+
* (Read-only) returns a ValidityState with the validity states that this element is in.
45+
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/validity
46+
*/
47+
public get validity () : ValidityState {
48+
return this.radio.validity;
49+
}
50+
1651
render () {
17-
return html`<h1 data-test-id="pie-radio">Hello world!</h1>`;
52+
const {
53+
checked, disabled, name, required, value,
54+
} = this;
55+
56+
return html`
57+
<input
58+
type="radio"
59+
id="radioId"
60+
data-test-id="pie-radio"
61+
.checked="${live(checked)}"
62+
.value="${value}"
63+
name="${ifDefined(name)}"
64+
?disabled="${disabled}"
65+
?required="${required}">
66+
<label for="radioId">
67+
<slot></slot>
68+
</label>`;
1869
}
1970

2071
// Renders a `CSSResult` generated from SCSS by Vite

packages/components/pie-radio/test/accessibility/pie-radio.spec.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11

22
import { test, expect } from '@justeattakeaway/pie-webc-testing/src/playwright/webc-fixtures.ts';
3-
import { PieRadio, RadioProps } from '../../src/index.ts';
3+
import { PieRadio, type RadioProps } from '../../src/index.ts';
44

55
test.describe('PieRadio - Accessibility tests', () => {
66
test('a11y - should test the PieRadio component WCAG compliance', async ({ makeAxeBuilder, mount }) => {
7-
await mount(
8-
PieRadio,
9-
{
10-
props: {} as RadioProps,
7+
await mount(PieRadio, {
8+
props: {
9+
name: 'option-1',
10+
} as RadioProps,
11+
slots: {
12+
default: 'Label',
1113
},
12-
);
14+
});
1315

1416
const results = await makeAxeBuilder().analyze();
1517

0 commit comments

Comments
 (0)