From 1f2ff3d14810a71763a1184fa94d025be3aa7b24 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Wed, 21 Oct 2026 22:05:43 +0300 Subject: [PATCH 01/26] refactor(checkbox): use native input events BREAKING CHANGE: Replace active/defaultActive/onCheck with checked/defaultChecked/onChange. Use checkedIcon instead of icon. Refs now target the native input. Remove CheckboxAppearanceName and the blue/green/red appearance variants. Rename size keys small/medium/large to sm/md/lg. CheckboxShape and CheckboxAppearance now use new structures; consumers of the old exports and values must migrate. --- ui-parts/checkbox/src/checkbox.component.tsx | 86 +++++++------------ ui-parts/checkbox/src/checkbox.interfaces.ts | 18 ++-- .../src/hidden-input/hidden-input.css.ts | 6 +- .../checkbox/stories/checkbox.stories.tsx | 10 +-- 4 files changed, 47 insertions(+), 73 deletions(-) diff --git a/ui-parts/checkbox/src/checkbox.component.tsx b/ui-parts/checkbox/src/checkbox.component.tsx index 3121e030a..80a306059 100644 --- a/ui-parts/checkbox/src/checkbox.component.tsx +++ b/ui-parts/checkbox/src/checkbox.component.tsx @@ -1,12 +1,11 @@ -import type { KeyboardEvent } from 'react' -import type { MouseEvent } from 'react' +import type { ChangeEvent } from 'react' import type { ReactNode } from 'react' import type { CheckboxProps } from './checkbox.interfaces.js' import { clsx } from 'clsx' -import { useEffect } from 'react' import { useState } from 'react' +import { useId } from 'react' import { boxBaseStyles } from './box/index.js' import { checkBaseStyles } from './check/index.js' @@ -19,82 +18,56 @@ import { checkboxAppearances } from './styles/index.js' import { checkboxShapes } from './styles/index.js' export const Checkbox = ({ - onCheck, - children, - active, - defaultActive = false, - labelPosition = 'end', + onChange, + checked, + defaultChecked = false, + disabled, + indeterminate, appearance = checkboxAppearances.blue, shape = checkboxShapes.medium, - icon, - checkedIcon = icon, className, classNames, - onClick, - onKeyDown, + labelPosition = 'end', + checkedIcon, + indeterminateIcon, + children, ref, - tabIndex, + id, ...props }: CheckboxProps): ReactNode => { - const [isChecked, setIsChecked] = useState(active ?? defaultActive) - const checked = active ?? isChecked + const [isChecked, setIsChecked] = useState(checked ?? defaultChecked) + const localChecked = checked ?? isChecked - useEffect(() => { - if (active !== undefined) { - setIsChecked(active) - } - }, [active]) - - const handleCheck = (): void => { - const nextChecked = !checked + const handleChange = (event: ChangeEvent) => { + const nextChecked = event.currentTarget.checked - if (active === undefined) { + if (checked === undefined) { setIsChecked(nextChecked) } - - onCheck?.(nextChecked) + onChange?.(event) } - const handleClick = (event: MouseEvent): void => { - onClick?.(event) - - if (!event.defaultPrevented) { - handleCheck() - } - } - - const handleKeyDown = (event: KeyboardEvent): void => { - onKeyDown?.(event) - - if (!event.defaultPrevented && event.key === ' ') { - event.preventDefault() - handleCheck() - } - } + const generatedId = useId() + const inputId = id ?? generatedId return (
{checkedIcon}
-
{children} -
+
) } diff --git a/ui-parts/checkbox/src/checkbox.interfaces.ts b/ui-parts/checkbox/src/checkbox.interfaces.ts index b63eaeaad..41401378e 100644 --- a/ui-parts/checkbox/src/checkbox.interfaces.ts +++ b/ui-parts/checkbox/src/checkbox.interfaces.ts @@ -1,6 +1,6 @@ -import type { HTMLAttributes } from 'react' -import type { Ref } from 'react' -import type { ReactNode } from 'react' +import type { InputHTMLAttributes } from 'react' +import type { Ref } from 'react' +import type { ReactNode } from 'react' export interface CheckboxClassNames { box?: string @@ -26,15 +26,13 @@ export interface CheckboxShape { label: string } -export interface CheckboxProps extends HTMLAttributes { - active?: boolean +export interface CheckboxProps extends InputHTMLAttributes { + indeterminate?: boolean appearance?: CheckboxAppearance + shape?: CheckboxShape checkedIcon?: ReactNode + indeterminateIcon?: ReactNode classNames?: CheckboxClassNames - defaultActive?: boolean - onCheck?: (checked: boolean) => void labelPosition?: 'bottom' | 'end' | 'start' | 'top' - shape?: CheckboxShape - icon?: ReactNode - ref?: Ref + ref?: Ref } diff --git a/ui-parts/checkbox/src/hidden-input/hidden-input.css.ts b/ui-parts/checkbox/src/hidden-input/hidden-input.css.ts index af1982597..998115f30 100644 --- a/ui-parts/checkbox/src/hidden-input/hidden-input.css.ts +++ b/ui-parts/checkbox/src/hidden-input/hidden-input.css.ts @@ -4,6 +4,8 @@ export const hiddenInputStyles = style({ position: 'absolute', cursor: 'pointer', opacity: 0, - width: 0, - height: 0, + inset: 0, + width: '100%', + height: '100%', + zIndex: 1, }) diff --git a/ui-parts/checkbox/stories/checkbox.stories.tsx b/ui-parts/checkbox/stories/checkbox.stories.tsx index c80523dc4..73af924f8 100644 --- a/ui-parts/checkbox/stories/checkbox.stories.tsx +++ b/ui-parts/checkbox/stories/checkbox.stories.tsx @@ -29,8 +29,8 @@ const meta: Meta = { ), tags: ['autodocs'], argTypes: { - active: { - name: 'Active', + checked: { + name: 'Checked', description: 'Is the checkbox checked', control: { type: 'boolean' }, }, @@ -64,8 +64,8 @@ type Story = StoryObj export const Base: Story = { name: 'Базовый', args: { - onCheck: () => undefined, - active: false, + onChange: () => undefined, + checked: false, labelPosition: 'start', appearance: checkboxAppearances.blue, shape: checkboxShapes.medium, @@ -76,7 +76,7 @@ export const Base: Story = { export const Custom: Story = { name: 'Кастомный', args: { - defaultActive: true, + defaultChecked: true, labelPosition: 'end', appearance: checkboxAppearances.blue, shape: checkboxShapes.medium, From ae2e476a20c7d96091bc241da031decf9061549a Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Thu, 22 Oct 2026 01:00:19 +0300 Subject: [PATCH 02/26] feat(checkbox): indeterminate state --- ui-parts/checkbox/src/checkbox.component.tsx | 26 ++++++++++++++++--- .../checkbox/stories/checkbox.stories.tsx | 17 ++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/ui-parts/checkbox/src/checkbox.component.tsx b/ui-parts/checkbox/src/checkbox.component.tsx index 80a306059..d8c787be5 100644 --- a/ui-parts/checkbox/src/checkbox.component.tsx +++ b/ui-parts/checkbox/src/checkbox.component.tsx @@ -1,9 +1,12 @@ -import type { ChangeEvent } from 'react' import type { ReactNode } from 'react' +import type { ChangeEvent } from 'react' import type { CheckboxProps } from './checkbox.interfaces.js' import { clsx } from 'clsx' +import { useEffect } from 'react' +import { useImperativeHandle } from 'react' +import { useRef } from 'react' import { useState } from 'react' import { useId } from 'react' @@ -38,6 +41,21 @@ export const Checkbox = ({ const [isChecked, setIsChecked] = useState(checked ?? defaultChecked) const localChecked = checked ?? isChecked + const inputRef = useRef(null) + useImperativeHandle(ref, () => inputRef.current!, []) + + useEffect(() => { + const input = inputRef.current + if (!input) { + return + } + + input.indeterminate = indeterminate ?? false + }, [indeterminate, localChecked]) + + const isMarked = indeterminate || localChecked + const mark = indeterminate ? indeterminateIcon : checkedIcon + const handleChange = (event: ChangeEvent) => { const nextChecked = event.currentTarget.checked @@ -60,7 +78,7 @@ export const Checkbox = ({ )} > - {checkedIcon} + {mark}