Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8f977ca
design: 볼륨 아이콘 추가
i-meant-to-be Nov 29, 2025
62f7173
feat: 기존 훅에 벨 소리 볼륨 관련 로직 추가
i-meant-to-be Nov 29, 2025
4258ddf
refactor: 헤더에 z-index 부여
i-meant-to-be Nov 30, 2025
75191a0
feat: 슬라이더 구현
i-meant-to-be Nov 30, 2025
55384e5
feat: 볼륨 바 구현
i-meant-to-be Nov 30, 2025
7d39632
feat: 타이머 페이지에 볼륨 바 적용
i-meant-to-be Nov 30, 2025
b798e5a
test: 슬라이더 Storybook 추가
i-meant-to-be Nov 30, 2025
a32bd10
refactor: 최소, 최대 볼륨 및 간격 상수화
i-meant-to-be Nov 30, 2025
2ca7770
feat: 음소거 버튼 로직 구현
i-meant-to-be Nov 30, 2025
b873bab
feat: 볼륨 설정이 로컬 저장소에 저장되도록 구현
i-meant-to-be Nov 30, 2025
954e6d4
fix: CodeRabbit 리뷰 반영
i-meant-to-be Dec 1, 2025
5abd764
refactor: 접근성 옵션 추가
i-meant-to-be Dec 1, 2025
abc28ed
refactor: 외부에서 볼륨 변경 시 동기화하는 로직 추가
i-meant-to-be Dec 1, 2025
3deed5e
chore: 주석 수정
i-meant-to-be Dec 1, 2025
bbf487f
fix: z-index 일부 수정
i-meant-to-be Dec 2, 2025
a468f3f
refactor: 볼륨 값을 안전하게 갱신하는 함수 추가
i-meant-to-be Dec 23, 2025
6ef17e6
refactor: 볼륨 값 초기화 함수 분리
i-meant-to-be Dec 23, 2025
df049c4
chore: 이해를 위한 주석 추가
i-meant-to-be Dec 23, 2025
37204f8
refactor: 코드 리뷰 반영
i-meant-to-be Dec 23, 2025
1543f45
feat: 볼륨 바 바깥 클릭 시 닫히도록 개선
i-meant-to-be Dec 23, 2025
917886c
fix: 버그 수정 및 볼륨 기본값 상수로 분리
i-meant-to-be Dec 23, 2025
d724926
chore: 사소한 변경 사항
i-meant-to-be Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/components/CustomRangeSlider/CustomRangeSlider.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Storybook 코드
import { Meta, StoryObj } from '@storybook/react';
import CustomRangeSlider from './CustomRangeSlider';

const meta: Meta<typeof CustomRangeSlider> = {
title: 'Components/CustomRangeSlider',
component: CustomRangeSlider,
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof CustomRangeSlider>;

export const Default: Story = {
args: {
value: 5,
max: 10,
min: 0,
onValueChange: (value: number) => {
console.log(value);
},
},
};
72 changes: 72 additions & 0 deletions src/components/CustomRangeSlider/CustomRangeSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState } from 'react';

interface CustomRangeSliderProps {
// Controlled props
value: number;
onValueChange: (value: number) => void;

// Configuration
min?: number;
max?: number;
step?: number;
}

export default function CustomRangeSlider({
value,
onValueChange,
min = 0,
max = 100,
step = 1,
}: CustomRangeSliderProps) {
const [showTooltip, setShowTooltip] = useState(false);
const range = max - min;
const percentage =
range <= 0 ? 0 : Math.min(100, Math.max(0, ((value - min) / range) * 100));

return (
<div className="relative mx-4 flex h-6 w-full items-center">
{/* 스타일링 레이어 */}
{/* 1. 전체 트랙 (배경) */}
<div className={`absolute h-[8px] w-full rounded-full bg-brand/40`} />

{/* 2. 현재 진행도 트랙 */}
<div
className={`absolute h-[8px] rounded-full bg-brand transition-all`}
style={{ width: `${percentage}%` }}
/>

{/* 3. Thumb (원형 드래그 핸들) */}
<div
className={`pointer-events-none absolute top-1/2 size-[12px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-brand shadow-md transition-all hover:scale-105`}
title={value.toString()}
style={{ left: `${percentage}%` }}
>
{/* 볼륨 수치로 나타내는 말풍선 */}
{showTooltip && (
<div className="absolute -top-8 left-1/2 -translate-x-1/2 transform">
<div className="relative rounded bg-gray-800 px-2 py-1 text-xs text-white shadow-lg">
{value}
{/* 말풍선 꼬리 */}
<div className="absolute -bottom-1 left-1/2 -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
</div>
</div>
)}
</div>

{/* 실제 상호작용 레이어 */}
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={(e) => onValueChange(Number(e.target.value))}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onFocus={() => setShowTooltip(true)}
onBlur={() => setShowTooltip(false)}
className="absolute inset-0 z-10 h-full w-full cursor-pointer opacity-0"
/>
</div>
);
}
19 changes: 19 additions & 0 deletions src/components/VolumeBar/VolumeBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta, StoryObj } from '@storybook/react';
import VolumeBar from './VolumeBar';

const meta: Meta<typeof VolumeBar> = {
title: 'Components/VolumeBar',
component: VolumeBar,
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof VolumeBar>;

export const Default: Story = {
args: {
volume: 0,
onVolumeChange: (volume: number) => console.log(volume),
},
};
143 changes: 143 additions & 0 deletions src/components/VolumeBar/VolumeBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Integer 0-10, step = 1
// Mute button available
import { useEffect, useState } from 'react';
import CustomRangeSlider from '../CustomRangeSlider/CustomRangeSlider';
import DTVolume from '../icons/Volume';
import clsx from 'clsx';

interface VolumeBarProps {
volume: number;
onVolumeChange: (volume: number) => void;
className?: string;
}

const MIN_VOLUME = 0;
const MAX_VOLUME = 10;
const STEP_VOLUME = 1;

export default function VolumeBar({
volume,
onVolumeChange,
className = '',
}: VolumeBarProps) {
// 음소거 해제 시 가장 마지막의 볼륨 값을 복원하기 위함
const [lastVolume, setLastVolume] = useState(volume > 0 ? volume : 5);

// 음소거 로직
const handleMute = () => {
if (volume === 0) {
onVolumeChange(lastVolume === 0 ? 1 : lastVolume);
} else {
setLastVolume(volume);
onVolumeChange(0);
}
};

// 음소거 버튼은 오직 볼륨이 0일 때에만 흐리게 강조됨
const isNotMute = volume > 0;

// 외부에서 볼륨이 변경될 경우, 값을 관측하여 동기화
useEffect(() => {
if (volume > 0) {
setLastVolume(volume);
}
}, [volume]);

return (
<div className={`relative h-[76px] w-[234px] ${className}`}>
{/* SVG Layer */}
<svg
className="pointer-events-none absolute inset-0 h-full w-full"
viewBox="0 0 234 76"
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
>
<g filter="url(#filter0_d_2434_13098)">
<mask id="path-1-inside-1_2434_13098" fill="white">
<path d="M164.025 18.1911C164.386 18.7925 165.037 19.1603 165.738 19.1603H227C228.105 19.1603 229 20.0558 229 21.1603V65.1662C229 66.2708 228.105 67.1662 227 67.1662H7.00001C5.89544 67.1662 5 66.2708 5 65.1662V21.1603C5 20.0558 5.89543 19.1603 7 19.1603H140.574C141.276 19.1603 141.926 18.7925 142.288 18.1911L151.442 2.96925C152.22 1.67692 154.093 1.67692 154.87 2.96925L164.025 18.1911Z" />
</mask>
<path
d="M164.025 18.1911C164.386 18.7925 165.037 19.1603 165.738 19.1603H227C228.105 19.1603 229 20.0558 229 21.1603V65.1662C229 66.2708 228.105 67.1662 227 67.1662H7.00001C5.89544 67.1662 5 66.2708 5 65.1662V21.1603C5 20.0558 5.89543 19.1603 7 19.1603H140.574C141.276 19.1603 141.926 18.7925 142.288 18.1911L151.442 2.96925C152.22 1.67692 154.093 1.67692 154.87 2.96925L164.025 18.1911Z"
fill="white"
/>
<path
d="M151.442 2.96925L150.757 2.55695L151.442 2.96925ZM154.87 2.96925L155.556 2.55695L154.87 2.96925ZM142.288 18.1911L142.974 18.6034L142.288 18.1911ZM164.025 18.1911L163.339 18.6034L164.025 18.1911ZM165.738 19.1603V19.9603H227V19.1603V18.3603H165.738V19.1603ZM229 21.1603H228.2V65.1662H229H229.8V21.1603H229ZM227 67.1662V66.3662H7.00001V67.1662V67.9662H227V67.1662ZM5 65.1662H5.8V21.1603H5H4.2V65.1662H5ZM7 19.1603V19.9603H140.574V19.1603V18.3603H7V19.1603ZM142.288 18.1911L142.974 18.6034L152.128 3.38155L151.442 2.96925L150.757 2.55695L141.602 17.7788L142.288 18.1911ZM154.87 2.96925L154.185 3.38155L163.339 18.6034L164.025 18.1911L164.71 17.7788L155.556 2.55695L154.87 2.96925ZM151.442 2.96925L152.128 3.38155C152.594 2.60615 153.718 2.60615 154.185 3.38155L154.87 2.96925L155.556 2.55695C154.468 0.747682 151.845 0.747684 150.757 2.55695L151.442 2.96925ZM140.574 19.1603V19.9603C141.557 19.9603 142.467 19.4454 142.974 18.6034L142.288 18.1911L141.602 17.7788C141.385 18.1396 140.995 18.3603 140.574 18.3603V19.1603ZM5 21.1603H5.8C5.8 20.4976 6.33726 19.9603 7 19.9603V19.1603V18.3603C5.4536 18.3603 4.2 19.6139 4.2 21.1603H5ZM7.00001 67.1662V66.3662C6.33726 66.3662 5.8 65.8289 5.8 65.1662H5H4.2C4.2 66.7126 5.45361 67.9662 7.00001 67.9662V67.1662ZM229 65.1662H228.2C228.2 65.8289 227.663 66.3662 227 66.3662V67.1662V67.9662C228.546 67.9662 229.8 66.7126 229.8 65.1662H229ZM227 19.1603V19.9603C227.663 19.9603 228.2 20.4976 228.2 21.1603H229H229.8C229.8 19.6139 228.546 18.3603 227 18.3603V19.1603ZM165.738 19.1603V18.3603C165.317 18.3603 164.927 18.1396 164.71 17.7788L164.025 18.1911L163.339 18.6034C163.845 19.4454 164.756 19.9603 165.738 19.9603V19.1603Z"
fill="#D6D7D9"
mask="url(#path-1-inside-1_2434_13098)"
/>
</g>
<defs>
<filter
id="filter0_d_2434_13098"
x="0"
y="0"
width="234"
height="76"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="3" />
<feGaussianBlur stdDeviation="2.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_2434_13098"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_2434_13098"
result="shape"
/>
</filter>
</defs>
</svg>

{/* Content Layer */}
<div className="relative z-10 flex h-full w-full items-center justify-center px-4 pb-2 pt-5">
<div className="flex w-full flex-row items-center gap-2">
<button
onClick={handleMute}
className="size-[36px]"
title={isNotMute ? '음소거' : '음소거 해제'}
aria-label={isNotMute ? '음소거' : '음소거 해제'}
>
<DTVolume
className={clsx('size-full', {
'text-default-black': isNotMute,
'text-default-disabled/hover': !isNotMute,
})}
/>
</button>
<CustomRangeSlider
value={volume}
onValueChange={(value: number) => {
onVolumeChange(value);

// 마지막 볼륨이 0으로 저장되면, 음소거를 해제해도 음소거가 유지되는 버그를 피하기 위함
if (value > 0) {
setLastVolume(value);
}
}}
min={MIN_VOLUME}
max={MAX_VOLUME}
step={STEP_VOLUME}
/>
</div>
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions src/components/icons/Icon.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DTReset from './Reset';
import DTShare from './Share';
import DTExchange from './Exchange';
import DTBell from './Bell';
import DTVolume from './Volume';

const meta: Meta<typeof DTLogin> = {
title: 'Design System/Icons',
Expand Down Expand Up @@ -256,3 +257,15 @@ export const OnBell: Story = {
</div>
),
};

export const OnVolume: Story = {
args: {
color: '#FECD4C',
},
render: (args) => (
<div className="bg-neutral-white flex flex-col items-center rounded border p-4 shadow-sm">
<DTVolume className="size-16" {...args} />
<p className="mt-2 text-sm text-gray-600">볼륨 조절</p>
</div>
),
};
22 changes: 22 additions & 0 deletions src/components/icons/Volume.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IconProps } from './IconProps';

export default function DTVolume({
color = 'currentColor',
className = '',
...props
}: IconProps) {
return (
<svg
viewBox="0 0 29 25"
fill="none"
className={`aspect-[29/25] ${className}`}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M13.7139 0.295898C14.2709 -0.312858 15.2852 0.0811005 15.2852 0.90625V23.1699C15.285 23.9947 14.271 24.3892 13.7139 23.7812L7.0918 16.5439H2.26074C1.01222 16.5439 9.79401e-05 15.5317 0 14.2832V9.7959C4.62319e-05 8.54735 1.01219 7.5352 2.26074 7.53516H7.08887L13.7139 0.295898ZM24.9521 0.728516L24.9541 0.730469C24.9552 0.731945 24.9565 0.734391 24.958 0.736328C24.9611 0.74036 24.9651 0.745802 24.9697 0.751953C24.9794 0.764721 24.9927 0.782049 25.0088 0.803711C25.0414 0.847724 25.0874 0.910254 25.1436 0.989258C25.2561 1.14762 25.4128 1.3755 25.5986 1.66309C25.9697 2.23739 26.4614 3.05908 26.9531 4.0625C27.9269 6.04997 28.9522 8.85294 28.9561 11.9082C28.9599 15.0079 27.9379 17.8718 26.9648 19.9092C26.4735 20.938 25.9825 21.7827 25.6113 22.373C25.4257 22.6684 25.2687 22.9017 25.1562 23.0645C25.1001 23.1456 25.054 23.2097 25.0215 23.2549C25.0054 23.2773 24.992 23.2955 24.9824 23.3086C24.9777 23.3151 24.9738 23.321 24.9707 23.3252C24.9693 23.3271 24.9678 23.3287 24.9668 23.3301L24.9658 23.332L24.9648 23.333L22.5 21.4951C22.5007 21.4942 22.5015 21.4922 22.5029 21.4902C22.5073 21.4843 22.5157 21.4737 22.5264 21.459C22.548 21.429 22.5815 21.3807 22.626 21.3164C22.7154 21.187 22.8477 20.9909 23.0078 20.7363C23.3288 20.2258 23.7596 19.486 24.1904 18.584C25.062 16.7591 25.8839 14.3707 25.8809 11.9121C25.8778 9.50748 25.0597 7.18628 24.1924 5.41602C23.7637 4.54113 23.3355 3.82456 23.0166 3.33105C22.8577 3.08521 22.7263 2.89614 22.6377 2.77148C22.5935 2.70936 22.5594 2.66252 22.5381 2.63379C22.5277 2.61983 22.5198 2.61004 22.5156 2.60449L22.5137 2.59961C22.523 2.59284 22.6244 2.51588 23.7324 1.66406C24.9511 0.727197 24.9519 0.727174 24.9521 0.727539V0.728516ZM20.8818 6.71777C20.883 6.71915 20.8844 6.72112 20.8857 6.72266C20.8884 6.72575 20.8913 6.72955 20.8945 6.7334C20.9013 6.74146 20.9097 6.75135 20.9189 6.7627C20.9377 6.78571 20.9613 6.81627 20.9893 6.85254C21.0454 6.92541 21.1193 7.02548 21.2041 7.15039C21.3738 7.40025 21.5923 7.75549 21.8076 8.2041C22.2375 9.09971 22.6702 10.3942 22.6738 11.9814C22.6775 13.5877 22.2464 14.907 21.8184 15.8213C21.6037 16.2796 21.3859 16.6435 21.2168 16.8994C21.1322 17.0275 21.0587 17.1296 21.0029 17.2041C20.9751 17.2413 20.9521 17.2725 20.9336 17.2959C20.9244 17.3076 20.9158 17.318 20.9092 17.3262C20.906 17.3301 20.903 17.3338 20.9004 17.3369C20.8992 17.3384 20.8976 17.3396 20.8965 17.3408L20.8945 17.3438L20.8936 17.3447C20.8908 17.3432 20.8385 17.2994 19.7109 16.3623C18.6207 15.4562 18.5352 15.386 18.5283 15.3809L18.5264 15.3828C18.53 15.3781 18.5354 15.3711 18.542 15.3623C18.5644 15.3323 18.6022 15.2785 18.6514 15.2041C18.7504 15.0542 18.8909 14.8214 19.0332 14.5176C19.3191 13.907 19.601 13.0372 19.5986 11.9883C19.5963 10.9613 19.3163 10.1199 19.0352 9.53418C18.8952 9.24261 18.7577 9.02009 18.6611 8.87793C18.6132 8.80729 18.5751 8.75731 18.5537 8.72949C18.5494 8.72384 18.5459 8.71861 18.543 8.71484L20.8789 6.71484L20.8818 6.71777Z"
fill={color}
/>
</svg>
);
}
2 changes: 1 addition & 1 deletion src/hooks/useModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function useModal(options: UseModalOptions = {}) {
return (
<GlobalPortal.Consumer>
<div
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
onClick={handleOverlayClick}
>
<div className="relative overflow-hidden rounded-[20px] bg-white shadow-lg">
Expand Down
2 changes: 1 addition & 1 deletion src/layout/components/header/StickyTriSectionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function StickyTriSectionHeader(props: PropsWithChildren) {
const { children } = props;

return (
<header className="sticky top-0 h-[80px] flex-shrink-0 border-b-[3px] border-default-disabled/hover">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

header에 z index를 추가해서 설명문구가 header밑줄에 덮혀지는 문제가 있습니다. 다른 방식의 해결 방법이 필요해보입니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 모달 Wrapper에 z-50 부여하여 해결했습니다.

일단 헤더에 z-index 부여한 배경에 대해 설명을 드려야 할 것 같습니다. 찾아보니, z-index가 지정되지 않았거나 동일한 경우, HTML 코드상 나중에 작성된 요소가 위에 올라오는 게 원인이었던 것 같습니다. 우리 코드에서 <header><main>에는 둘 다 z-index가 없었고, 순서상 <main><header>의 아래에 있었기 때문에, <header> 공간을 넘어가서 <main> 공간을 일부 침범해 있는 볼륨 바가 클릭이 불가능한 상황이 발생했던 거였죠. 그래서 <header>의 z-index를 명시적으로 <main>보다 높게 두어야 이 문제를 해결할 수 있었습니다.

알려주신 문제는 이처럼 헤더의 z-index가 50으로 지정되었지만, 모달 Wrapper는 z-index가 부여되지 않아, 헤더의 흰색 테두리가 모달보다 위에 올라와서 발생한 것으로 생각됩니다. 따라서 이번에는 모달과 헤더 간 경합을 조정하기 위해, 2가지 수정 사항을 적용했음을 알립니다:

  • 헤더의 z-index는 50에서 30으로 감소
  • 모달 Wrapper의 z-index는 없었지만 50으로 신규 부여

설명을 최대한 풀어서 쓰려고 노력했는데, 그럼에도 제 설명이 좀 미숙했던 부분이 있으시다면 이따가 통합 회의 때 말씀 남겨주세요. 이 부분 섬세하게 짚어주셔서 감사합니당 bb

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 이해가 너무 잘됐습니다!!! 아직 헤더랑 이렇게 겹치는 것이 모달 밖에 없는 것 같은데, 나중에 더 많아지면 z-index 순서를 명시적으로 관리하도록 하는 것도 좋은 방법일 것 같아요!

export const Z_INDEX = {
  header: 100,
  dropdown: 200,
  toast: 500,
  modal: 1000,
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 확인했습니다. 감사합니다.

<header className="sticky top-0 z-30 h-[80px] flex-shrink-0 border-b-[3px] border-default-disabled/hover">
<div className="relative flex h-full items-center justify-between p-[16px]">
{children}
</div>
Expand Down
27 changes: 27 additions & 0 deletions src/page/TimerPage/TimerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import clsx from 'clsx';
import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator';
import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator';
import { RiFullscreenFill, RiFullscreenExitFill } from 'react-icons/ri';
import DTVolume from '../../components/icons/Volume';
import VolumeBar from '../../components/VolumeBar/VolumeBar';

export default function TimerPage() {
const pathParams = useParams();
Expand All @@ -39,9 +41,14 @@ export default function TimerPage() {
isLoading,
isError,
refetch,
isVolumeBarOpen,
toggleVolumeBar,
volume,
setVolume,
isFullscreen,
setFullscreen,
toggleFullscreen,
volumeRef,
} = state;

// If error, print error message and let user be able to retry
Expand Down Expand Up @@ -103,6 +110,26 @@ export default function TimerPage() {
<RiFullscreenFill className="h-full w-full" />
)}
</button>

<div className="relative flex h-full flex-col" ref={volumeRef}>
<button
className="flex aspect-square h-full items-center justify-center p-[4px]"
aria-label="볼륨 조절"
title="볼륨 조절"
onClick={toggleVolumeBar}
>
<DTVolume className="h-full w-full" />
</button>

{isVolumeBarOpen && (
<div className="absolute -right-[60px] top-[48px]">
<VolumeBar
volume={volume}
onVolumeChange={(value: number) => setVolume(value)}
/>
</div>
)}
</div>
</DefaultLayout.Header.Right>
</DefaultLayout.Header>

Expand Down
Loading