Skip to content

Commit

Permalink
fix(react): useScrollTo align 옵션 추가 (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssi02014 committed Jul 29, 2024
1 parent c8cbde2 commit 3c46b27
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-cows-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/react': patch
---

fix(react): useScrollTo align 옵션 추가 - @ssi02014
112 changes: 89 additions & 23 deletions docs/docs/react/hooks/useScrollTo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const { containerRef } = useScrollTo<HTMLDivElement>();
## Interface
```ts title="typescript"
type ScrollBehavior = "auto" | "instant" | "smooth";
type Align = 'start' | 'center' | 'end';

interface ScrollOptions {
behavior?: ScrollBehavior; // default: 'auto'
Expand All @@ -45,6 +46,8 @@ interface ScrollToElementOptions {
offsetX?: number; // default: 0
offsetY?: number; // default: 0
behavior?: ScrollBehavior; // default: 'auto'
alignY?: Align; // default: 'start'
alignX?: Align; // default: 'start'
}
```
```ts title="typescript"
Expand Down Expand Up @@ -80,40 +83,72 @@ const Example = () => {
scrollToPosition({
behavior: 'smooth',
top: 400,
})
}
});
};

const handleScrollToElement = () => {
scrollToElement(targetRef.current, {
behavior: 'smooth',
});
};

const handleScrollToElementOffset = () => {
const handleScrollToElementOffset = (offset: number) => {
scrollToElement(targetRef.current, {
behavior: 'smooth',
offsetY: 200,
offsetY: offset,
});
};

const handleScrollToElementAlign = (type: Align) => {
scrollToElement(targetRef.current, {
behavior: 'smooth',
alignY: type,
});
};
return (
<>
<button onClick={handleScrollToPosition}>포지션 스크롤</button>
<button onClick={handleScrollToElement}>타겟 요소 스크롤</button>
<button onClick={handleScrollToElementOffset}>타겟 요소 스크롤 offset200</button>
<div>
<div>
<button onClick={handleScrollToPosition}>포지션 스크롤</button>
<button onClick={handleScrollToElement}>타겟 요소 스크롤</button>
<button onClick={() => handleScrollToElementOffset(200)}>
타겟 요소 스크롤 offset 200
</button>
<button onClick={() => handleScrollToElementOffset(-200)}>
타겟 요소 스크롤 offset -200
</button>
</div>

<div ref={containerRef} style={{ width:'500px', height: '400px', overflow: 'scroll' }}>
<div style={{ height: '400px', background: 'green' }}>InnerBox1</dix>
<div style={{ height: '400px', background: 'red' }}>InnerBox2</dix>
<div>
<button onClick={() => handleScrollToElementAlign('start')}>
타겟 요소 스크롤 align start
</button>
<button onClick={() => handleScrollToElementAlign('center')}>
타겟 요소 스크롤 align center
</button>
<button onClick={() => handleScrollToElementAlign('end')}>
타겟 요소 스크롤 align end
</button>
</div>

<div ref={targetRef} style={{ height: '400px', background: 'blue' }}>InnerBox3</dix>
<div
ref={containerRef}
style={{ width: '500px', height: '800px', overflow: 'scroll' }}>
<div style={{ height: '400px', background: 'green' }}>InnerBox1</div>
<div style={{ height: '400px', background: 'red' }}>InnerBox2</div>
<div ref={targetRef} style={{ height: '400px', background: 'coral', fontSize: '24px' }}>
target Box
</div>
<div style={{ height: '400px', background: 'aqua' }}>InnerBox4</div>
<div style={{ height: '400px', background: 'black' }}>InnerBox4</div>
</div>
</>
</div>
);
};

```

## Example

export const Example = () => {
const { containerRef, scrollToPosition, scrollToElement } = useScrollTo();
const targetRef = useRef(null);
Expand All @@ -128,26 +163,57 @@ export const Example = () => {
behavior: 'smooth',
});
};
const handleScrollToElementOffset = () => {
const handleScrollToElementOffset = (offset) => {
scrollToElement(targetRef.current, {
behavior: 'smooth',
offsetY: 200,
offsetY: offset,
});
};
const handleScrollToElementAlign = (type) => {
scrollToElement(targetRef.current, {
behavior: 'smooth',
alignY: type,
});
};
return (
<>
<button onClick={handleScrollToPosition}>포지션 스크롤</button>
<button onClick={handleScrollToElement}>타겟 요소 스크롤</button>
<button onClick={handleScrollToElementOffset}>타겟 요소 스크롤 offset200</button>
<div>
<div>
<button onClick={handleScrollToPosition}>포지션 스크롤</button>
<button onClick={handleScrollToElement}>타겟 요소 스크롤</button>
<button onClick={() => handleScrollToElementOffset(200)}>
타겟 요소 스크롤 offset 200
</button>
<button onClick={() => handleScrollToElementOffset(-200)}>
타겟 요소 스크롤 offset -200
</button>
</div>

<div ref={containerRef} style={{ width:'500px', height: '400px', overflow: 'scroll' }}>
<div>
<button onClick={() => handleScrollToElementAlign('start')}>
타겟 요소 스크롤 align start
</button>
<button onClick={() => handleScrollToElementAlign('center')}>
타겟 요소 스크롤 align center
</button>
<button onClick={() => handleScrollToElementAlign('end')}>
타겟 요소 스크롤 align end
</button>
</div>

<div
ref={containerRef}
style={{ width: '500px', height: '800px', overflow: 'scroll' }}>
<div style={{ height: '400px', background: 'green' }}>InnerBox1</div>
<div style={{ height: '400px', background: 'red' }}>InnerBox2</div>
<div ref={targetRef} style={{ height: '400px', background: 'blue' }}>InnerBox3</div>
<div ref={targetRef} style={{ height: '400px', background: 'coral', fontSize: '24px' }}>
target Box
</div>
<div style={{ height: '400px', background: 'aqua' }}>InnerBox4</div>
<div style={{ height: '400px', background: 'black' }}>InnerBox4</div>
</div>
</>
</div>
);
};


<Example />
4 changes: 2 additions & 2 deletions packages/react/src/hooks/useScrollTo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ScrollToElementOptions, getRelativePosition } from './internal';
* @example
* // scrollToElement를 사용하면 인자로 넣은 엘리먼트를 기준으로 이동합니다.
* const { containerRef, scrollToElement } = useScrollTo();
* scrollToElement(target.current, { offsetX, offsetY });
* scrollToElement(target.current, { offsetX, offsetY, alignY, alignX, behavior });
*/
export function useScrollTo(): {
containerRef: React.MutableRefObject<Window | null>;
Expand Down Expand Up @@ -61,7 +61,7 @@ export function useScrollTo<T extends HTMLElement>() {
target: E,
scrollToElementOptions: ScrollToElementOptions = {}
) => {
if (!containerRef.current) return;
if (!target || !containerRef.current) return;

const scrollElement = containerRef.current;
const { behavior = 'auto' } = scrollToElementOptions;
Expand Down
61 changes: 51 additions & 10 deletions packages/react/src/hooks/useScrollTo/internal.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,73 @@
import { isWindow } from '@modern-kit/utils';

type Align = 'start' | 'center' | 'end';

export interface ScrollToElementOptions {
offsetX?: number;
offsetY?: number;
behavior?: ScrollBehavior;
alignY?: Align; // 수직
alignX?: Align; // 수평
}

const getAlignPosition = (
containerDimension: number,
targetDimension: number,
align: Align
) => {
if (align === 'start') {
return 0;
}
if (align === 'center') {
return -containerDimension / 2 + targetDimension / 2;
}
return -containerDimension + targetDimension;
};

/**
* @description parent가 window인지 특정 요소인지에 child의 위치 값을 가져오는 함수
*/
export const getRelativePosition = <T extends HTMLElement>(
parent: T | Window,
child: HTMLElement,
container: T | Window,
target: HTMLElement,
scrollToOptions: ScrollToElementOptions
) => {
const childRect = child.getBoundingClientRect();
const { offsetX = 0, offsetY = 0 } = scrollToOptions;
const targetRect = target.getBoundingClientRect();
const {
offsetX = 0,
offsetY = 0,
alignY = 'start',
alignX = 'start',
} = scrollToOptions;

if (isWindow(parent)) {
if (isWindow(container)) {
return {
top: childRect.top + window.scrollY + offsetY,
left: childRect.left + window.scrollX + offsetX,
top:
targetRect.top +
window.scrollY +
offsetY +
getAlignPosition(window.innerHeight, targetRect.height, alignY),
left:
targetRect.left +
window.scrollX +
offsetX +
getAlignPosition(window.innerWidth, targetRect.width, alignX),
};
}
const parentRect = parent.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();

return {
top: childRect.top - parentRect.top + parent.scrollTop + offsetY,
left: childRect.left - parentRect.left + parent.scrollLeft + offsetX,
top:
targetRect.top -
containerRect.top +
container.scrollTop +
offsetY +
getAlignPosition(containerRect.height, targetRect.height, alignY),
left:
targetRect.left -
containerRect.left +
container.scrollLeft +
offsetX +
getAlignPosition(containerRect.width, targetRect.width, alignX),
};
};
6 changes: 5 additions & 1 deletion packages/react/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export default defineConfig({
setupFiles: './vitest.setup.ts',
coverage: {
provider: 'istanbul',
exclude: ['src/utils/test/**', 'src/hooks/useClipboard'],
exclude: [
'src/utils/test/**',
'src/**/internal.ts',
'src/hooks/useClipboard',
],
},
},
});

0 comments on commit 3c46b27

Please sign in to comment.