Skip to content

Commit

Permalink
Allow refs to be handled by form-related Components, with the excepti…
Browse files Browse the repository at this point in the history
…on of <Label>.
  • Loading branch information
8845musign committed Jun 7, 2024
1 parent 6aa3b9f commit 17b6178
Show file tree
Hide file tree
Showing 20 changed files with 261 additions and 106 deletions.
12 changes: 12 additions & 0 deletions src/components/Button/Button.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { Button } from './Button';

describe('Button', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLButtonElement>();
render(<Button ref={ref}>Test</Button>);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('BUTTON');
});
});
17 changes: 17 additions & 0 deletions src/components/Checkbox/Checkbox.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { Checkbox } from './Checkbox';

describe('Checkbox', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLInputElement>();
render(
<Checkbox name="test" value="test" ref={ref}>
Test
</Checkbox>,
);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('INPUT');
expect(ref.current?.type).toBe('checkbox');
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { useState, useCallback } from 'react';
import { Checkbox, CheckboxGroup, Stack } from '../';
import { Checkbox, CheckboxGroup, Stack } from '../../index';
import type { ChangeEventHandler } from 'react';

export default {
Expand Down
11 changes: 7 additions & 4 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { CheckAIcon } from '@ubie/ubie-icons';
import clsx from 'clsx';
import { FC, InputHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import styles from './Checkbox.module.css';
import type { InputHTMLAttributes } from 'react';

type Props = {
/**
Expand All @@ -29,14 +30,16 @@ type Props = {
onChange?: InputHTMLAttributes<HTMLInputElement>['onChange'];
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'children' | 'onChange'>;

export const Checkbox: FC<Props> = ({ size = 'medium', children, ...otherProps }) => {
export const Checkbox = forwardRef<HTMLInputElement, Props>(({ size = 'medium', children, ...otherProps }, ref) => {
return (
<label className={clsx(styles.container, styles[size])}>
<input type="checkbox" className={styles.checkbox} {...otherProps} />
<input ref={ref} type="checkbox" className={styles.checkbox} {...otherProps} />
<span className={styles.checkIconContainer}>
<CheckAIcon className={styles.checkIcon} />
</span>
{children}
</label>
);
};
});

Checkbox.displayName = 'Checkbox';
17 changes: 17 additions & 0 deletions src/components/CheckboxGroup/CheckboxGroup.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { CheckboxGroup } from './CheckboxGroup';

describe('CheckboxGroup', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLFieldSetElement>();
render(
<CheckboxGroup label="test" ref={ref}>
<p>Test</p>
<p>Test</p>
</CheckboxGroup>,
);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('FIELDSET');
});
});
52 changes: 20 additions & 32 deletions src/components/CheckboxGroup/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,34 @@
'use client';

import { forwardRef } from 'react';
import styles from './CheckboxGroup.module.css';
import { RequiredLabel } from '../../sharedComponents/RequiredLabel/RequiredLabel';
import { CustomDataAttributeProps } from '../../types/attributes'; // 追加したインポート
import { CustomDataAttributeProps } from '../../types/attributes';
import { Checkbox } from '../Checkbox/Checkbox';
import { Flex } from '../Flex/Flex';
import type { FC, ReactElement } from 'react';
import type { ReactElement } from 'react';

export type Props = {
children: ReactElement<typeof Checkbox>[];
/**
* チェックボックスグループの見出し(legend要素)
*/
label: string;
/**
* 必須マークを表示するか
* 注意: trueとしてもinput要素のrequired属性は付与されません
*/
showRequiredLabel?: boolean;
/**
* チェックボックスの配置方向
* @default column
*/
direction?: 'column' | 'row';
} & CustomDataAttributeProps;

export const CheckboxGroup: FC<Props> = ({
children,
label,
showRequiredLabel,
direction = 'column',
...otherProps
}) => {
return (
<fieldset className={styles.wrapper} {...otherProps}>
<legend className={styles.legend}>
{label}
{showRequiredLabel && <RequiredLabel />}
</legend>
<Flex spacing="md" direction={direction}>
{children}
</Flex>
</fieldset>
);
};
export const CheckboxGroup = forwardRef<HTMLFieldSetElement, Props>(
({ children, label, showRequiredLabel, direction = 'column', ...otherProps }, ref) => {
return (
<fieldset className={styles.wrapper} ref={ref} {...otherProps}>
<legend className={styles.legend}>
{label}
{showRequiredLabel && <RequiredLabel />}
</legend>
<Flex spacing="md" direction={direction}>
{children}
</Flex>
</fieldset>
);
},
);

CheckboxGroup.displayName = 'CheckboxGroup';
12 changes: 12 additions & 0 deletions src/components/ErrorMessage/ErrorMessage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { ErrorMessage } from './ErrorMessage';

describe('ErrorMessage', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLParagraphElement>();
render(<ErrorMessage ref={ref}>Test</ErrorMessage>);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('P');
});
});
13 changes: 8 additions & 5 deletions src/components/ErrorMessage/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
'use client';

import { forwardRef } from 'react';
import styles from './ErrorMessage.module.css';
import { CustomDataAttributeProps } from '../../types/attributes'; // 追加したインポート
import type { FC, ReactNode } from 'react';
import { CustomDataAttributeProps } from '../../types/attributes';
import type { ReactNode } from 'react';

type Props = {
children: ReactNode;
} & CustomDataAttributeProps;

export const ErrorMessage: FC<Props> = ({ children, ...otherProps }) => (
<p aria-live="polite" className={styles.error} {...otherProps}>
export const ErrorMessage = forwardRef<HTMLParagraphElement, Props>(({ children, ...otherProps }, ref) => (
<p aria-live="polite" className={styles.error} ref={ref} {...otherProps}>
{children}
</p>
);
));

ErrorMessage.displayName = 'ErrorMessage';
14 changes: 14 additions & 0 deletions src/components/Input/Input.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { Input } from './Input';

describe('Input', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLInputElement>();

render(<Input name="test" value="test" ref={ref} />);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('INPUT');
expect(ref.current?.type).toBe('text');
});
});
4 changes: 2 additions & 2 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { clsx } from 'clsx';
import { FC, forwardRef, InputHTMLAttributes } from 'react';
import { forwardRef, InputHTMLAttributes } from 'react';
import styles from './Input.module.css';
import { CustomDataAttributeProps } from '../../types/attributes'; // 追加したインポート

Expand All @@ -18,7 +18,7 @@ type Props = {
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'invalid' | 'value'> &
CustomDataAttributeProps;

export const Input: FC<Props> = forwardRef<HTMLInputElement, Props>(({ isInvalid, ...props }, ref) => {
export const Input = forwardRef<HTMLInputElement, Props>(({ isInvalid, ...props }, ref) => {
const className = clsx({ [styles.isInvalid]: isInvalid && !props.disabled }, styles.input, props.className);

return <input {...props} className={className} ref={ref} aria-invalid={isInvalid} />;
Expand Down
25 changes: 25 additions & 0 deletions src/components/RadioButton/RadioButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { RadioButton } from './RadioButton';

describe('RadioButton', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLInputElement>();
render(
<RadioButton
name="test"
value="test"
ref={ref}
onChange={() => {
/**/
}}
checked={false}
>
Test
</RadioButton>,
);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('INPUT');
expect(ref.current?.type).toBe('radio');
});
});
53 changes: 25 additions & 28 deletions src/components/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import clsx from 'clsx';
import { FC, InputHTMLAttributes } from 'react';
import { forwardRef, type InputHTMLAttributes } from 'react';
import styles from './RadioButton.module.css';
import { CustomDataAttributeProps } from '../../types/attributes';

Expand Down Expand Up @@ -29,30 +29,27 @@ type Props = {
} & RadioProps &
CustomDataAttributeProps;

export const RadioButton: FC<Props> = ({
size = 'medium',
checked,
onChange,
value,
name,
children,
...otherProps
}) => {
return (
<div className={clsx(styles[size])}>
<label className={styles.label}>
<input
type="radio"
checked={checked}
name={name}
value={value}
className={styles.radio}
onChange={onChange}
{...otherProps}
/>
<span className={styles.icon} />
<span className={styles.text}>{children}</span>
</label>
</div>
);
};
export const RadioButton = forwardRef<HTMLInputElement, Props>(
({ size = 'medium', checked, onChange, value, name, children, ...otherProps }, ref) => {
return (
<div className={clsx(styles[size])}>
<label className={styles.label}>
<input
type="radio"
checked={checked}
name={name}
value={value}
className={styles.radio}
onChange={onChange}
ref={ref}
{...otherProps}
/>
<span className={styles.icon} />
<span className={styles.text}>{children}</span>
</label>
</div>
);
},
);

RadioButton.displayName = 'RadioButton';
17 changes: 17 additions & 0 deletions src/components/RadioGroup/RadioGroup.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react';
import { createRef } from 'react';
import { RadioGroup } from './RadioGroup';

describe('RadioGroup', () => {
it('access to DOM through ref prop', () => {
const ref = createRef<HTMLFieldSetElement>();
render(
<RadioGroup label="test" ref={ref}>
<p>Test</p>
<p>Test</p>
</RadioGroup>,
);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('FIELDSET');
});
});
Loading

0 comments on commit 17b6178

Please sign in to comment.