From b14a55672db9c77874644c1561b4ff6019eb6909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gromit=20=28=EC=A0=84=EB=AF=BC=EC=9E=AC=29?= <64779472+ssi02014@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:35:24 +0900 Subject: [PATCH] =?UTF-8?q?feat(react):=20AspectRatio=20Slot=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20(#579)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(react): AspectRatio Slot 적용 * fix: docs --- .changeset/mighty-buckets-drum.md | 5 ++ docs/docs/react/components/AspectRatio.mdx | 60 +++++++++++--- .../AspectRatio/AspectRatio.spec.tsx | 36 ++++++-- .../src/components/AspectRatio/index.tsx | 82 ++++++++++++------- 4 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 .changeset/mighty-buckets-drum.md diff --git a/.changeset/mighty-buckets-drum.md b/.changeset/mighty-buckets-drum.md new file mode 100644 index 000000000..3e3dd4d12 --- /dev/null +++ b/.changeset/mighty-buckets-drum.md @@ -0,0 +1,5 @@ +--- +'@modern-kit/react': minor +--- + +feat(react): AspectRatio Slot 적용 - @ssi0214 diff --git a/docs/docs/react/components/AspectRatio.mdx b/docs/docs/react/components/AspectRatio.mdx index 5e5ee760f..5017815be 100644 --- a/docs/docs/react/components/AspectRatio.mdx +++ b/docs/docs/react/components/AspectRatio.mdx @@ -3,7 +3,9 @@ import { AspectRatio } from '@modern-kit/react'; # AspectRatio -자식 요소의 가로, 세로 비율 **[aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)** 을 편리하게 맞춰 줄 수 있는 컴포넌트입니다. +주어진 **[aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)** 비율에 맞게 가로/세로 비율을 편리하게 맞춰주는 컴포넌트입니다. + +미리 영역을 확보하여 Layout Shift를 방지하는데 효과적입니다.
@@ -12,17 +14,19 @@ import { AspectRatio } from '@modern-kit/react'; ## Interface ```ts title="typescript" -interface AspectRatioProps extends ComponentProps<'div'> { - ratio: number; -} - -const AspectRatio: React.ForwardRefExoticComponent< - Omit, 'ref'> & - React.RefAttributes ->; +const AspectRatio: < + T extends React.ElementType = 'div' +>({ + asChild, + ratio, + children, + ...props +}: React.PropsWithChildren>) => JSX.Element; ``` ## Usage +### Basic +- 기본적으로 div 태그를 부모로 두고 자식 요소를 렌더링 하며, div 태그에 aspect-ratio 속성을 적용합니다. ```tsx title="typescript" import { AspectRatio } from '@modern-kit/react' @@ -37,9 +41,7 @@ const Example = () => { }; ``` -## Example - -export const Example = () => { +export const Example1 = () => { return (
@@ -49,4 +51,36 @@ export const Example = () => { ); }; - \ No newline at end of file + + +### asChild +- asChild 속성을 true로 설정하면 자식 요소에 aspect-ratio 속성을 적용합니다. +```tsx title="typescript" +import { AspectRatio } from '@modern-kit/react' + +const Example = () => { + return ( +
+ +
{/* section 태그에 aspect-ratio 속성이 적용됩니다. */} + +
+
+
+ ); +}; +``` + +export const Example2 = () => { + return ( +
+ +
+ +
+
+
+ ); +}; + + \ No newline at end of file diff --git a/packages/react/src/components/AspectRatio/AspectRatio.spec.tsx b/packages/react/src/components/AspectRatio/AspectRatio.spec.tsx index 7661635b4..1b474a521 100644 --- a/packages/react/src/components/AspectRatio/AspectRatio.spec.tsx +++ b/packages/react/src/components/AspectRatio/AspectRatio.spec.tsx @@ -1,23 +1,43 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { AspectRatio } from '.'; describe('AspectRatio component', () => { - it('should render correctly with the given ratio', () => { + it('기본적으로 div 태그를 부모 요소로 두고 자식 요소를 렌더링하며, div 태그에 aspect-ratio 설정이 적용되어야 합니다.', () => { + render( + +

Content

+
+ ); + + const wrapper = screen.getByRole('wrapper'); + + expect(wrapper).toHaveStyle( + 'padding-top: calc(100% * (1 / 1.7777777777777777))' + ); + expect(wrapper.tagName).toBe('DIV'); + }); + + it('asChild 속성이 true라면 aspect-ratio 설정이 자식 요소에 적용됩니다.', () => { const { container } = render( - -
Content
+ +
Content
); - const wrapper = container.firstChild as HTMLElement; + const divWrapper = container.querySelector('div'); + + expect(divWrapper).not.toBeInTheDocument(); + + const wrapper = screen.getByRole('wrapper'); expect(wrapper).toHaveStyle( 'padding-top: calc(100% * (1 / 1.7777777777777777))' ); + expect(wrapper.tagName).toBe('SECTION'); }); - it('should throw an error if more than one child is passed', () => { + it('하나 이상의 하위 요소가 있을 때 오류를 발생시켜야 합니다.', () => { const renderComponent = () => { render( @@ -27,8 +47,6 @@ describe('AspectRatio component', () => { ); }; - expect(renderComponent).toThrow( - 'AspectRatio component expects exactly one child element.' - ); + expect(renderComponent).toThrow(); }); }); diff --git a/packages/react/src/components/AspectRatio/index.tsx b/packages/react/src/components/AspectRatio/index.tsx index cc13fde92..ea04bbad3 100644 --- a/packages/react/src/components/AspectRatio/index.tsx +++ b/packages/react/src/components/AspectRatio/index.tsx @@ -1,42 +1,66 @@ -import { - CSSProperties, - Children, - ComponentProps, - PropsWithChildren, - forwardRef, - useMemo, -} from 'react'; import classNames from 'classnames'; import styles from './AspectRatio.module.css'; +import { Slot } from '../Slot'; +import React from 'react'; -interface AspectRatioProps extends ComponentProps<'div'> { - ratio: number; -} +type AspectRatioProps = + React.ComponentPropsWithoutRef & { + asChild?: boolean; + ratio: number; + }; -export const AspectRatio = forwardRef< - HTMLDivElement, - PropsWithChildren ->(({ ratio, children, className, style, ...props }, ref) => { - const customStyle: CSSProperties = useMemo( +/** + * @description 주어진 aspect-ratio 비율에 맞게 가로/세로 비율을 편리하게 맞춰주는 컴포넌트입니다. + * + * 미리 영역을 확보하여 Layout Shift를 방지하는데 효과적입니다. + * + * @template T - 사용할 HTML 요소의 타입을 지정합니다. 기본값은 'div'입니다. + * + * @param {PropsWithChildren>} aspectRatioProps - 컴포넌트에 전달되는 속성들입니다. + * @param {boolean} [aspectRatioProps.asChild=false] - `true`로 설정하면 `Slot` 컴포넌트를 사용해 자식 요소에 aspect-ratio 속성을 적용합니다. + * @param {number} aspectRatioProps.ratio - 자식 요소의 가로 세로 비율을 지정합니다. + * @param {React.ReactNode} aspectRatioProps.children - 렌더링 할 자식요소 입니다. + * @param {Object} [aspectRatioProps.props] - 기타 추가할 나머지 속성들입니다. + * + * @returns {JSX.Element} 주어진 비율에 맞춰 스타일이 적용된 래퍼 요소를 반환합니다. + * + * @example + * // 기본적으로 div를 생성 후 aspect-ratio 속성을 적용합니다. + * ```tsx + * + *

Content

+ *
+ * ``` + * + * @example + * // asChild 속성을 true로 설정하면 자식 요소에 aspect-ratio 속성을 적용합니다. + * ```tsx + * + *
Content
+ *
+ * ``` + */ +export const AspectRatio = ({ + asChild = false, + ratio, + children, + ...props +}: React.PropsWithChildren>): JSX.Element => { + const Wrapper = asChild ? Slot : 'div'; + const customStyle: React.CSSProperties = React.useMemo( () => ({ paddingTop: `calc(100% * (1 / ${ratio}))`, - ...style, + ...props.style, }), - [ratio, style] + [ratio, props.style] ); - if (Children.count(children) > 1) { - throw new Error('AspectRatio component expects exactly one child element.'); - } return ( -
- {Children.only(children)} -
+ {React.Children.only(children)} + ); -}); - -AspectRatio.displayName = 'AspectRatio'; +};