From 456f9e173a57f0060b3b6ad9a0fa5b8d88b69f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=97=B0=EC=A7=84?= Date: Fri, 31 Oct 2025 18:12:10 +0900 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=B0=EB=A6=AC=EB=8D=94=EA=B0=80=20=EC=9E=98=20?= =?UTF-8?q?=EC=9D=BD=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/react/SimulationFilter.tsx | 87 ++++++++++++++++++++++++++-------- src/react/ThemeSwitcher.tsx | 42 ++++++++++++---- 2 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/react/SimulationFilter.tsx b/src/react/SimulationFilter.tsx index 26cffa6..89bf9df 100644 --- a/src/react/SimulationFilter.tsx +++ b/src/react/SimulationFilter.tsx @@ -11,7 +11,7 @@ import { isVisionMode, } from '../core/constants/modes.js'; import { VisionMode, VisionOptions } from '../types/simulationTypes.js'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useId, useRef, useState } from 'react'; import { SimulationKey, useTheme } from './ThemeProvider.js'; import { VisionFilterPortal } from './VisionPortal.js'; import { TOOLBAR_POSITION } from '../core/constants/position.js'; @@ -65,6 +65,7 @@ export default function SimulationFilter(props?: SimulationFilterProps) { const [open, setOpen] = useState(false); const { simulationFilter, setSimulationFilter, language } = useTheme(); const initialized = useRef(false); + const optionsId = useId(); if (!visible) return null; if (!allowInProd && !IS_DEV) return null; @@ -169,37 +170,83 @@ export default function SimulationFilter(props?: SimulationFilterProps) {
- - - + - setOpen(!open)} > {SIMULATE_LABEL[language]} - + {open && ( -
+
)} {open && - MODES.map((value) => ( - - ))} + {MODES.map((value) => ( + + ))} +
+ )}
); diff --git a/src/react/ThemeSwitcher.tsx b/src/react/ThemeSwitcher.tsx index d07c5a3..10e76d1 100644 --- a/src/react/ThemeSwitcher.tsx +++ b/src/react/ThemeSwitcher.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect, useRef, useState, useMemo } from 'react'; +import React, { useEffect, useId, useMemo, useRef, useState } from 'react'; import { useTheme, getThemeOptions, type ThemeKey } from './ThemeProvider.js'; import Logo from '../icons/Logo.js'; import US from '../icons/Us.js'; @@ -38,6 +38,7 @@ export function ThemeSwitcher({ options, position }: TThemeSwitcherProps) { ); const [isOpen, setIsOpen] = useState(false); const wrapperRef = useRef(null); + const menuId = useId(); const switcherClass = SWITCHER_POSITION[position ?? 'right-bottom']; const switcherMenuClass = @@ -62,6 +63,15 @@ export function ThemeSwitcher({ options, position }: TThemeSwitcherProps) { setIsOpen((prev) => !prev); }; + const toggleAriaLabel = + language === 'Korean' + ? isOpen + ? '테마 전환 메뉴 닫기' + : '테마 전환 메뉴 열기' + : isOpen + ? 'Close theme switcher menu' + : 'Open theme switcher menu'; + return (
@@ -71,6 +81,8 @@ export function ThemeSwitcher({ options, position }: TThemeSwitcherProps) { type="button" aria-haspopup="menu" aria-expanded={isOpen} + aria-controls={menuId} + aria-label={toggleAriaLabel} onClick={toggle} className={`fixed w-[60px] h-[60px] p-[10px] ${isOpen ? 'bg-[#252525] border-[1px] border-[#8144FF]' : 'bg-[rgba(129,68,255,0.2)]'} rounded-full flex justify-center items-center shadow-[0_0_3px_0_rgba(0,0,0,0.17)] ${switcherClass} @@ -78,7 +90,11 @@ export function ThemeSwitcher({ options, position }: TThemeSwitcherProps) { > {isOpen ? (
- +
) : (
{isOpen ? (
@@ -112,6 +133,7 @@ export function ThemeSwitcher({ options, position }: TThemeSwitcherProps) {