mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Один инструмент вместо девяти dev-зависимостей. Замеры на этом репо (483 файла, медиана из 3 прогонов, один и тот же компьютер): - eslint . ~20.4s -> biome lint . ~2s - prettier --check . ~8.8s -> biome format . ~0.2s - суммарно линт+формат ~29s -> biome check . ~2-3s (~10x) Паритет с прежними правилами: - форматирование бинарно совместимо с Prettier-конфигом (biome format --write тронул 9 файлов из ~480 — микроразличия); - запрет window.confirm/alert/open/prompt и navigator.clipboard (Telegram WebView) перенесён GritQL-плагином biome-plugins/telegram-webview-guards.grit c теми же сообщениями и исключениями для src/platform/** и clipboard.ts; плагин сразу нашёл window.open в Wheel.tsx под старым eslint-disable — переведён на biome-ignore; - react-hooks-правила: useExhaustiveDependencies/useHookAtTopLevel; - новые для проекта правила (a11y и пр.) не включались или понижены до warning, чтобы миграция не смешивалась с чисткой кода — включать можно отдельными PR. Ограничения (задокументированы): сортировка tailwind-классов (prettier-plugin-tailwindcss) в Biome пока nursery — не включена; CSS исключён из Biome (парсер спотыкается о Tailwind-синтаксис globals.css) — файл один и правится редко. CI не меняется: имена npm-скриптов lint/format:check сохранены. lint-staged переведён на biome check --write.
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
|
import { forwardRef, type ComponentPropsWithoutRef } from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import { usePlatform } from '@/platform';
|
|
|
|
export interface SwitchProps
|
|
extends Omit<ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, 'onChange'> {
|
|
label?: string;
|
|
description?: string;
|
|
onChange?: (checked: boolean) => void;
|
|
haptic?: boolean;
|
|
}
|
|
|
|
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
|
|
({ className, label, description, onChange, onCheckedChange, haptic = true, ...props }, ref) => {
|
|
const { haptic: platformHaptic } = usePlatform();
|
|
|
|
const handleCheckedChange = (checked: boolean) => {
|
|
if (haptic) {
|
|
platformHaptic.impact('light');
|
|
}
|
|
onCheckedChange?.(checked);
|
|
onChange?.(checked);
|
|
};
|
|
|
|
const switchElement = (
|
|
<SwitchPrimitive.Root
|
|
ref={ref}
|
|
className={cn(
|
|
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full',
|
|
'border-2 border-transparent transition-colors duration-200',
|
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-dark-950',
|
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
'data-[state=checked]:bg-accent-500 data-[state=unchecked]:bg-dark-700',
|
|
className,
|
|
)}
|
|
onCheckedChange={handleCheckedChange}
|
|
{...props}
|
|
>
|
|
<SwitchPrimitive.Thumb asChild>
|
|
<span
|
|
className={cn(
|
|
'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform duration-200',
|
|
'data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
|
|
)}
|
|
/>
|
|
</SwitchPrimitive.Thumb>
|
|
</SwitchPrimitive.Root>
|
|
);
|
|
|
|
if (!label) {
|
|
return switchElement;
|
|
}
|
|
|
|
return (
|
|
<label className="flex cursor-pointer items-center justify-between gap-4">
|
|
<div className="min-w-0 flex-1">
|
|
<span className="block text-sm font-medium text-dark-100">{label}</span>
|
|
{description && <span className="mt-0.5 block text-sm text-dark-400">{description}</span>}
|
|
</div>
|
|
{switchElement}
|
|
</label>
|
|
);
|
|
},
|
|
);
|
|
|
|
Switch.displayName = 'Switch';
|