Skip to content

Commit

Permalink
fix: AspectRatio Slot 및 polymorphicForwardRef 활용을 통한 다형성 지원 (#609)
Browse files Browse the repository at this point in the history
* fix: AspectRatio Slot 및 polymorphicForwardRef 적용

* chore: changesetlog

* fix: 문서 수정

* docs: ts-doc 수정
  • Loading branch information
ssi02014 authored Nov 27, 2024
1 parent f12816e commit 7b98712
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 109 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-windows-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/react': minor
---

feat(react): AspectRatio Slot 및 polymorphicForwardRef 활용을 통한 다형성 지원 - @ssi02014
88 changes: 80 additions & 8 deletions docs/docs/react/components/AspectRatio.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ 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`를 방지하는데 효과적입니다.

다형성을 지원하기 때문에 `as`, `asChild` 속성을 지원합니다.

- 기본적으로 `div` 태그로 자식 요소를 감싸서 렌더링하며, `as` 속성을 통해 감싸는 요소를 특정 요소로 변경해 렌더링할 수 있습니다. 해당 요소에 `aspect-ratio` 속성을 적용해 영역을 확보합니다.
- `asChild` 속성이 `true`라면 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 을 통해 자식 요소를 그대로 렌더링하고, 자식 요소에 `aspect-ratio` 속성을 적용해 영역을 확보합니다.
- Slot은 자식으로 `단일 요소`만 허용됩니다.
- Slot은 자식으로 컴포넌트가 올 경우 `forwardRef`, `props`를 허용해야 합니다. 허용하지 않으면 정상적으로 동작하지 않습니다.
- `asChild` 속성을 사용 할 경우 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 문서를 참고해주세요.

<br />

## Code
Expand All @@ -17,36 +25,100 @@ import { AspectRatio } from '@modern-kit/react';
interface AspectRatioProps {
children: JSX.Element;
ratio: number;
asChild?: boolean;
style?: CSSProperties;
className?: string;
}
```
```tsx title="typescript"
const AspectRatio: ({ ratio, children }: AspectRatioProps) => JSX.Element;
const AspectRatio: PolyForwardComponent<"div", AspectRatioProps, React.ElementType>
```
## Usage
### Default
- 기본적으로 `div` 요소에 감싸지며 해당 `div``aspect-ratio` 속성을 적용해 영역을 확보합니다.
```tsx title="typescript"
import { AspectRatio } from '@modern-kit/react'

const Example = () => {
const imgUrl = 'https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9';

return (
<div style={{ width: '700px' }}>
<div style={{ width: '500px' }}>
<AspectRatio ratio={427 / 640}>
<img src="https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9" />
<img src={imgUrl} />
</AspectRatio>
</div>
);
};
```

export const Example1 = () => {
### as
- `as` 속성을 통해 감싸는 요소를 `div`가 아닌 특정 요소로 변경해 렌더링할 수 있으며, 해당 요소에 `aspect-ratio` 속성을 적용해 영역을 확보합니다.
```tsx title="typescript"
import { AspectRatio } from '@modern-kit/react'

const Example = () => {
const imgUrl = 'https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9';

return (
<div style={{ width: '700px' }}>
<div style={{ width: '500px' }}>
<AspectRatio as="article" ratio={427 / 640}>
<img src={imgUrl} />
</AspectRatio>
</div>
);
};
```

### asChild
- `asChild` 속성이 `true`라면 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 을 통해 자식 요소를 그대로 렌더링하고, 자식 요소에 `aspect-ratio` 속성을 적용해 영역을 확보합니다.
```tsx title="typescript"
import { AspectRatio } from '@modern-kit/react'

const Example = () => {
const imgUrl = 'https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9';

return (
<div style={{ width: '500px' }}>
<AspectRatio asChild ratio={427 / 640}>
<img src={imgUrl} style={{ width: '100%' }} />
</AspectRatio>
</div>
);
};
```

export const Example = () => {
const imgUrl = 'https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9';

return (
<div style={{ width: '500px' }}>
<h3>Default</h3>
<AspectRatio ratio={427 / 640}>
<img src="https://github.com/user-attachments/assets/dd60ec12-afd7-44c9-bd6b-0069e16bf2c9" />
<img src={imgUrl} />
</AspectRatio>

<br />

<h3>as article</h3>
<AspectRatio as="article" ratio={427 / 640}>
<img src={imgUrl} />
</AspectRatio>

<br />

<h3>asChild</h3>
<AspectRatio asChild ratio={427 / 640}>
<img src={imgUrl} style={{ width: '100%' }} />
</AspectRatio>
</div>
);
};

<Example1 />
## Example

<Example />



108 changes: 62 additions & 46 deletions docs/docs/react/components/InView.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import { InView } from '@modern-kit/react';

`InView``IntersectionObserver`를 선언적으로 활용 할 수 있는 컴포넌트입니다.

- `@modern-kit/react`**[useIntersectionObserver](https://modern-agile-team.github.io/modern-kit/docs/react/hooks/useIntersectionObserver)** 훅을 사용하여 구현되었습니다.

관찰 대상이 `Viewport`에 노출될 때(`onIntersectStart`) 혹은 나갈 때(`onIntersectEnd`) 특정 action 함수를 호출 할 수 있는 컴포넌트입니다.

다형성을 지원하기 때문에 `as` 속성을 통해 특정 요소로 렌더링할 수 있습니다.
다형성을 지원하기 때문에 `as`, `asChild` 속성을 지원합니다.

- 기본적으로 `div` 태그로 자식 요소를 감싸서 렌더링하며, `as` 속성을 통해 감싸는 요소를 특정 요소로 변경해 렌더링할 수 있습니다. 이때 `해당 요소가 관찰 대상`입니다.
- `asChild` 속성이 true라면 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 을 통해 래퍼 요소 없이 자식 요소를 그대로 렌더링하고, `자식 요소를 관찰 대상으로 설정`할 수 있습니다.
- Slot의 자식은 `단일 요소`만 허용됩니다.
- Slot의 자식으로 컴포넌트가 올 경우 `forwardRef`, `props`를 허용해야 합니다.
- `asChild` 속성을 사용 할 경우 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 문서를 참고해주세요.

`@modern-kit/react`**[useIntersectionObserver](https://modern-agile-team.github.io/modern-kit/docs/react/hooks/useIntersectionObserver)** 훅을 사용하여 구현되었습니다.

<br />

Expand All @@ -19,6 +26,7 @@ import { InView } from '@modern-kit/react';
```ts title="typescript"
interface InViewProps extends UseIntersectionObserverProps {
children: React.ReactNode;
asChild?: boolean;
}
```
```ts title="typescript"
Expand All @@ -33,53 +41,54 @@ const InView: PolyForwardComponent<"div", InViewProps, React.ElementType>
import { InView } from '@modern-kit/react';

const Example = () => {
const handleIntersectStart = () => {
/* action */
}
const handleIntersectStart = () => {/* action */}
const handleIntersectEnd = () => {/* action */}

const handleIntersectEnd = () => {
/* action */
}
return (
<InView onIntersectStart={handleIntersectStart} onIntersectEnd={handleIntersectEnd}>
<div>Box1</div>
</InView>
);
};
```

### as
- `as` 속성을 통해 특정 요소로 렌더링할 수 있으며, 해당 요소가 관찰 대상입니다.
- 해당 요소가 viewport에 노출되거나 숨겨질 때 `onIntersectStart/onIntersectEnd` 콜백 함수를 호출합니다.
```tsx title="typescript"
import { InView } from '@modern-kit/react';

const Example = () => {
const handleIntersectStart = () => {/* action */}
const handleIntersectEnd = () => {/* action */}

return (
<div>
<InView onIntersectStart={handleIntersectStart} onIntersectEnd={handleIntersectEnd}>
<div>Box1</div>
</InView>
</div>;
<InView
as='ul' // div가 아닌 ul 요소로 감싸 렌더링
onIntersectStart={handleIntersectStart}
onIntersectEnd={handleIntersectEnd}
>
<li>List Item1</li>
<li>List Item2</li>
</InView>
);
};
```

### asChild
- 자식 요소를 그대로 렌더링하고, 해당 요소를 관찰 대상으로 설정합니다.
- `asChild` 속성이 true라면 **[Slot](https://modern-agile-team.github.io/modern-kit/docs/react/components/Slot)** 을 통해 자식 요소를 그대로 렌더링하고, 해당 자식 요소를 관찰 대상으로 설정합니다.
- 자식 요소가 viewport에 노출되거나 숨겨질 때 `onIntersectStart/onIntersectEnd` 콜백 함수를 호출합니다.
- 이때 자식 요소는 단일 요소만 허용됩니다.
```tsx title="typescript"
import { InView } from '@modern-kit/react';

const Example = () => {
const ref = useRef<HTMLUListElement>(null);

const handleIntersectStart = () => {
/* action */
}

const handleIntersectEnd = () => {
/* action */
}
const handleIntersectStart = () => {/* action */}
const handleIntersectEnd = () => {/* action */}

return (
<div>
<InView
as='ul'
onIntersectStart={handleIntersectStart}
onIntersectEnd={handleIntersectEnd}
>
<li>List Item1</li>
<li>List Item2</li>
</InView>
</div>;
<InView asChild onIntersectStart={handleIntersectStart} onIntersectEnd={handleIntersectEnd}>
<div>Box1</div>
</InView>
);
};
```
Expand All @@ -100,38 +109,45 @@ export const Example = () => {
style={{
height: '500px',
textAlign: 'center',
fontSize: '2rem',
fontSize: '1.7rem',
}}>
스크롤 해주세요.
<p>스크롤 해주세요.</p>
<p>브라우저 개발자 도구의 콘솔을 확인해주세요.</p>
</div>

<InView
onIntersectStart={() => console.log('action onIntersectStart(1)')}
onIntersectEnd={() => console.log('action onIntersectEnd(1)')}
style={{
...inViewStyle,
background: '#c0392B',
}}
style={{ ...inViewStyle, background: '#c0392B' }}
calledOnce
>
<p>Box1</p>
<p>브라우저 개발자 도구의 콘솔을 확인해주세요.</p>
<p>calledOnce: true</p>
<p>as: div</p>
</InView>
<div style={{ height: '300px' }} />
<InView
onIntersectStart={() => console.log('action onIntersectStart(2)')}
onIntersectEnd={() => console.log('action onIntersectEnd(2)')}
style={{
...inViewStyle,
background: '#89a5ea',
}}
style={{ ...inViewStyle, background: '#89a5ea' }}
>
<li>Box2</li>
<li>브라우저 개발자 도구의 콘솔을 확인해주세요.</li>
<li>calledOnce: false</li>
<li>as: ul</li>
</InView>
<div style={{ height: '300px' }} />
<InView
asChild
onIntersectStart={() => console.log('action onIntersectStart(3)')}
onIntersectEnd={() => console.log('action onIntersectEnd(3)')}
style={{ ...inViewStyle, background: '#77DD77' }}
>
<div>
<p>Box3</p>
<p>calledOnce: false</p>
<p>asChild: true</p>
</div>
</InView>
<div style={{ width: '100%', height: '900px', textAlign: 'center' }} />
</div>
);
Expand Down
56 changes: 52 additions & 4 deletions docs/docs/react/components/Slot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,53 @@ import { Slot, Slottable } from '@modern-kit/react'

# Slot

주어진 Props를 직계 자식 컴포넌트에 병합하는 컴포넌트입니다.
주어진 Props를 직계 자식 컴포넌트에 병합하고, 자식 컴포넌트를 렌더링하는 컴포넌트입니다.

Slot은 부모 컴포넌트의 기능을 자식 컴포넌트에 전달하는 합성 패턴을 구현합니다. 이를 통해:

- 부모 컴포넌트의 `props`, `ref`, `이벤트 핸들러` 등을 자식 컴포넌트에 전달할 수 있습니다
- 자식 컴포넌트의 구현을 변경하지 않고도 새로운 기능을 추가할 수 있습니다
- 컴포넌트 간의 결합도를 낮추고 재사용성을 높일 수 있습니다

예를 들어 **[InView](https://modern-agile-team.github.io/modern-kit/docs/react/components/InView)****[AspectRatio](https://modern-agile-team.github.io/modern-kit/docs/react/components/AspectRatio)** 와 같은 컴포넌트에서 `asChild` prop을 사용해서, 래퍼 요소 없이 자식 컴포넌트에 직접 기능을 주입할 수 있습니다.

Slot은 아래와 같은 특징이 있습니다.
1. 자식 요소로 `단일 요소`만 허용됩니다.
```tsx title="typescript"
// 가능
<Slot>
<div>Contents</div>
</Slot>

// 가능
<Slot>
<div>
<div>Contents1</div>
<div>Contents2</div>
</div>
</Slot>
```

```tsx title="typescript"
// 불가능
<Slot>
<div>Contents1</div>
<div>Contents2</div>
</Slot>
```

2. 자식 요소로 컴포넌트가 온다면 해당 컴포넌트는 필수적으로 `forwardRef`, `props`를 허용해야 합니다. 허용하지 않으면 기능이 정상적으로 동작하지 않습니다.
- **[radix-ui#your-component-must-spread-props](https://www.radix-ui.com/primitives/docs/guides/composition#your-component-must-spread-props)**
- **[radix-ui#your-component-must-forward-ref](https://www.radix-ui.com/primitives/docs/guides/composition#your-component-must-forward-ref)**
```tsx title="typescript"
const MyButton = React.forwardRef((props, forwardedRef) => (
<button {...props} ref={forwardedRef} />
));

<Slot>
<MyButton>Button</MyButton>
</Slot>
```

<br />

Expand All @@ -22,10 +68,10 @@ const Slottable: ({ children }: React.PropsWithChildren) => JSX.Element;
```

## Usage
### Default
```tsx title="typescript"
import { Slot } from '@modern-kit/react'

// Basic
const Button = ({
asChild,
...props
Expand All @@ -35,8 +81,9 @@ const Button = ({
return <Comp {...props} />;
};
```

### With Slottable
```tsx title='typescript'
// with Slottable
const SlottableButton = ({
asChild,
leftElement,
Expand All @@ -61,7 +108,8 @@ const SlottableButton = ({
};
```

### Basic
## Example
### Default
```tsx title="typescript"
// Basic Usage
<Button onClick={() => console.log('click')}>Button</Button>
Expand Down
Loading

0 comments on commit 7b98712

Please sign in to comment.