Skip to content

Commit

Permalink
Merge pull request #82 from 8845musign/add-required-label
Browse files Browse the repository at this point in the history
Add showRequiredLabel prop
  • Loading branch information
takanorip authored May 13, 2024
2 parents 80776d5 + 94f9464 commit 58016e2
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/components/CheckboxGroup/CheckboxGroup.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@
color: var(--color-text-sub);
margin-bottom: var(--size-spacing-md);
padding: 0;
display: flex;
align-items: center;
gap: var(--size-spacing-xs);
}
13 changes: 11 additions & 2 deletions src/components/CheckboxGroup/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import styles from './CheckboxGroup.module.css';
import { RequiredLabel } from '../../sharedComponents/RequiredLabel/RequiredLabel';
import { Checkbox } from '../Checkbox/Checkbox';
import { Flex } from '../Flex/Flex';
import type { FC, ReactElement } from 'react';
Expand All @@ -11,17 +12,25 @@ export type Props = {
* チェックボックスグループの見出し(legend要素)
*/
label: string;
/**
* 必須マークを表示するか
* 注意: trueとしてもinput要素のrequired属性は付与されません
*/
showRequiredLabel?: boolean;
/**
* チェックボックスの配置方向
* @default column
*/
direction?: 'column' | 'row';
};

export const CheckboxGroup: FC<Props> = ({ children, label, direction = 'column' }) => {
export const CheckboxGroup: FC<Props> = ({ children, label, showRequiredLabel, direction = 'column' }) => {
return (
<fieldset className={styles.wrapper}>
<legend className={styles.legend}>{label}</legend>
<legend className={styles.legend}>
{label}
{showRequiredLabel && <RequiredLabel />}
</legend>
<Flex spacing="md" direction={direction}>
{children}
</Flex>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Label/Label.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
.label {
display: flex;
font-size: var(--text-body-sm-size);
font-weight: bold;
color: var(--color-text-sub);
align-items: center;
gap: var(--size-spacing-xs);
}

legend.label {
Expand Down
9 changes: 8 additions & 1 deletion src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import styles from './Label.module.css';
import { RequiredLabel } from '../../sharedComponents/RequiredLabel/RequiredLabel';
import type { ElementType, FC, ReactNode } from 'react';

type Props = {
Expand All @@ -14,12 +15,18 @@ type Props = {
* ラベルが紐づくフォーム要素のid属性。asにlabelを指定した場合に必用
*/
htmlFor?: string;
/**
* 必須マークを表示するか
* 注意: trueとしてもinput要素のrequired属性は付与されません
*/
showRequiredLabel?: boolean;
};

export const Label: FC<Props> = ({ children, as: LabelComponent = 'label', htmlFor }) => {
export const Label: FC<Props> = ({ children, as: LabelComponent = 'label', htmlFor, showRequiredLabel }) => {
return (
<LabelComponent htmlFor={htmlFor} className={styles.label}>
{children}
{showRequiredLabel && <RequiredLabel />}
</LabelComponent>
);
};
3 changes: 3 additions & 0 deletions src/components/RadioGroup/RadioGroup.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@
color: var(--color-text-sub);
margin-bottom: var(--size-spacing-md);
padding: 0;
display: flex;
align-items: center;
gap: var(--size-spacing-xs);
}
13 changes: 11 additions & 2 deletions src/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import styles from './RadioGroup.module.css';
import { RequiredLabel } from '../../sharedComponents/RequiredLabel/RequiredLabel';
import { Flex } from '../Flex/Flex';
import { RadioButton } from '../RadioButton/RadioButton';
import { RadioCard } from '../RadioCard/RadioCard';
Expand All @@ -14,20 +15,28 @@ export type Props = {
* ラジオグループの見出し(legend要素)
*/
label: string;
/**
* 必須マークを表示するか
* 注意: trueとしてもinput要素のrequired属性は付与されません
*/
showRequiredLabel?: boolean;
/**
* ラジオボタンの配置方向
* @default column
*/
direction?: 'column' | 'row';
};

export const RadioGroup: FC<Props> = ({ children, label, direction = 'column' }) => {
export const RadioGroup: FC<Props> = ({ children, label, showRequiredLabel = false, direction = 'column' }) => {
const childrenIsCard = children.some((child) => child.type === RadioCard);
const childenIsBlock = direction === 'row' || (childrenIsCard && direction === 'column');

return (
<fieldset className={styles.wrapper}>
<legend className={styles.legend}>{label}</legend>
<legend className={styles.legend}>
{label}
{showRequiredLabel && <RequiredLabel />}
</legend>
<Flex
spacing={childrenIsCard ? 'sm' : 'md'}
alignItems={childenIsBlock ? 'normal' : undefined}
Expand Down
9 changes: 9 additions & 0 deletions src/sharedComponents/RequiredLabel/RequiredLabel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.required {
padding: 2px var(--size-spacing-xxs) 1px;
border-radius: var(--radius-sm);
font-size: var(--text-tag-md-size);
line-height: var(--text-tag-md-line);
display: inline-block;
color: var(--color-accent);
background-color: var(--color-background-accent);
}
8 changes: 8 additions & 0 deletions src/sharedComponents/RequiredLabel/RequiredLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';

import React from 'react';
import styles from './RequiredLabel.module.css';

export const RequiredLabel: React.FC = () => {
return <span className={styles.required}>必須</span>;
};
33 changes: 33 additions & 0 deletions src/stories/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,36 @@ export const Disabled: Story = {
</Stack>
),
};

export const ShowRequiredLabel: Story = {
render: () => {
const [selectedItem, setSelectedItem] = useState<string[]>([options[0]]);

const onChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => {
if (event.target.checked) {
setSelectedItem([...selectedItem, event.target.value]);
} else {
setSelectedItem(selectedItem.filter((item) => item !== event.target.value));
}
},
[selectedItem],
);

return (
<CheckboxGroup label="Checkbox" showRequiredLabel>
{options.map((option) => (
<Checkbox
name="default"
value={option}
onChange={onChange}
checked={selectedItem.includes(option)}
key={option}
>
{option}
</Checkbox>
))}
</CheckboxGroup>
);
},
};
6 changes: 4 additions & 2 deletions src/stories/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const Default: StoryObj = {

return (
<Stack spacing="xs">
<Label htmlFor="field">項目</Label>
<Label htmlFor="field" showRequiredLabel>
項目
</Label>
<Input id="field" value={value} onChange={onChange} />
<HelperMessage>説明文です</HelperMessage>
<HelperMessage>説明文です</HelperMessage>
Expand Down Expand Up @@ -55,7 +57,7 @@ export const RadioButtons: StoryObj = {

return (
<Stack spacing="md" alignItems="normal">
<RadioGroup label="通院状況">
<RadioGroup label="通院状況" showRequiredLabel>
{options.map((option) => (
<RadioButton
name="commuting"
Expand Down
8 changes: 8 additions & 0 deletions src/stories/Label.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ type Story = StoryObj<typeof Label>;
export const Default: Story = {
render: () => <Label htmlFor="id">全角カタカナでご入力ください</Label>,
};

export const Rqruied: Story = {
render: () => (
<Label htmlFor="id" showRequiredLabel>
全角カタカナでご入力ください
</Label>
),
};
32 changes: 32 additions & 0 deletions src/stories/RadioButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,35 @@ export const Disabled: Story = {
name: 'disabled',
},
};

export const ShowRequiredLabel: Story = {
render: (args) => {
const [selectedItem, setSelectedItem] = useState(options[0]);

const onChange: ChangeEventHandler<HTMLInputElement> = useCallback((event) => {
setSelectedItem(event.target.value);
}, []);

return (
<RadioGroup label="RadioButton" showRequiredLabel>
{options.map((option) => (
<RadioButton
key={option}
{...args}
value={option}
id={option}
onChange={onChange}
checked={selectedItem === option}
name="default"
>
{option}
</RadioButton>
))}
</RadioGroup>
);
},
args: {
...defaultArgs,
name: 'default',
},
};
27 changes: 27 additions & 0 deletions src/stories/RadioCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,30 @@ export const Block: Story = {
block: true,
},
};

export const ShowRequiredLabel: Story = {
render: () => {
const [selectedItem, setSelectedItem] = useState(options[0]);

const onChange: ChangeEventHandler<HTMLInputElement> = useCallback((event) => {
setSelectedItem(event.target.value);
}, []);

return (
<RadioGroup label="RadioCard" showRequiredLabel>
{options.map((option) => (
<RadioCard
name="options"
value={option}
onChange={onChange}
checked={selectedItem === option}
id={option}
key={option}
>
{option}
</RadioCard>
))}
</RadioGroup>
);
},
};

0 comments on commit 58016e2

Please sign in to comment.