enhance code

This commit is contained in:
Toinane
2026-08-06 01:50:14 +02:00
parent 4dfdc9ef54
commit 72ee062df2
34 changed files with 160 additions and 142 deletions
+14 -2
View File
@@ -1,4 +1,13 @@
{
// `suspicious`/`pedantic`/`style` were evaluated and left off: they're
// dominated by false positives for this codebase's conventions —
// react/react-in-jsx-scope (React 19's automatic JSX runtime needs no
// import), import/no-unassigned-import (CSS side-effect imports),
// import/no-named-export (named exports are this project's convention for
// stores/hooks), eslint/max-lines-per-function, import/max-dependencies.
// `perf` is enabled below since it was mostly signal; `oxc/no-map-spread`
// is its one false positive (it'd push stores toward in-place mutation,
// which breaks Zustand's shallow-equality change detection).
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [
"typescript",
@@ -8,9 +17,12 @@
"import"
],
"categories": {
"correctness": "error"
"correctness": "error",
"perf": "error"
},
"rules": {
"oxc/no-map-spread": "off"
},
"rules": {},
"env": {
"builtin": true
}
+6
View File
@@ -15,6 +15,12 @@
"terms": {
"website": "Website"
},
"color": {
"red": "Red",
"green": "Green",
"blue": "Blue",
"hex": "Hex color"
},
"tray": {
"open": "Open Colorpicker",
"settings": "Settings",
+8 -4
View File
@@ -11,15 +11,18 @@
"title": "Colorpicker",
"openAtLogin": {
"label": "Open Colorpicker at login",
"description": "Launch Colorpicker automatically when you log in"
"description": "Launch Colorpicker automatically when you log in",
"errorToast": "Failed to update \"Open Colorpicker at login\""
},
"keepOnTop": {
"label": "Keep Colorpicker on top",
"description": "Always display Colorpicker above other windows"
"description": "Always display Colorpicker above other windows",
"errorToast": "Failed to update \"Keep Colorpicker on top\""
},
"closeToTray": {
"label": "Close to tray",
"description": "Keep Colorpicker running in the system tray instead of quitting when the window is closed"
"description": "Keep Colorpicker running in the system tray instead of quitting when the window is closed",
"errorToast": "Failed to update \"Close to tray\""
},
"theme": {
"label": "Theme",
@@ -159,7 +162,8 @@
"title": "Picker",
"pickerHotkey": {
"label": "Launch Picker Hotkey",
"description": "Global keyboard shortcut to launch the color picker from anywhere, even when Colorpicker is closed"
"description": "Global keyboard shortcut to launch the color picker from anywhere, even when Colorpicker is closed",
"errorToast": "Failed to register the picker hotkey"
}
}
},
+6
View File
@@ -15,6 +15,12 @@
"terms": {
"website": "Site web"
},
"color": {
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"hex": "Couleur hexadécimale"
},
"tray": {
"open": "Ouvrir Colorpicker",
"settings": "Paramètres",
+8 -4
View File
@@ -11,15 +11,18 @@
"title": "Colorpicker",
"openAtLogin": {
"label": "Ouvrir Colorpicker à la connexion",
"description": "Ouvre Colorpicker automatiquement au démarrage de votre session"
"description": "Ouvre Colorpicker automatiquement au démarrage de votre session",
"errorToast": "Échec de la mise à jour de « Ouvrir Colorpicker à la connexion »"
},
"keepOnTop": {
"label": "Garder Colorpicker au premier plan",
"description": "Affiche toujours Colorpicker au-dessus des autres fenêtres"
"description": "Affiche toujours Colorpicker au-dessus des autres fenêtres",
"errorToast": "Échec de la mise à jour de « Garder Colorpicker au premier plan »"
},
"closeToTray": {
"label": "Fermer dans la barre système",
"description": "Garde Colorpicker actif dans la barre système au lieu de quitter à la fermeture de la fenêtre"
"description": "Garde Colorpicker actif dans la barre système au lieu de quitter à la fermeture de la fenêtre",
"errorToast": "Échec de la mise à jour de « Fermer dans la barre système »"
},
"theme": {
"label": "Thème",
@@ -159,7 +162,8 @@
"title": "Pipette",
"pickerHotkey": {
"label": "Raccourci de lancement",
"description": "Raccourci clavier global pour lancer le sélecteur de couleur depuis n'importe où, même lorsque Colorpicker est fermé"
"description": "Raccourci clavier global pour lancer le sélecteur de couleur depuis n'importe où, même lorsque Colorpicker est fermé",
"errorToast": "Échec de l'enregistrement du raccourci de la pipette"
}
}
},
+2 -25
View File
@@ -85,18 +85,7 @@ getWindowLabel().catch(() => {
// Ignore errors, will use default
})
/**
* Logger interface - the single source of truth for all application logging.
*
* @example
* ```ts
* import { logger } from '@/common/logger'
*
* logger.debug('Starting color pick', { gridSize: 11 })
* logger.info('Color picked successfully', { color: '#8E44AD' })
* logger.error('Pick failed', { error: err.message })
* ```
*/
/** The single source of truth for all application logging. */
export const logger = {
/**
* Trace - Most granular logging, typically for tracking program flow.
@@ -139,19 +128,7 @@ export const logger = {
},
} as const
/**
* Create a scoped logger with automatic scope tagging.
* The scope name appears as a tag in the log output.
*
* @param scopeName - Name of the scope (e.g., "SettingsStore", "ColorPicker")
*
* @example
* ```ts
* const log = createScopedLogger('SettingsStore')
* log.info('Settings saved')
* // Output: [23:22:25] DEBUG [window:main][SettingsStore] Settings saved
* ```
*/
/** Logger whose messages are tagged with `scopeName`, e.g. `[SettingsStore]`. */
export function createScopedLogger(scopeName: string) {
return {
trace: (message: string, context?: LogContext) => {
+1 -3
View File
@@ -26,9 +26,7 @@ export const persistToStore = async (
entries: Record<string, unknown>,
): Promise<void> => {
if (!store) return
for (const [key, value] of Object.entries(entries)) {
await store.set(key, value)
}
await Promise.all(Object.entries(entries).map(([key, value]) => store.set(key, value)))
await store.save()
}
@@ -1,7 +1,7 @@
import { FunctionComponent, JSX, useCallback, useState, useEffect } from 'react'
import { FunctionComponent, JSX, useCallback } from 'react'
import Color from 'colorjs.io'
import { useTranslation } from 'react-i18next'
// import { writeText } from '@tauri-apps/plugin-clipboard-manager'
// import { useTranslation } from 'react-i18next'
// import Icon, { IconEnum } from '@components/icons'
import { useColorStore } from '@stores/colorStore'
@@ -9,18 +9,15 @@ import { useColorStore } from '@stores/colorStore'
import { useColorHistoryStore } from '@stores/colorHistoryStore'
// import { showToast } from '@stores/toastStore'
import { isValidHex /*, serializeColor*/, toHex } from '@common/color'
import { useControlledValue } from '@hooks/index'
import './hexInput.css'
import style from './hexInput.module.css'
const HexInput: FunctionComponent = (): JSX.Element => {
// const CommonT = useTranslation('common')
const CommonT = useTranslation('common')
const { color, setColor /*, isDarkColor */ } = useColorStore((state) => state)
// const { defaultFormat, hexPrefix } = useSettingsStore()
const [inputValue, setInputValue] = useState(toHex(color))
useEffect(() => {
setInputValue(toHex(color))
}, [color])
const [inputValue, setInputValue] = useControlledValue(toHex(color))
const onInput = useCallback(
(event: React.FormEvent<HTMLInputElement>) => {
@@ -34,7 +31,7 @@ const HexInput: FunctionComponent = (): JSX.Element => {
useColorHistoryStore.getState().commitColor(toHex(newColor))
}
},
[setColor],
[setColor, setInputValue],
)
const onKeyboard = (event: React.KeyboardEvent<HTMLInputElement>) => {
@@ -72,12 +69,13 @@ const HexInput: FunctionComponent = (): JSX.Element => {
// }
return (
<div className="hexInputGroup">
<div className={style.hexInputGroup}>
<input
className="hexInput"
className={style.hexInput}
type="text"
maxLength={7}
value={inputValue}
aria-label={CommonT.t('color.hex')}
onInput={onInput}
onKeyDown={onKeyboard}
onFocus={(e) => e.target.select()}
@@ -85,7 +83,7 @@ const HexInput: FunctionComponent = (): JSX.Element => {
/>
{/* <button
type="button"
className="hexInputCopyButton"
className={style.hexInputCopyButton}
onClick={onCopy}
title={CommonT.t("action.copy")}
aria-label={CommonT.t("action.copy")}
@@ -1,6 +1,8 @@
import { FunctionComponent, JSX, useState, useEffect } from 'react'
import { FunctionComponent, JSX } from 'react'
import './numberInput.css'
import { useControlledValue } from '@hooks/index'
import style from './numberInput.module.css'
type NumberInputProps = {
min: number
@@ -9,6 +11,8 @@ type NumberInputProps = {
step?: number
value: number
onChange?: (value: number) => void
/** Accessible name (e.g. "Red") — this input has no visible text label. */
label: string
}
const NumberInput: FunctionComponent<NumberInputProps> = ({
@@ -18,12 +22,9 @@ const NumberInput: FunctionComponent<NumberInputProps> = ({
step = 1,
value,
onChange,
label,
}): JSX.Element => {
const [number, setNumber] = useState(Number.isNaN(value) ? 0 : value)
useEffect(() => {
setNumber(Number.isNaN(value) ? 0 : value)
}, [value])
const [number, setNumber] = useControlledValue(Number.isNaN(value) ? 0 : value)
const verifyNumber = (currentNumber: number): number => {
if (currentNumber < min) return min
@@ -60,13 +61,14 @@ const NumberInput: FunctionComponent<NumberInputProps> = ({
return (
<input
className="numberInput"
className={style.numberInput}
type="input"
min={min}
max={max}
maxLength={maxLength}
step={step}
value={number}
aria-label={label}
onFocus={(e) => e.target.select()}
onInput={onInput}
onKeyDown={onKeyboard}
@@ -1,5 +1,6 @@
import { FunctionComponent, JSX, useMemo, useCallback } from 'react'
import Color from 'colorjs.io'
import { useTranslation } from 'react-i18next'
import { useColorStore } from '@stores/colorStore'
import { useColorHistoryStore } from '@stores/colorHistoryStore'
@@ -8,9 +9,10 @@ import { toHex } from '@common/color'
import Slider from '@components/colorpicker/sliders/slider'
import NumberInput from '@components/colorpicker/inputs/numberInput/numberInput'
import './RGBSlider.css'
import style from './RGBSlider.module.css'
const RGBSlider: FunctionComponent = (): JSX.Element => {
const CommonT = useTranslation('common')
const color = useColorStore((state) => state.color)
// NOTE: use getAll('srgb') rather than the color.srgb accessor - colorjs.io's
// package.json sideEffects list omits src/space-accessors.js, so Vite's esbuild
@@ -34,8 +36,8 @@ const RGBSlider: FunctionComponent = (): JSX.Element => {
)
return (
<section className={'RGBSlider'}>
<section className="slider">
<section className={style.RGBSlider}>
<section className={style.slider}>
<Slider
type="redGradient"
min={0}
@@ -48,9 +50,10 @@ const RGBSlider: FunctionComponent = (): JSX.Element => {
max={255}
value={Math.round((r ?? 0) * 255)}
onChange={(value) => handleChange('r', value)}
label={CommonT.t('color.red')}
/>
</section>
<section className="slider">
<section className={style.slider}>
<Slider
type="greenGradient"
min={0}
@@ -63,9 +66,10 @@ const RGBSlider: FunctionComponent = (): JSX.Element => {
max={255}
value={Math.round((g ?? 0) * 255)}
onChange={(value) => handleChange('g', value)}
label={CommonT.t('color.green')}
/>
</section>
<section className="slider">
<section className={style.slider}>
<Slider
type="blueGradient"
min={0}
@@ -78,6 +82,7 @@ const RGBSlider: FunctionComponent = (): JSX.Element => {
max={255}
value={Math.round((b ?? 0) * 255)}
onChange={(value) => handleChange('b', value)}
label={CommonT.t('color.blue')}
/>
</section>
</section>
@@ -1,6 +1,8 @@
import { FunctionComponent, JSX, useState, useEffect } from 'react'
import { FunctionComponent, JSX } from 'react'
import classNames from 'clsx'
import { useColorStore } from '@stores/colorStore'
import { useControlledValue } from '@hooks/index'
import style from './slider.module.css'
@@ -19,14 +21,9 @@ const Slider: FunctionComponent<SliderProps> = ({
value,
onChange,
}): JSX.Element => {
const [color, setColor] = useState(Number.isNaN(value) ? 0 : value)
const [color, setColor] = useControlledValue(Number.isNaN(value) ? 0 : value)
const isDarkColor = useColorStore((state) => state.isDarkColor)
// Sync local state when prop changes
useEffect(() => {
setColor(Number.isNaN(value) ? 0 : value)
}, [value])
const changeValue = (event: React.FormEvent<HTMLInputElement>) => {
const newColor = event.target instanceof HTMLInputElement ? Number(event.target.value) : 0
setColor(newColor)
@@ -44,7 +41,7 @@ const Slider: FunctionComponent<SliderProps> = ({
onInput={changeValue}
/>
<progress
className={`${style.progress} ${style[type]} ${isDarkColor ? style.dark : style.light}`}
className={classNames(style.progress, style[type], isDarkColor ? style.dark : style.light)}
max={max}
value={color}
/>
@@ -1,3 +1,5 @@
import classNames from 'clsx'
import { Text } from '@components/ui'
import style from './navigationItem.module.css'
@@ -12,7 +14,7 @@ const NavigationItem = ({
isActive?: boolean
}) => {
return (
<button className={`${style.navigationItem} ${isActive ? style.active : ''}`}>
<button className={classNames(style.navigationItem, isActive && style.active)}>
<div className={style.colorSwatch} style={{ backgroundColor: color }}></div>
<Text color="primary" className={style.label}>
{label}
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import classNames from 'clsx'
import { Text, Button } from '@components/ui'
import Icon, { IconEnum } from '@components/icons'
@@ -47,7 +48,7 @@ const NavigationSection = ({
}}
>
<div className={style.navigationSectionToggle}>
<span className={`${style.chevron} ${isOpen ? style.chevronOpen : ''}`}>
<span className={classNames(style.chevron, isOpen && style.chevronOpen)}>
<Icon type={IconEnum.ARROW} />
</span>
<Text
@@ -68,7 +69,10 @@ const NavigationSection = ({
>
<Button
variant="transparent"
className={`${style.navigationSectionMenuButton} ${isMenuOpen ? style.navigationSectionMenuButtonActive : ''}`}
className={classNames(
style.navigationSectionMenuButton,
isMenuOpen && style.navigationSectionMenuButtonActive,
)}
onClick={() => setIsMenuOpen((open) => !open)}
aria-label="Section options"
>
@@ -1,3 +1,5 @@
import classNames from 'clsx'
import { Heading, Text } from '@components/ui'
import style from './settingsItem.module.css'
@@ -11,7 +13,7 @@ export interface SettingsItemProps {
const SettingsItem = ({ label, description, disabled = false, children }: SettingsItemProps) => {
return (
<section className={`${style.settingsItem} ${disabled ? style.settingsItemDisabled : ''}`}>
<section className={classNames(style.settingsItem, disabled && style.settingsItemDisabled)}>
<div className={style.settingsItemInfo}>
<Heading level={3} className={style.settingsItemLabel}>
{label}
@@ -13,17 +13,17 @@ export interface SettingsItemListProps {
const SettingsItemList = ({ items }: SettingsItemListProps) => {
return (
<section className={style.settingsItemList}>
{items.map((item, index) => {
{items.map((item) => {
if (typeof item === 'string') {
return (
<React.Fragment key={`string-${index}`}>
<React.Fragment key={item}>
<Text className={style.settingsItemListValue}>{item}</Text>
<span className={style.settingsItemListEmpty}></span>
</React.Fragment>
)
} else {
return Object.entries(item).map(([key, value]) => (
<React.Fragment key={`${index}-${key}`}>
<React.Fragment key={key}>
<Text className={style.settingsItemListLabel}>{key}</Text>
<Text className={style.settingsItemListValue}>{value}</Text>
</React.Fragment>
+9 -1
View File
@@ -1,3 +1,5 @@
import classNames from 'clsx'
import style from './Heading.module.css'
export type HeadingLevel = 1 | 2 | 3
@@ -31,7 +33,13 @@ const Heading = ({
return (
<Tag
id={id}
className={`${style.heading} ${color && style[color]} ${style[size]} ${style[weight]} ${className ?? ''}`}
className={classNames(
style.heading,
color && style[color],
style[size],
style[weight],
className,
)}
>
{children}
</Tag>
+2 -1
View File
@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react'
import classNames from 'clsx'
import { Text, KeyCombo } from '@components/ui'
import Icon, { IconEnum } from '@components/icons'
@@ -135,7 +136,7 @@ const HotkeyInput = ({
<div className={style.hotkeyInputWrapper}>
<button
type="button"
className={`${style.hotkeyInput} ${isRecording ? style.hotkeyInputRecording : ''}`}
className={classNames(style.hotkeyInput, isRecording && style.hotkeyInputRecording)}
onClick={() => !disabled && !isRecording && setIsRecording(true)}
onBlur={stopRecording}
disabled={disabled}
+1 -1
View File
@@ -27,7 +27,7 @@ const KeyCombo = ({ keys, type = 'joined', variant = 'filled' }: KeyComboProps)
return (
<span className={style.keyComboGroup}>
{keys.map((part, i) => (
<Fragment key={`${part}-${i}`}>
<Fragment key={part}>
{type === 'separated' && i > 0 && (
<Text color="accent" size="small">
+
+3 -2
View File
@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from 'react'
import classNames from 'clsx'
import style from './Select.module.css'
import Icon, { IconEnum } from '@components/icons'
import { Text } from '@components/ui'
@@ -79,7 +80,7 @@ const Select = ({ value, onChange, options, disabled = false }: SelectProps) =>
return (
<div className={style.selectWrapper} ref={selectRef}>
<div
className={`${style.select} ${disabled ? style.selectDisabled : ''} ${isOpen ? style.selectOpen : ''}`}
className={classNames(style.select, disabled && style.selectDisabled, isOpen && style.selectOpen)}
onClick={handleToggle}
onKeyDown={handleKeyDown}
tabIndex={disabled ? -1 : 0}
@@ -97,7 +98,7 @@ const Select = ({ value, onChange, options, disabled = false }: SelectProps) =>
{options.map((option) => (
<div
key={option.value}
className={`${style.selectOption} ${option.value === value ? style.selectOptionSelected : ''}`}
className={classNames(style.selectOption, option.value === value && style.selectOptionSelected)}
onClick={() => handleOptionClick(option.value)}
onKeyDown={(e) => handleOptionKeyDown(e, option.value)}
tabIndex={0}
+3 -1
View File
@@ -1,3 +1,5 @@
import classNames from 'clsx'
import style from './Text.module.css'
export type TextSize = 'small' | 'medium' | 'large'
@@ -15,7 +17,7 @@ export interface TextProps {
const Text = ({ size = 'medium', weight = 'normal', children, className, color }: TextProps) => {
return (
<p
className={`${style.text} ${color && style[color]} ${style[size]} ${style[weight]} ${className ?? ''}`}
className={classNames(style.text, color && style[color], style[size], style[weight], className)}
>
{children}
</p>
+4 -3
View File
@@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import classNames from 'clsx'
import { Text } from '@components/ui'
import style from './Toggle.module.css'
@@ -144,7 +145,7 @@ const Toggle = ({ checked, onChange, disabled = false }: ToggleProps) => {
return (
<div
className={`${style.toggleWrapper} ${disabled ? style.toggleDisabled : ''}`}
className={classNames(style.toggleWrapper, disabled && style.toggleDisabled)}
tabIndex={disabled ? -1 : 0}
onClick={handleClick}
onKeyDown={handleClick}
@@ -153,7 +154,7 @@ const Toggle = ({ checked, onChange, disabled = false }: ToggleProps) => {
{checked ? 'On' : 'Off'}
</Text>
<button
className={`${style.toggle} ${checked ? style.toggleChecked : ''} ${disabled ? style.toggleDisabled : ''}`}
className={classNames(style.toggle, checked && style.toggleChecked, disabled && style.toggleDisabled)}
disabled={disabled}
role="switch"
tabIndex={-1}
@@ -164,7 +165,7 @@ const Toggle = ({ checked, onChange, disabled = false }: ToggleProps) => {
onPointerCancel={handlePointerCancel}
>
<span
className={`${style.toggleSlider} ${instant ? style.toggleSliderDragging : ''}`}
className={classNames(style.toggleSlider, instant && style.toggleSliderDragging)}
style={pinnedLeft !== null ? { left: `${pinnedLeft}px` } : undefined}
></span>
</button>
@@ -1,4 +1,5 @@
import { FunctionComponent, JSX, useEffect, useState, useRef } from 'react'
import classNames from 'clsx'
import { getCurrentWindow } from '@tauri-apps/api/window'
import style from './windowControls.module.css'
@@ -49,6 +50,8 @@ const WindowControls: FunctionComponent = (): JSX.Element => {
return (
<section className={style.windowControls}>
{/* tabIndex={-1} on all three: window-chrome actions (minimize/maximize/close)
are conventionally excluded from the app's own tab order, matching native title bars. */}
<button
className={style.controlButton}
onClick={handleMinimize}
@@ -101,7 +104,7 @@ const WindowControls: FunctionComponent = (): JSX.Element => {
)}
</button>
<button
className={`${style.controlButton} ${style.closeButton}`}
className={classNames(style.controlButton, style.closeButton)}
onClick={handleClose}
aria-label="Close"
title="Close"
@@ -54,6 +54,11 @@
color: var(--win-icon-active);
}
.controlButton:focus-visible {
outline: 2px solid var(--focus-outline);
outline-offset: -2px;
}
.closeButton:hover {
background-color: var(--close-bg-hover);
color: var(--close-icon-hover);
+1
View File
@@ -2,6 +2,7 @@
* React custom hooks
*/
export { useTheme } from './useTheme'
export { useControlledValue } from './useControlledValue'
export { useLogger } from './useLogger'
export { useHotkeyConflict } from './useHotkeyConflict'
export { useInitializeHistory } from './useHistory'
+17
View File
@@ -0,0 +1,17 @@
import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'
/**
* Local state that mirrors an external `value`, so an input can show live
* keystrokes before the parent's re-render round-trips back down, while
* staying in sync if `value` changes for a reason other than the input
* itself (e.g. a reset, or an edit made from another window).
*/
export function useControlledValue<T>(value: T): [T, Dispatch<SetStateAction<T>>] {
const [local, setLocal] = useState(value)
useEffect(() => {
setLocal(value)
}, [value])
return [local, setLocal]
}
+1 -27
View File
@@ -1,33 +1,7 @@
import { useMemo } from 'react'
import { createScopedLogger } from '@common/logger'
/**
* React hook that provides a logger with automatic component scope tagging.
*
* This hook creates a scoped logger that automatically tags all log messages
* with the component name, making it easier to trace logs back to their source.
*
* @param componentName - Name of the component or module using the logger
*
* @example
* ```tsx
* function ColorPicker() {
* const logger = useLogger('ColorPicker')
*
* const handlePick = async () => {
* logger.debug('Starting color pick', { gridSize: 11 })
* // Output: [23:22:25] DEBUG [window:main][ColorPicker] Starting color pick gridSize=11
*
* try {
* const color = await pickColor()
* logger.info('Color picked', { color })
* } catch (err) {
* logger.error('Pick failed', { error: err })
* }
* }
* }
* ```
*/
/** Scoped logger tagged with `componentName`, memoized across re-renders. */
export function useLogger(componentName: string) {
return useMemo(() => createScopedLogger(componentName), [componentName])
}
+1 -15
View File
@@ -1,18 +1,4 @@
export type ColorpickerTool = 'picker' | 'swatch' | 'tint' | 'contrast'
export interface IWindowSchema {
width: number
height: number
x?: number
y?: number
}
export interface ISettingsSchema extends IWindowSchema {
currentColor: string
history: Array<string>
sendCrashReport: boolean
tools: Array<ColorpickerTool>
}
// Convention: `interface` for object/store shapes, `type` for unions and primitives.
export type ThemeOption = 'light' | 'dark' | 'system'
export type LanguageOption = 'en_US' | 'fr_FR'
+3 -3
View File
@@ -20,7 +20,7 @@ import { showToast } from '@stores/toastStore'
import { rgbToColor, serializeColor, toHex } from '@common/color'
import { onColorPicked, onPaletteColorApplied } from '@common/ipc'
import './colorpicker.css'
import style from './colorpicker.module.css'
const notifyColorPicked = async (title: string, text: string): Promise<void> => {
let granted = await isPermissionGranted()
@@ -97,9 +97,9 @@ const Colorpicker = () => {
}, [setColor])
return (
<section className="colorpicker">
<section className={style.colorpicker}>
<WindowBar />
<section className="sliders">
<section className={style.sliders}>
<RGBSlider />
<HexInput />
</section>
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { SettingsSection, SettingsItem } from '@components/settings'
import { Toggle, Select } from '@components/ui'
import { useOpenAtLogin, useKeepOnTop, useCloseToTray, useThemeSetting } from '@hooks/index'
import { showToast } from '@stores/toastStore'
import type { ThemeOption } from '@interfaces/settings'
const ColorpickerSettings = () => {
@@ -26,10 +27,10 @@ const ColorpickerSettings = () => {
await setOpenAtLogin(checked)
} catch (error) {
console.error('Failed to update open at login:', error)
// TODO: Show error notification to user
showToast(SettingsT.t('openAtLogin.errorToast'))
}
},
[setOpenAtLogin],
[setOpenAtLogin, SettingsT],
)
const handleKeepOnTopChange = useCallback(
@@ -38,10 +39,10 @@ const ColorpickerSettings = () => {
await setKeepOnTop(checked)
} catch (error) {
console.error('Failed to update keep on top:', error)
// TODO: Show error notification to user
showToast(SettingsT.t('keepOnTop.errorToast'))
}
},
[setKeepOnTop],
[setKeepOnTop, SettingsT],
)
const handleCloseToTrayChange = useCallback(
@@ -50,10 +51,10 @@ const ColorpickerSettings = () => {
await setCloseToTray(checked)
} catch (error) {
console.error('Failed to update close to tray:', error)
// TODO: Show error notification to user
showToast(SettingsT.t('closeToTray.errorToast'))
}
},
[setCloseToTray],
[setCloseToTray, SettingsT],
)
return (
@@ -6,6 +6,7 @@ import { useSettingsStore, DEFAULT_SETTINGS } from '@stores/settingsStore'
import { useHotkeyConflict } from '@hooks/index'
import { setPickerHotkey } from '@common/ipc'
import { HotkeyInput } from '@components/ui'
import { showToast } from '@stores/toastStore'
const PickerShortcutsSettings = () => {
const SettingsT = useTranslation('settings', { keyPrefix: 'shortcuts.picker' })
@@ -21,10 +22,10 @@ const PickerShortcutsSettings = () => {
await updateSetting('pickerHotkey', hotkey)
} catch (error) {
console.error('Failed to register picker hotkey:', error)
// TODO: Show error notification to user
showToast(SettingsT.t('pickerHotkey.errorToast'))
}
},
[updateSetting],
[updateSetting, SettingsT],
)
return (