From 35cb8b4f14eec033defe6487465b397554775924 Mon Sep 17 00:00:00 2001 From: d0422 Date: Fri, 17 May 2024 11:20:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20useRadio=20=ED=9B=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useRadio/useRadio.tsx | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/useRadio/useRadio.tsx diff --git a/src/useRadio/useRadio.tsx b/src/useRadio/useRadio.tsx new file mode 100644 index 0000000..3d8dfeb --- /dev/null +++ b/src/useRadio/useRadio.tsx @@ -0,0 +1,71 @@ +import React, { CSSProperties, ReactNode, createContext, useContext, useState } from 'react'; + +type RadioValue = string | number; +const RadioContext = createContext<[RadioValue | undefined, React.Dispatch>]>([ + undefined, + () => {}, +]); + +const RadioGroup = ({ + radioState, + className, + style, + children, +}: { + radioState: [T | undefined, React.Dispatch>]; + className?: string; + style?: CSSProperties; + children: ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; + +const Radio = ({ + value, + className, + style, + children, +}: { + value: T; + className?: string; + style?: CSSProperties; + children: ReactNode; +}) => { + const [radioValue, setRadioValue] = useContext(RadioContext); + + return ( + + ); +}; + +export default function useRadio(defaultValue?: T) { + const radioState = useState(defaultValue); + + const RadioGroupWrapper = ({ className, style, children }: { className?: string; style?: CSSProperties; children: ReactNode }) => { + return ( + + {children} + + ); + }; + + return { + RadioGroup: RadioGroupWrapper, + Radio: Radio, + value: radioState[0], + }; +} From 4ac31da2eabee6e224c2e814f8235fd96dcb265f Mon Sep 17 00:00:00 2001 From: d0422 Date: Fri, 17 May 2024 15:57:51 +0900 Subject: [PATCH 2/4] =?UTF-8?q?test:=20useRadio=20storybook=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/useRadio/Docs.mdx | 31 +++++++++++++++++++++++++ src/stories/useRadio/Radio.stories.ts | 21 +++++++++++++++++ src/stories/useRadio/Radio.tsx | 33 +++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/stories/useRadio/Docs.mdx create mode 100644 src/stories/useRadio/Radio.stories.ts create mode 100644 src/stories/useRadio/Radio.tsx diff --git a/src/stories/useRadio/Docs.mdx b/src/stories/useRadio/Docs.mdx new file mode 100644 index 0000000..2efe79c --- /dev/null +++ b/src/stories/useRadio/Docs.mdx @@ -0,0 +1,31 @@ +import { Canvas, Meta, Description } from '@storybook/blocks'; +import * as Radio from './Radio.stories'; + + + +# useRadio + +비제어 컴포넌트 Radio를 제어 컴포넌트로 사용하기 위한 훅입니다. + +## 제네릭 + +훅 사용시 제네릭을 사용하여 type safe하게 Radio를 구성할 수 있습니다. + +```typescript +type RadioType = '🍕' | '🍔' | '🍟' | '🌭'; +const { value, Radio, RadioGroup } = useRadio('🍕'); +``` + +## 함수 인자 + +`defaultValue`를 설정할 수 있습니다. + +## 반환값 + +`value` : 현재 Radio들 중 선택된 값입니다. + +`Radio`: Radio 컴포넌트입니다. 훅을 통해 type safe하게 value를 설정할 수 있습니다. children으로 화면에 표기할 값을 변경할 수 있습니다. + +`RadioGroup`: Radio들을 묶어줄 하나의 그룹입니다. + + diff --git a/src/stories/useRadio/Radio.stories.ts b/src/stories/useRadio/Radio.stories.ts new file mode 100644 index 0000000..b420314 --- /dev/null +++ b/src/stories/useRadio/Radio.stories.ts @@ -0,0 +1,21 @@ +import { Meta, StoryObj } from '@storybook/react'; +import Radio from './Radio'; + +const meta = { + title: 'hooks/useRadio', + component: Radio, + parameters: { + layout: 'centered', + docs: { + canvas: {}, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const defaultStory: Story = { + args: {}, +}; diff --git a/src/stories/useRadio/Radio.tsx b/src/stories/useRadio/Radio.tsx new file mode 100644 index 0000000..d0b1cc5 --- /dev/null +++ b/src/stories/useRadio/Radio.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import useRadio from '../../useRadio/useRadio'; + +type RadioType = '🍕' | '🍔' | '🍟' | '🌭'; + +export default function Radio() { + const { value, Radio, RadioGroup } = useRadio('🍕'); + return ( +
+ + 🍕 + 🍔 + 🍟 + 🌭 + +
+ 오늘 점심은 {value} +
+
+ ); +} From c4476b1f9f921442e03129a7dc30034f5ac3018e Mon Sep 17 00:00:00 2001 From: d0422 Date: Fri, 17 May 2024 16:02:42 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20useRadio=20Docs=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 6c85e7f..d5fbc8e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,31 @@ simple hook to change input components(uncontroll component) to controll compone ``` +### useRadio + +A hook to use the uncontrolled Radio component as a controlled component. + +#### Generic + +By using generics, you can configure the Radio component in a type-safe manner when using the hook. + +```typescript +type RadioType = '🍕' | '🍔' | '🍟' | '🌭'; +const { value, Radio, RadioGroup } = useRadio('🍕'); +``` + +#### Function Arguments + +You can set a `defaultValue`. + +#### Return Values + +`value`: The currently selected value among the Radios. + +`Radio`: The Radio component. You can set the value in a type-safe manner through the hook. You can change the displayed value using children. + +`RadioGroup`: A group that wraps multiple Radios. + ### useInterval simple hook to setInterval with React Component From 2f38f551eba9006f4fa6644c5d45ccdfc8b051d0 Mon Sep 17 00:00:00 2001 From: d0422 Date: Fri, 17 May 2024 16:03:24 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test:=20useRadio=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useRadio/useRadio.test.tsx | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/useRadio/useRadio.test.tsx diff --git a/src/useRadio/useRadio.test.tsx b/src/useRadio/useRadio.test.tsx new file mode 100644 index 0000000..ed05b30 --- /dev/null +++ b/src/useRadio/useRadio.test.tsx @@ -0,0 +1,61 @@ +import useRadio from './useRadio'; +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; + +type RadioType = '1' | '2' | '3' | '4'; + +const RadioTestComponent = () => { + const { value, Radio, RadioGroup } = useRadio('1'); + return ( +
+ + 1 + 2 + 3 + 4 + +
{value}
+
+ ); +}; + +const RadioNonDefaultTestComponent = () => { + const { value, Radio, RadioGroup } = useRadio(); + return ( +
+ + 1 + 2 + 3 + 4 + +
{value}
+
+ ); +}; +describe('useRadio 기능테스트', () => { + it('Radio가 선택되면 값을 변경할 수 있다.', async () => { + render(); + fireEvent.click(await screen.findByText('3')); + const [result] = await screen.findAllByRole('result'); + expect(result.textContent).toBe('3'); + + fireEvent.click(await screen.findByText('1')); + expect(result.textContent).toBe('1'); + + fireEvent.click(await screen.findByText('2')); + expect(result.textContent).toBe('2'); + }); + + it('Default 값이 없는 경우, check되지 않는 상태로 렌더링된다.', async () => { + const { container } = render(); + const result = await screen.findByRole('result'); + expect(result.textContent).toBe(''); + + const labels = container.querySelectorAll('label'); + labels.forEach((label) => { + const input = label.querySelector('input'); + expect(input!.checked).toBeFalsy(); + }); + }); +});