Skip to content

Commit

Permalink
chore(web): radio and radio group field components (#650)
Browse files Browse the repository at this point in the history
Co-authored-by: nina992 <nouralali992@gmail.com>
  • Loading branch information
nina992 and nina992 committed Sep 8, 2023
1 parent c2c2b06 commit afaa24f
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 0 deletions.
35 changes: 35 additions & 0 deletions web/src/beta/components/RadioGroup/RadioBox/index.stories.tsx
@@ -0,0 +1,35 @@
import { useArgs } from "@storybook/preview-api";
import { Meta, StoryObj } from "@storybook/react";
import { useCallback } from "react";

import RadioBox from ".";

type Props = React.ComponentProps<typeof RadioBox>;

const meta: Meta<typeof RadioBox> = {
component: RadioBox,
};

export default meta;

type Story = StoryObj<typeof RadioBox>;

export const Default: Story = (args: Props) => {
const [_, updateArgs] = useArgs();

const handleChange = useCallback(
(value: string) => {
console.log(value);
updateArgs({ value });
},
[updateArgs],
);

return <RadioBox {...args} onClick={handleChange} />;
};

Default.args = {
inactive: false,
selected: false,
value: "test",
};
86 changes: 86 additions & 0 deletions web/src/beta/components/RadioGroup/RadioBox/index.tsx
@@ -0,0 +1,86 @@
import { useCallback, useState } from "react";

import { fonts, styled } from "@reearth/services/theme";

export type Props = {
inactive?: boolean;
selected?: boolean;
value: string;
onClick?: (value: string) => void;
};

const RadioBox: React.FC<Props> = ({ inactive, selected, value, onClick }: Props) => {
const [isChecked, setIsChecked] = useState(selected ?? false);

const handleRadioClick = useCallback(() => {
setIsChecked(!isChecked);
if (onClick) onClick(value);
}, [isChecked, onClick, value]);

return (
<Radio selected={isChecked} inactive={inactive}>
<RadioInput type="radio" value={inactive ? undefined : value} onClick={handleRadioClick} />
<RadioButton selected={isChecked} inactive={inactive}>
{isChecked && <Checkmark selected={isChecked} inactive={inactive} />}
</RadioButton>
<RadioText>{value}</RadioText>
</Radio>
);
};
export default RadioBox;

const Checkmark = styled.div<{
inactive?: boolean;
selected?: boolean;
}>`
width: 10px;
height: 10px;
border-radius: 50%;
background-color: white;
background-color: ${({ selected, inactive, theme }) =>
selected ? theme.select.main : inactive ? theme.content.weaker : theme.content.main};
`;

const Radio = styled.label<{
inactive?: boolean;
selected?: boolean;
}>`
display: flex;
align-items: center;
min-width: 30px;
min-height: 30px;
list-style: none;
padding: 4px;
font-size: ${fonts.sizes.footnote}px;
cursor: pointer;
box-sizing: border-box;
border-radius: 2px;
:not(:last-child) {
margin-right: 1px;
}
`;

const RadioInput = styled.input`
opacity: 0;
position: absolute;
`;

const RadioButton = styled.span<{
inactive?: boolean;
selected?: boolean;
}>`
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid
${({ selected, inactive, theme }) =>
selected ? theme.select.main : inactive ? theme.content.weaker : theme.content.main};
margin-right: 4px;
display: flex;
justify-content: center;
align-items: center;
`;

const RadioText = styled.span`
flex-grow: 1;
`;
54 changes: 54 additions & 0 deletions web/src/beta/components/RadioGroup/index.stories.tsx
@@ -0,0 +1,54 @@
import { useArgs } from "@storybook/preview-api";
import { Meta, StoryObj } from "@storybook/react";
import { useCallback } from "react";

import RadioGroup from "./index";

type Props = React.ComponentProps<typeof RadioGroup>;

const meta: Meta<typeof RadioGroup> = {
component: RadioGroup,
};

export default meta;

type Story = StoryObj<typeof RadioGroup>;

const options = [
{ key: "option1", value: "Option 1", selected: false },
{ key: "option2", value: "Option 2", selected: false },
];

export const VerticalRadioGroup: Story = (args: Props) => {
const [_, updateArgs] = useArgs();

const handleChange = useCallback((value: string) => updateArgs({ value }), [updateArgs]);

return (
<div>
<RadioGroup {...args} onChange={handleChange} />
</div>
);
};
VerticalRadioGroup.args = {
options: options,
layout: "vertical",
onChange: () => console.log("clicked"),
};

export const HorizontalRadioGroup: Story = (args: Props) => {
const [_, updateArgs] = useArgs();

const handleChange = useCallback((value: string) => updateArgs({ value }), [updateArgs]);

return (
<div>
<RadioGroup {...args} onChange={handleChange} />
</div>
);
};
HorizontalRadioGroup.args = {
options: options,
layout: "horizontal",
onChange: () => console.log("clicked"),
};
53 changes: 53 additions & 0 deletions web/src/beta/components/RadioGroup/index.tsx
@@ -0,0 +1,53 @@
import { memo, useCallback, useState } from "react";

import RadioBox from "@reearth/beta/components/RadioGroup/RadioBox";
import { styled } from "@reearth/services/theme";

export type Option = {
key: string;
value: string;
selected: boolean;
};

export type RadioGroupProps = {
options: Option[];
layout?: "vertical" | "horizontal";
onChange?: (value: string) => void;
};

const RadioGroup: React.FC<RadioGroupProps> = ({ options, layout, onChange }) => {
const [currentOptions, updateOptions] = useState<Option[]>(options);
const [key, setKey] = useState(0);

const handleRadioChange = useCallback(
(value: string) => {
updateOptions(
currentOptions.map(option => ({
...option,
selected: !option.selected && option.value === value,
})),
);
setKey(prevKey => prevKey + 1);
onChange?.(value);
},
[currentOptions, onChange],
);
return (
<RadioGroupContainer layout={layout}>
{currentOptions.map(option => (
<RadioBox
key={`${option.key}-${key}`}
value={option.value}
selected={option.selected}
onClick={() => handleRadioChange(option.value)}
/>
))}
</RadioGroupContainer>
);
};
export default memo(RadioGroup);

const RadioGroupContainer = styled.div<{ layout?: "vertical" | "horizontal" }>`
display: flex;
flex-direction: ${({ layout }) => (layout === "vertical" ? "column" : "row")};
`;

0 comments on commit afaa24f

Please sign in to comment.