File name
Commit message
Commit date
File name
Commit message
Commit date
08-18
08-18
File name
Commit message
Commit date
08-18
"use client";
import { useSyncExternalStore } from "react";
import styles from "./dev-test.module.scss";
/** `@fox/styles/_root.scss`가 읽는 수동 테마 선택 속성. */
const THEME_ATTRIBUTE = "data-theme";
type ThemeChoice = "system" | "light" | "dark";
const CHOICES: { value: ThemeChoice; label: string }[] = [
{ value: "system", label: "시스템" },
{ value: "light", label: "라이트" },
{ value: "dark", label: "다크" },
];
/**
* `<html data-theme>`가 SSOT라 React state를 두지 않는다. 저장 키는 호스트 앱이
* 소유하는 값이라 `@fox`가 알면 포터빌리티가 깨지므로 선택을 저장하지 않는다.
*/
function subscribe(onChange: () => void): () => void {
const observer = new MutationObserver(onChange);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: [THEME_ATTRIBUTE],
});
return () => observer.disconnect();
}
function getSnapshot(): ThemeChoice {
const value = document.documentElement.getAttribute(THEME_ATTRIBUTE);
return value === "light" || value === "dark" ? value : "system";
}
function getServerSnapshot(): ThemeChoice {
return "system";
}
function apply(next: ThemeChoice): void {
const root = document.documentElement;
if (next === "system") {
root.removeAttribute(THEME_ATTRIBUTE);
} else {
root.setAttribute(THEME_ATTRIBUTE, next);
}
}
export function ThemeSwitch() {
const current = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return (
<div className={styles.themeSwitch} role="group" aria-label="테마 전환">
{CHOICES.map(({ value, label }) => (
<button
key={value}
type="button"
className={styles.themeButton}
aria-pressed={current === value}
onClick={() => apply(value)}
>
{label}
</button>
))}
</div>
);
}