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

feat: add RadioGroup middleware #918

Merged
merged 2 commits into from
Jan 6, 2020
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
1 change: 1 addition & 0 deletions .dojorc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"src/popup",
"src/progress",
"src/radio",
"src/radio-group",
"src/raised-button",
"src/range-slider",
"src/select",
Expand Down
30 changes: 30 additions & 0 deletions src/examples/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ import ProgressWithCustomOutput from './widgets/progress/ProgressWithCustomOutpu
import ProgressWithMax from './widgets/progress/ProgressWithMax';
import ProgressWithoutOutput from './widgets/progress/ProgressWithoutOutput';
import BasicRadio from './widgets/radio/Basic';
import BasicRadioGroup from './widgets/radio-group/Basic';
import CustomLabelRadioGroup from './widgets/radio-group/CustomLabel';
import CustomRendererRadioGroup from './widgets/radio-group/CustomRenderer';
import InitialValueRadioGroup from './widgets/radio-group/InitialValue';
import BasicRaisedButton from './widgets/raised-button/Basic';
import RaisedDisabledSubmit from './widgets/raised-button/DisabledSubmit';
import RaisedToggleButton from './widgets/raised-button/ToggleButton';
Expand Down Expand Up @@ -620,6 +624,32 @@ export const config = {
}
}
},
'radio-group': {
examples: [
{
filename: 'InitialValue',
module: InitialValueRadioGroup,
title: 'Initial Value'
},
{
filename: 'CustomLabel',
module: CustomLabelRadioGroup,
title: 'Custom Label'
},
{
filename: 'CustomRenderer',
module: CustomRendererRadioGroup,
title: 'Custom Renderer'
}
],
filename: 'index',
overview: {
example: {
filename: 'Basic',
module: BasicRadioGroup
}
}
},
'raised-button': {
examples: [
{
Expand Down
25 changes: 25 additions & 0 deletions src/examples/src/widgets/radio-group/Basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import RadioGroup from '@dojo/widgets/radio-group';
import { create, tsx } from '@dojo/framework/core/vdom';
import { icache } from '@dojo/framework/core/middleware/icache';

const factory = create({ icache });

const App = factory(function({ properties, middleware: { icache } }) {
const { get, set } = icache;

return (
<virtual>
<RadioGroup
label="pets"
name="standard"
options={[{ value: 'cat' }, { value: 'dog' }, { value: 'fish' }]}
onValue={(value) => {
set('standard', value);
}}
/>
<pre>{`${get('standard')}`}</pre>
</virtual>
);
});

export default App;
29 changes: 29 additions & 0 deletions src/examples/src/widgets/radio-group/CustomLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import RadioGroup from '@dojo/widgets/radio-group';
import { create, tsx } from '@dojo/framework/core/vdom';
import { icache } from '@dojo/framework/core/middleware/icache';

const factory = create({ icache });

const App = factory(function({ properties, middleware: { icache } }) {
const { get, set } = icache;

return (
<virtual>
<RadioGroup
label="colours"
name="colours"
options={[
{ value: 'red', label: 'Rouge' },
{ value: 'green', label: 'Vert' },
{ value: 'blue', label: 'Bleu' }
]}
onValue={(value) => {
set('colours', value);
}}
/>
<pre>{`${get('colours')}`}</pre>
</virtual>
);
});

export default App;
44 changes: 44 additions & 0 deletions src/examples/src/widgets/radio-group/CustomRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import RadioGroup from '@dojo/widgets/radio-group';
import { Radio } from '@dojo/widgets/radio';
import { create, tsx } from '@dojo/framework/core/vdom';
import { icache } from '@dojo/framework/core/middleware/icache';

const factory = create({ icache });

const App = factory(function({ properties, middleware: { icache } }) {
const { get, set } = icache;

return (
<virtual>
<RadioGroup
label="going?"
name="custom"
options={[{ value: 'yes' }, { value: 'no' }, { value: 'maybe' }]}
onValue={(value) => {
set('custom', value);
}}
renderer={(name, radioGroup, options) => {
return options.map(({ value, label }) => {
const { checked } = radioGroup(value);
return (
<virtual>
<span>I'm custom!</span>
<Radio
checked={checked()}
label={label || value}
name={name}
onValue={checked}
value={value}
/>
<hr />
</virtual>
);
});
}}
/>
<pre>{`${get('custom')}`}</pre>
</virtual>
);
});

export default App;
26 changes: 26 additions & 0 deletions src/examples/src/widgets/radio-group/InitialValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import RadioGroup from '@dojo/widgets/radio-group';
import { create, tsx } from '@dojo/framework/core/vdom';
import { icache } from '@dojo/framework/core/middleware/icache';

const factory = create({ icache });

const App = factory(function({ properties, middleware: { icache } }) {
const { get, set } = icache;

return (
<virtual>
<RadioGroup
initialValue="tom"
label="favourite names"
name="initial-value"
options={[{ value: 'tom' }, { value: 'dick' }, { value: 'harry' }]}
onValue={(value) => {
set('initial-value', value);
}}
/>
<pre>{`${get('initial-value')}`}</pre>
</virtual>
);
});

export default App;
9 changes: 9 additions & 0 deletions src/radio-group/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# @dojo/widgets/radio-group widget

Dojo's `RadioGroup` widget provides an opinionated way to use a group of check boxes in a form.

## Features

- Takes an options property to define the radios to create
- Offers a custom renderer allowing the user to create their own radios
- Provides a middleware for custom use
62 changes: 62 additions & 0 deletions src/radio-group/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as css from '../theme/radio-group.m.css';
import theme from '@dojo/framework/core/middleware/theme';
import { Radio } from '../radio/index';
import { RenderResult } from '@dojo/framework/core/interfaces';
import { create, tsx } from '@dojo/framework/core/vdom';
import { radioGroup } from './middleware';

type RadioOptions = { value: string; label?: string }[];

interface RadioGroupProperties {
/** Initial value of the radio group */
initialValue?: string;
/** The label to be displayed in the legend */
label?: string;
/** The name attribute for this form group */
name: string;
/** Callback for the current value */
onValue(value: string): void;
/** Object containing the values / labels to create radios for */
options: RadioOptions;
/** Custom renderer for the radios, receives the radio group middleware and options */
renderer?(
name: string,
middleware: ReturnType<ReturnType<typeof radioGroup>['api']>,
options: RadioOptions
): RenderResult;
}

const factory = create({ radioGroup, theme }).properties<RadioGroupProperties>();

export const RadioGroup = factory(function({ properties, middleware: { radioGroup, theme } }) {
const { name, label, options, renderer, onValue, initialValue } = properties();
Copy link
Member

Choose a reason for hiding this comment

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

you could do initialValue = '' here rather than use || below.

const radio = radioGroup(onValue, initialValue || '');
const { root, legend } = theme.classes(css);

function renderRadios() {
if (renderer) {
return renderer(name, radio, options);
}
return options.map(({ value, label }) => {
const { checked } = radio(value);
return (
<Radio
checked={checked()}
label={label || value}
name={name}
onValue={checked}
value={value}
/>
);
});
}

return (
<fieldset key="root" classes={root} name={name}>
{label && <legend classes={legend}>{label}</legend>}
{renderRadios()}
</fieldset>
);
});

export default RadioGroup;
34 changes: 34 additions & 0 deletions src/radio-group/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from '@dojo/framework/core/vdom';
import { createICacheMiddleware } from '@dojo/framework/core/middleware/icache';

interface RadioGroupICache {
initial: string;
value: string;
}

const icache = createICacheMiddleware<RadioGroupICache>();
const factory = create({ icache });

export const radioGroup = factory(({ middleware: { icache } }) => {
return (onValue: (value: string) => void, initialValue: string) => {
const existingInitialValue = icache.get('initial');
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be typed as a string: icache.get<string>('initial');?

Copy link
Member

Choose a reason for hiding this comment

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

it's typed above @JamesLMilner via RadioGroupICache


if (existingInitialValue !== initialValue) {
icache.set('value', initialValue);
icache.set('initial', initialValue);
}

return (key: string) => ({
checked(checked?: boolean) {
const existingValue = icache.get('value');
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be typed as a string icache.get<string>('value'); ?

Copy link
Member

Choose a reason for hiding this comment

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

same as above


if (!checked && existingValue === key) {
return existingValue === key && true;
} else if (checked && existingValue !== key) {
icache.set('value', key);
onValue(key);
}
}
});
};
});
95 changes: 95 additions & 0 deletions src/radio-group/tests/RadioGroup.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const { describe, it } = intern.getInterface('bdd');
import * as css from '../../theme/radio-group.m.css';
import Radio from '../../radio/index';
import RadioGroup from '../index';
import assertionTemplate from '@dojo/framework/testing/assertionTemplate';
import harness from '@dojo/framework/testing/harness';
import { tsx } from '@dojo/framework/core/vdom';

function noop() {}

describe('RadioGroup', () => {
const template = assertionTemplate(() => (
<fieldset key="root" classes={css.root} name="test" />
));

it('renders with options', () => {
const h = harness(() => (
<RadioGroup
name="test"
onValue={noop}
options={[{ value: 'cat' }, { value: 'fish' }, { value: 'dog' }]}
/>
));
const optionTemplate = template.setChildren('@root', () => [
<Radio name="test" value="cat" label="cat" checked={undefined} onValue={noop} />,
<Radio name="test" value="fish" label="fish" checked={undefined} onValue={noop} />,
<Radio name="test" value="dog" label="dog" checked={undefined} onValue={noop} />
]);
h.expect(optionTemplate);
});

it('renders with a label', () => {
const h = harness(() => (
<RadioGroup
label="test label"
name="test"
onValue={noop}
options={[{ value: 'cat' }]}
/>
));
const labelTemplate = template.setChildren('@root', () => [
<legend classes={css.legend}>test label</legend>,
<Radio name="test" value="cat" label="cat" checked={undefined} onValue={noop} />
]);
h.expect(labelTemplate);
});

it('renders with initial value', () => {
const h = harness(() => (
<RadioGroup
initialValue="fish"
name="test"
onValue={noop}
options={[{ value: 'cat' }, { value: 'fish' }, { value: 'dog' }]}
/>
));
const optionTemplate = template.setChildren('@root', () => [
<Radio name="test" value="cat" label="cat" checked={undefined} onValue={noop} />,
<Radio name="test" value="fish" label="fish" checked={true} onValue={noop} />,
<Radio name="test" value="dog" label="dog" checked={undefined} onValue={noop} />
]);
h.expect(optionTemplate);
});

it('renders with custom renderer', () => {
const h = harness(() => (
<RadioGroup
label="custom render label"
name="test"
onValue={noop}
options={[{ value: 'cat' }]}
renderer={() => {
return [
<span>custom label</span>,
<Radio
name="test"
value="cat"
label="cat"
checked={false}
onValue={noop}
/>,
<hr />
];
}}
/>
));
const customTemplate = template.setChildren('@root', () => [
<legend classes={css.legend}>custom render label</legend>,
<span>custom label</span>,
<Radio name="test" value="cat" label="cat" checked={false} onValue={noop} />,
<hr />
]);
h.expect(customTemplate);
});
});
Loading