-
Notifications
You must be signed in to change notification settings - Fork 3
상태바 컴포넌트 개발 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
상태바 컴포넌트 개발 #23
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| .track { | ||
| width: 100%; | ||
| max-width: 322px; | ||
| height: 20px; | ||
|
|
||
| border-radius: 9999px; | ||
| overflow: hidden; | ||
| position: relative; | ||
|
|
||
| background: | ||
| repeating-linear-gradient( | ||
| -45deg, | ||
| rgba(0, 0, 0, 0.04) 0px, | ||
| rgba(0, 0, 0, 0.04) 40px, | ||
| rgba(255, 255, 255, 0.7) 40px, | ||
| rgba(255, 255, 255, 0.7) 80px | ||
| ), | ||
| var(--color-brand-secondary); | ||
| } | ||
|
|
||
| .fill { | ||
| height: 100%; | ||
| border-radius: inherit; | ||
| background: var(--color-brand-primary); | ||
|
|
||
| transition: width 800ms ease; | ||
| will-change: width; | ||
| } | ||
|
|
||
| @media (min-width: 744px) { | ||
| .track { | ||
| max-width: 566px; | ||
| height: 27px; | ||
| } | ||
| } | ||
|
|
||
| @media (min-width: 1024px) { | ||
| .track { | ||
| max-width: 1010px; | ||
| height: 27px; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| 'use client'; | ||
|
|
||
| import { useEffect, useMemo, useRef } from 'react'; | ||
| import styles from './ProgressBar.module.css'; | ||
|
|
||
| export type ProgressBarProps = { | ||
| /** 0~1 (0.3 = 30%) */ | ||
| value?: number; | ||
| /** done/total로도 계산 가능 */ | ||
| done?: number; | ||
| total?: number; | ||
|
|
||
| /** 애니메이션 (default: true) */ | ||
| animate?: boolean; | ||
|
|
||
| replayOnMount?: boolean; | ||
|
|
||
| durationMs?: number; | ||
|
|
||
| ariaLabel?: string; | ||
| className?: string; | ||
| }; | ||
|
|
||
| function clamp01(n: number): number { | ||
| if (Number.isNaN(n)) return 0; | ||
| return Math.max(0, Math.min(1, n)); | ||
| } | ||
|
|
||
| export default function ProgressBar({ | ||
| value, | ||
| done, | ||
| total, | ||
| animate = true, | ||
| replayOnMount = true, | ||
| durationMs = 800, | ||
| ariaLabel = 'progress', | ||
| className, | ||
| }: ProgressBarProps) { | ||
| const ratio = useMemo(() => { | ||
| if (typeof value === 'number') return clamp01(value); | ||
| if (typeof done === 'number' && typeof total === 'number' && total > 0) { | ||
| return clamp01(done / total); | ||
| } | ||
| return 0; | ||
| }, [value, done, total]); | ||
|
|
||
| const targetPercent = useMemo(() => ratio * 100, [ratio]); | ||
|
|
||
| const fillRef = useRef<HTMLDivElement | null>(null); | ||
| const didMountRef = useRef<boolean>(false); | ||
| const rafRef = useRef<number | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| const el = fillRef.current; | ||
| if (!el) return; | ||
|
|
||
| el.style.transition = animate ? `width ${durationMs}ms ease` : 'none'; | ||
|
|
||
| if (!animate) { | ||
| el.style.width = `${targetPercent}%`; | ||
| didMountRef.current = true; | ||
| return; | ||
| } | ||
|
|
||
| const isFirstMount = !didMountRef.current; | ||
| didMountRef.current = true; | ||
|
|
||
| if (replayOnMount && isFirstMount) { | ||
| el.style.width = '0%'; | ||
|
|
||
| if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); | ||
| rafRef.current = requestAnimationFrame(() => { | ||
| el.style.width = `${targetPercent}%`; | ||
| }); | ||
|
|
||
| return () => { | ||
| if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); | ||
| }; | ||
| } | ||
|
|
||
| el.style.width = `${targetPercent}%`; | ||
| }, [animate, durationMs, replayOnMount, targetPercent]); | ||
|
Comment on lines
+53
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 예를 들어, // 예시
useEffect(() => {
const el = fillRef.current;
if (!el) return;
el.style.setProperty('--pb-duration', animate ? `${durationMs}ms` : '0ms');
}, [animate, durationMs]);
useEffect(() => {
const el = fillRef.current;
if (!el) return;
// 너비 업데이트 및 애니메이션 로직
// ...
}, [animate, replayOnMount, targetPercent]); |
||
|
|
||
| return ( | ||
| <div | ||
| className={`${styles.track} ${className ?? ''}`} | ||
| role="progressbar" | ||
| aria-label={ariaLabel} | ||
| aria-valuemin={0} | ||
| aria-valuemax={100} | ||
| aria-valuenow={Math.round(targetPercent)} | ||
| > | ||
| <div | ||
| ref={fillRef} | ||
| className={styles.fill} | ||
| style={{ width: animate && replayOnMount ? '0%' : `${targetPercent}%` }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { default } from './ProgressBar'; | ||
| export type { ProgressBarProps } from './ProgressBar'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repeating-linear-gradient의color-stop순서가 올바르지 않아 그래디언트가 렌더링되지 않는 버그가 있습니다.color-stop의 위치 값은 오름차순으로 정렬되어야 합니다. 현재 코드에서는40px다음에10px가 나와서 유효하지 않은 구문입니다.의도하신 디자인이 줄무늬 패턴이라고 추측하여 아래와 같이 수정하는 것을 제안합니다. 이 코드는 40px 너비의 어두운 줄과 40px 너비의 밝은 줄이 반복되는 패턴을 만듭니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정 완료