refactor(appearance): organize components and hooks
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { useMessage } from "@workspace/i18n"
|
||||
import { SunIcon } from "@workspace/ui/components/icon"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
import type { Brightness } from "../../display"
|
||||
import { appearanceMessages } from "../../messages"
|
||||
|
||||
export interface BrightnessFieldProps {
|
||||
className?: string
|
||||
onValueChange: (value: Brightness) => void
|
||||
value: Brightness
|
||||
}
|
||||
|
||||
export function BrightnessField({
|
||||
className,
|
||||
onValueChange,
|
||||
value,
|
||||
}: BrightnessFieldProps) {
|
||||
const title = useMessage(appearanceMessages.brightness)
|
||||
const dim = useMessage(appearanceMessages.brightnessDim)
|
||||
const normal = useMessage(appearanceMessages.brightnessNormal)
|
||||
const bright = useMessage(appearanceMessages.brightnessBright)
|
||||
const description = useMessage(appearanceMessages.brightnessDescription)
|
||||
const items: ReadonlyArray<{ label: string; value: Brightness }> = [
|
||||
{ label: dim, value: "dim" },
|
||||
{ label: normal, value: "normal" },
|
||||
{ label: bright, value: "bright" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn("min-w-0 lg:flex-1", className)}>
|
||||
<h2 className="font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||
<Select
|
||||
items={items}
|
||||
value={value}
|
||||
onValueChange={(brightness) => {
|
||||
if (brightness) onValueChange(brightness)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={title} className="mt-2 w-full max-w-40">
|
||||
<SunIcon />
|
||||
<SelectValue>
|
||||
{(selectedValue: Brightness | null) =>
|
||||
items.find((item) => item.value === selectedValue)?.label
|
||||
}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useMessage } from "@workspace/i18n"
|
||||
import { EyeIcon } from "@workspace/ui/components/icon"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
import type { ColorVisionMode } from "../../color-vision"
|
||||
import { appearanceMessages } from "../../messages"
|
||||
|
||||
export interface ColorVisionModeFieldProps {
|
||||
className?: string
|
||||
onValueChange: (value: ColorVisionMode) => void
|
||||
value: ColorVisionMode
|
||||
}
|
||||
|
||||
export function ColorVisionModeField({
|
||||
className,
|
||||
onValueChange,
|
||||
value,
|
||||
}: ColorVisionModeFieldProps) {
|
||||
const title = useMessage(appearanceMessages.colorVisionMode)
|
||||
const standard = useMessage(appearanceMessages.colorVisionStandard)
|
||||
const protan = useMessage(appearanceMessages.colorVisionProtan)
|
||||
const deutan = useMessage(appearanceMessages.colorVisionDeutan)
|
||||
const tritan = useMessage(appearanceMessages.colorVisionTritan)
|
||||
const standardDescription = useMessage(
|
||||
appearanceMessages.colorVisionStandardDescription
|
||||
)
|
||||
const assistedDescription = useMessage(
|
||||
appearanceMessages.colorVisionAssistedDescription
|
||||
)
|
||||
const items: ReadonlyArray<{ label: string; value: ColorVisionMode }> = [
|
||||
{ label: standard, value: "standard" },
|
||||
{ label: protan, value: "protan" },
|
||||
{ label: deutan, value: "deutan" },
|
||||
{ label: tritan, value: "tritan" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn("min-w-0 lg:flex-1", className)}>
|
||||
<h2 className="font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{value === "standard" ? standardDescription : assistedDescription}
|
||||
</p>
|
||||
<Select
|
||||
items={items}
|
||||
value={value}
|
||||
onValueChange={(mode) => {
|
||||
if (mode) onValueChange(mode)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={title} className="mt-2 w-full max-w-56">
|
||||
<EyeIcon />
|
||||
<SelectValue>
|
||||
{(selectedValue: ColorVisionMode | null) =>
|
||||
items.find((item) => item.value === selectedValue)?.label
|
||||
}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { BrightnessField, type BrightnessFieldProps } from "./brightness-field"
|
||||
export {
|
||||
ColorVisionModeField,
|
||||
type ColorVisionModeFieldProps,
|
||||
} from "./color-vision-mode-field"
|
||||
export { MotionModeField, type MotionModeFieldProps } from "./motion-mode-field"
|
||||
export { TextScaleField, type TextScaleFieldProps } from "./text-scale-field"
|
||||
export {
|
||||
ThemeModeField,
|
||||
ThemeModeIcon,
|
||||
type ThemeModeFieldProps,
|
||||
} from "./theme-mode-field"
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useMessage } from "@workspace/i18n"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
import type { MotionMode } from "../../display"
|
||||
import { appearanceMessages } from "../../messages"
|
||||
|
||||
export interface MotionModeFieldProps {
|
||||
className?: string
|
||||
onValueChange: (value: MotionMode) => void
|
||||
value: MotionMode
|
||||
}
|
||||
|
||||
export function MotionModeField({
|
||||
className,
|
||||
onValueChange,
|
||||
value,
|
||||
}: MotionModeFieldProps) {
|
||||
const title = useMessage(appearanceMessages.motionMode)
|
||||
const system = useMessage(appearanceMessages.motionSystem)
|
||||
const reduce = useMessage(appearanceMessages.motionReduce)
|
||||
const full = useMessage(appearanceMessages.motionFull)
|
||||
const descriptions = {
|
||||
full: useMessage(appearanceMessages.motionFullDescription),
|
||||
reduce: useMessage(appearanceMessages.motionReduceDescription),
|
||||
system: useMessage(appearanceMessages.motionSystemDescription),
|
||||
}
|
||||
const items: ReadonlyArray<{ label: string; value: MotionMode }> = [
|
||||
{ label: system, value: "system" },
|
||||
{ label: reduce, value: "reduce" },
|
||||
{ label: full, value: "full" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn("min-w-0 lg:flex-1", className)}>
|
||||
<h2 className="font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{descriptions[value]}
|
||||
</p>
|
||||
<Select
|
||||
items={items}
|
||||
value={value}
|
||||
onValueChange={(mode) => {
|
||||
if (mode) onValueChange(mode)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={title} className="mt-2 w-full max-w-56">
|
||||
<SelectValue>
|
||||
{(selectedValue: MotionMode | null) =>
|
||||
items.find((item) => item.value === selectedValue)?.label
|
||||
}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useMessage } from "@workspace/i18n"
|
||||
import { ALargeSmall } from "@workspace/ui/components/icon"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
import type { TextScale } from "../../display"
|
||||
import { appearanceMessages } from "../../messages"
|
||||
|
||||
export interface TextScaleFieldProps {
|
||||
className?: string
|
||||
onValueChange: (value: TextScale) => void
|
||||
value: TextScale
|
||||
}
|
||||
|
||||
export function TextScaleField({
|
||||
className,
|
||||
onValueChange,
|
||||
value,
|
||||
}: TextScaleFieldProps) {
|
||||
const title = useMessage(appearanceMessages.textScale)
|
||||
const description = useMessage(appearanceMessages.textScaleDescription)
|
||||
const items: ReadonlyArray<{ label: string; value: TextScale }> = [
|
||||
{ label: "100%", value: "100" },
|
||||
{ label: "125%", value: "125" },
|
||||
{ label: "150%", value: "150" },
|
||||
{ label: "200%", value: "200" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn("min-w-0 lg:flex-1", className)}>
|
||||
<h2 className="font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||
<Select
|
||||
items={items}
|
||||
value={value}
|
||||
onValueChange={(scale) => {
|
||||
if (scale) onValueChange(scale)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={title} className="mt-2 w-full max-w-40">
|
||||
<ALargeSmall />
|
||||
<SelectValue>
|
||||
{(selectedValue: TextScale | null) =>
|
||||
items.find((item) => item.value === selectedValue)?.label
|
||||
}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { MonitorIcon, MoonIcon, SunIcon } from "@workspace/ui/components/icon"
|
||||
import { useMessage } from "@workspace/i18n"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
import { appearanceMessages } from "../../messages"
|
||||
import type { ThemeMode } from "../../preferences"
|
||||
|
||||
export interface ThemeModeFieldProps {
|
||||
className?: string
|
||||
onValueChange: (value: ThemeMode) => void
|
||||
value: ThemeMode
|
||||
}
|
||||
|
||||
export function ThemeModeIcon({ mode }: { mode: ThemeMode | null }) {
|
||||
if (mode === "light") return <SunIcon />
|
||||
if (mode === "dark") return <MoonIcon />
|
||||
return <MonitorIcon />
|
||||
}
|
||||
|
||||
export function ThemeModeField({
|
||||
className,
|
||||
onValueChange,
|
||||
value,
|
||||
}: ThemeModeFieldProps) {
|
||||
const lightLabel = useMessage(appearanceMessages.lightMode)
|
||||
const darkLabel = useMessage(appearanceMessages.darkMode)
|
||||
const systemLabel = useMessage(appearanceMessages.followSystem)
|
||||
const lightDescription = useMessage(appearanceMessages.lightModeDescription)
|
||||
const darkDescription = useMessage(appearanceMessages.darkModeDescription)
|
||||
const systemDescription = useMessage(
|
||||
appearanceMessages.followSystemDescription
|
||||
)
|
||||
const title = useMessage(appearanceMessages.themeMode)
|
||||
const modeItems: ReadonlyArray<{ label: string; value: ThemeMode }> = [
|
||||
{ label: lightLabel, value: "light" },
|
||||
{ label: darkLabel, value: "dark" },
|
||||
{ label: systemLabel, value: "system" },
|
||||
]
|
||||
const description = {
|
||||
light: lightDescription,
|
||||
dark: darkDescription,
|
||||
system: systemDescription,
|
||||
}[value]
|
||||
|
||||
return (
|
||||
<div className={cn("min-w-0 lg:flex-1", className)}>
|
||||
<h2 className="font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||
<Select
|
||||
items={modeItems}
|
||||
value={value}
|
||||
onValueChange={(mode) => {
|
||||
if (mode) onValueChange(mode)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger aria-label={title} className="mt-2 w-full max-w-40">
|
||||
<SelectValue>
|
||||
{(selectedValue: ThemeMode | null) => (
|
||||
<>
|
||||
<ThemeModeIcon mode={selectedValue} />
|
||||
{modeItems.find((item) => item.value === selectedValue)?.label}
|
||||
</>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
{modeItems.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<ThemeModeIcon mode={item.value} />
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user