Skip to content

Commit 865aa5c

Browse files
committed
Added Molecules RadioList and UnorderedList; Added Atom RadioButtonInput
1 parent 3c42170 commit 865aa5c

12 files changed

Lines changed: 378 additions & 0 deletions

src/assets/scss/6-components/molecules/__molecules.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
@import "error-banner";
77
@import "form-fields";
88
@import "forms";
9+
@import "radio-list";
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
.c-radio-list {
2+
> ul {
3+
> li {
4+
margin-bottom: 0;
5+
border-bottom: 1px solid get-color-neutral("30");
6+
> .c-radio {
7+
position: relative;
8+
&:hover {
9+
background-color: get-color-neutral("white");
10+
}
11+
&.-selected {
12+
background-color: get-color-accents("blue-light");
13+
&:hover {
14+
background-color: darken(
15+
get-color-accents("blue-light"),
16+
5%
17+
);
18+
}
19+
}
20+
21+
> label {
22+
position: static;
23+
width: 100%;
24+
&:before {
25+
top: calc(50% - 10px);
26+
right: 10px;
27+
}
28+
&:after {
29+
top: calc(50% - 5px);
30+
right: 15px; // 10px right + 1/2 of my width
31+
}
32+
}
33+
}
34+
}
35+
}
36+
37+
&.-button-style {
38+
> ul {
39+
> li {
40+
border: none;
41+
42+
> .c-radio {
43+
@include padding(0, 0, 0, 8px);
44+
border-radius: $border-radius-large;
45+
border: 1px solid get-color-neutral("30");
46+
47+
// Styles for native implmentation of :focus-within.
48+
&:focus-within {
49+
@include c-radio-focus-hover;
50+
}
51+
52+
// Styles for polyfill implementation to support IE. When grouping selectors
53+
// together, IE doesn't display desired styles.
54+
&.focus-within {
55+
@include c-radio-focus-hover;
56+
}
57+
58+
&:hover {
59+
@include c-radio-focus-hover;
60+
}
61+
62+
&.-selected {
63+
background-color: get-color-accents("blue-dark");
64+
65+
> label {
66+
color: get-color-neutral("white");
67+
}
68+
}
69+
70+
> label {
71+
@include padding(8px);
72+
@include padding-left(32px);
73+
color: get-color-accents("blue-dark");
74+
padding-right: 0;
75+
76+
&:before {
77+
right: unset;
78+
left: rem(10px);
79+
}
80+
81+
&:after {
82+
right: unset;
83+
left: rem(15px);
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import { text, boolean } from "@storybook/addon-knobs";
3+
import { RadioButton } from "./radio-button-input";
4+
import Faker from "faker";
5+
6+
export default {
7+
component: RadioButton,
8+
title: "Atoms | Forms / Radio Button",
9+
};
10+
11+
export const radioButtonKnobs = () => (
12+
<RadioButton
13+
checked={boolean("checked", false)}
14+
id={Faker.random.uuid()}
15+
label={text("Label", Faker.random.word())}
16+
name={text("Name", Faker.random.word())}
17+
value={text("Value", Faker.random.word())}
18+
/>
19+
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { RadioButton } from "./radio-button-input";
4+
import faker from "faker";
5+
import uuid from "uuid";
6+
7+
describe("RadioButton", () => {
8+
test("when default props, renders button with label", () => {
9+
// Arrange
10+
const expected = faker.random.word();
11+
12+
// Act
13+
const { getByLabelText } = render(
14+
<RadioButton
15+
checked={false}
16+
id={uuid()}
17+
label={expected}
18+
name={faker.random.word()}
19+
/>
20+
);
21+
22+
// Assert
23+
expect(getByLabelText(expected)).not.toBeNull();
24+
});
25+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from "react";
2+
import { forwardRef } from "react";
3+
4+
const COMPONENT_CLASS = "c-radio";
5+
6+
interface RadioButtonProps {
7+
autofocus?: boolean;
8+
checked: boolean;
9+
children?: React.ReactNode;
10+
cssClassName?: string;
11+
id: string;
12+
label: string;
13+
name: string;
14+
onCheck?: (e: React.ChangeEvent<HTMLInputElement>) => void;
15+
onClick?: () => void;
16+
ref?: React.RefObject<HTMLInputElement> | null;
17+
value?: string | string[] | number;
18+
}
19+
20+
const RadioButton: React.RefForwardingComponent<
21+
HTMLInputElement,
22+
RadioButtonProps
23+
> = forwardRef((props: RadioButtonProps, ref: React.Ref<HTMLInputElement>) => {
24+
const {
25+
autofocus,
26+
checked,
27+
children,
28+
cssClassName,
29+
id,
30+
label,
31+
name,
32+
onCheck,
33+
onClick,
34+
value,
35+
} = props;
36+
37+
const handleChecked = (e: React.ChangeEvent<HTMLInputElement>): void =>
38+
onCheck?.(e);
39+
const handleClick = (): void => onClick?.();
40+
41+
const cssChecked = checked ? "-selected" : "";
42+
43+
return (
44+
<div className={`${COMPONENT_CLASS} ${cssChecked} ${cssClassName}`}>
45+
<input
46+
autoFocus={autofocus}
47+
checked={checked}
48+
id={id}
49+
name={name}
50+
onChange={handleChecked}
51+
onClick={handleClick}
52+
ref={ref}
53+
type="radio"
54+
value={value}
55+
/>
56+
<label htmlFor={id}>
57+
{label}
58+
{children}
59+
</label>
60+
</div>
61+
);
62+
});
63+
64+
export { RadioButton };

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export { ProgressBar } from "./atoms/progress-bar/progress-bar";
1313
export { CheckboxButton } from "./atoms/forms/checkbox-button";
1414
export { CheckboxInput } from "./atoms/forms/checkbox-input";
1515
export { InputCharacterCount } from "./atoms/forms/input-character-count";
16+
export { RadioButton } from "./atoms/forms/radio-button-input";
1617
export { PasswordInput } from "./atoms/forms/password-input";
1718
export { Select } from "./atoms/forms/select";
1819
export { SubmitButton } from "./atoms/forms/submit-button";
@@ -63,6 +64,8 @@ export { Card } from "./molecules/cards/card";
6364
export { DropdownButton } from "./molecules/dropdown-button/dropdown-button";
6465
export { ErrorBanner } from "./molecules/errors/error-banner";
6566
export { Form } from "./molecules/forms/form";
67+
export { RadioList } from "./molecules/lists/radio-list";
68+
export { UnorderedList } from "./molecules/lists/unordered-list";
6669

6770
// Form Fields
6871

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react";
2+
import { RadioList } from "./radio-list";
3+
import Faker from "faker";
4+
import { RadioButton } from "../../atoms/forms/radio-button-input";
5+
import uuid from "uuid";
6+
import { boolean } from "@storybook/addon-knobs";
7+
8+
export default {
9+
component: RadioList,
10+
title: "Molecules | Lists / RadioList",
11+
};
12+
13+
export const radioListDefault = () => (
14+
<RadioList
15+
items={[
16+
<RadioButton
17+
checked={boolean("checked", false)}
18+
id={uuid()}
19+
label={Faker.random.word()}
20+
name={Faker.random.word()}>
21+
{Faker.lorem.text()}
22+
</RadioButton>,
23+
<RadioButton
24+
checked={boolean("checked", true)}
25+
id={uuid()}
26+
label={Faker.random.word()}
27+
name={Faker.random.word()}>
28+
{Faker.lorem.text()}
29+
</RadioButton>,
30+
]}
31+
/>
32+
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { RadioList } from "./radio-list";
4+
import faker from "faker";
5+
6+
describe("RadioList", () => {
7+
test("when default props, renders items", () => {
8+
// Arrange
9+
const expected = faker.random.words();
10+
11+
// Act
12+
const { getByText } = render(
13+
<RadioList items={[<span>{expected}</span>]} />
14+
);
15+
16+
// Assert
17+
expect(getByText(expected)).not.toBeNull();
18+
});
19+
});

src/molecules/lists/radio-list.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from "react";
2+
import { UnorderedList } from "./unordered-list";
3+
4+
const COMPONENT_CLASS = "c-radio-list";
5+
6+
export interface RadioListProps {
7+
items: JSX.Element[];
8+
style?: RadioListStyles;
9+
}
10+
11+
export enum RadioListStyles {
12+
Default = "default",
13+
Button = "button",
14+
}
15+
16+
const RadioList: React.FunctionComponent<RadioListProps> = (props) => {
17+
const { items, style } = props;
18+
19+
if (items.length === 0) {
20+
return null;
21+
}
22+
23+
const classNames = [COMPONENT_CLASS];
24+
if (style === RadioListStyles.Button) {
25+
classNames.push("-button-style");
26+
}
27+
28+
return (
29+
<fieldset className={classNames.join(" ")}>
30+
<UnorderedList listItems={items} />
31+
</fieldset>
32+
);
33+
};
34+
35+
export { RadioList };
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
import { UnorderedList } from "./unordered-list";
3+
import Faker from "faker";
4+
import { select } from "@storybook/addon-knobs";
5+
import { Icons } from "../../atoms/constants/icons";
6+
7+
export default {
8+
component: UnorderedList,
9+
title: "Molecules | Lists / UnorderedList",
10+
};
11+
12+
export const unorderedListDefault = () => (
13+
<UnorderedList listItems={[Faker.lorem.text(), Faker.lorem.text()]} />
14+
);
15+
16+
export const unorderedListKnobs = () => (
17+
<UnorderedList
18+
listIcon={select("List Icon", Icons, Icons.ChevronRight)}
19+
listItems={[Faker.lorem.text(), Faker.lorem.text()]}
20+
/>
21+
);

0 commit comments

Comments
 (0)