From cf260032e90956aabe96bc1e89bb31d62bc42445 Mon Sep 17 00:00:00 2001 From: ukorvl Date: Wed, 10 Apr 2024 19:56:45 +0400 Subject: [PATCH] add tap area to checkbox radio and toggle #242 --- .editorconfig | 2 ++ src/components/checkbox/Checkbox.tsx | 18 ++++++++++-- src/components/radio/Radio.tsx | 13 +++++++-- src/components/toggle/Toggle.tsx | 22 ++++++++------ src/shared/ui/tap-area/TapArea.tsx | 43 ++++++++++++++++++++++++++++ src/shared/ui/tap-area/index.tsx | 1 + src/shared/utils/isTouch.ts | 7 +++++ 7 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/shared/ui/tap-area/TapArea.tsx create mode 100644 src/shared/ui/tap-area/index.tsx create mode 100644 src/shared/utils/isTouch.ts diff --git a/.editorconfig b/.editorconfig index 907d607b..38ca95ab 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,5 @@ charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true +indent_style = space +indent_size = 2 diff --git a/src/components/checkbox/Checkbox.tsx b/src/components/checkbox/Checkbox.tsx index f3f0669c..de0f94a8 100644 --- a/src/components/checkbox/Checkbox.tsx +++ b/src/components/checkbox/Checkbox.tsx @@ -1,18 +1,32 @@ -import { FC } from "react"; +import { FC, useRef } from "react"; import { Checkbox as BaseCheckbox, LABEL_PLACEMENT } from "baseui/checkbox"; import { getCheckboxOverrides } from "./overrides"; import { getMergedOverrides } from "../../shared/utils/getMergedOverrides"; import { CheckboxProps } from "./types"; +import { TapArea } from "../../shared/ui/tap-area"; const Checkbox: FC = ({ labelPlacement = LABEL_PLACEMENT.right, overrides: baseOverrides, + inputRef, ...props }) => { const checkboxOverrides = getCheckboxOverrides(); const overrides = getMergedOverrides(checkboxOverrides, baseOverrides); + const checkboxRef = useRef(null); + const finalRef = inputRef || checkboxRef; - return ; + return ( + finalRef.current?.click()}> + + + ); }; export { LABEL_PLACEMENT }; diff --git a/src/components/radio/Radio.tsx b/src/components/radio/Radio.tsx index 6e85caac..a218c778 100644 --- a/src/components/radio/Radio.tsx +++ b/src/components/radio/Radio.tsx @@ -1,15 +1,22 @@ -import { FC } from "react"; +import { FC, useRef } from "react"; import { Radio as BaseRadio, RadioProps as BaseRadioProps } from "baseui/radio"; import { getRadioOverrides } from "./overrides"; import { getMergedOverrides } from "../../shared/utils/getMergedOverrides"; +import { TapArea } from "../../shared/ui/tap-area"; export type RadioProps = BaseRadioProps; -const Radio: FC = ({ overrides: baseOverrides, ...props }) => { +const Radio: FC = ({ overrides: baseOverrides, inputRef, ...props }) => { const radioOverrides = getRadioOverrides(); const overrides = getMergedOverrides(radioOverrides, baseOverrides); + const radioRef = useRef(null); + const finalRef = inputRef || radioRef; - return ; + return ( + finalRef.current?.click()}> + + + ); }; export default Radio; diff --git a/src/components/toggle/Toggle.tsx b/src/components/toggle/Toggle.tsx index c3186f32..696074a8 100644 --- a/src/components/toggle/Toggle.tsx +++ b/src/components/toggle/Toggle.tsx @@ -1,26 +1,32 @@ -import { FC } from "react"; +import { FC, useRef } from "react"; import { Checkbox as BaseCheckbox, LABEL_PLACEMENT } from "baseui/checkbox"; import { getToggleOverrides } from "./overrides"; import { getMergedOverrides } from "../../shared/utils/getMergedOverrides"; import { ToggleProps } from "./types"; +import { TapArea } from "../../shared/ui/tap-area"; const Toggle: FC = ({ labelPlacement = LABEL_PLACEMENT.right, overrides: baseOverrides, disabled, + inputRef, ...props }) => { const checkboxOverrides = getToggleOverrides(disabled); const overrides = getMergedOverrides(checkboxOverrides, baseOverrides); + const toggleRef = useRef(null); + const finalRef = inputRef || toggleRef; return ( - + finalRef.current?.click()}> + + ); }; diff --git a/src/shared/ui/tap-area/TapArea.tsx b/src/shared/ui/tap-area/TapArea.tsx new file mode 100644 index 00000000..b55d4894 --- /dev/null +++ b/src/shared/ui/tap-area/TapArea.tsx @@ -0,0 +1,43 @@ +import { MouseEvent, ReactNode } from "react"; +import { StyleObject, useStyletron } from "styletron-react"; +import { isTouch } from "../../utils/isTouch"; + +type TapAreaProps = { + tapAreaSize?: number; + children: ReactNode; + onClick: () => void; +}; + +const getOuterStyle = (tapAreaSize: TapAreaProps["tapAreaSize"]): StyleObject => ({ + minWidth: `${tapAreaSize}px`, + height: `${tapAreaSize}px`, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + userSelect: "none", +}); + +const isTouchDevice = isTouch(); // we can optimistically check this once because it won't change in 99.9% cases + +const TapArea = ({ tapAreaSize = 48, children, onClick }: TapAreaProps) => { + const [css] = useStyletron(); + + if (!isTouchDevice) { + return <>{children}; + } + + const outerStyle = css(getOuterStyle(tapAreaSize)); + const onClickWithStopPropagation = (event: MouseEvent) => { + onClick(); + event.stopPropagation(); + event.preventDefault(); + }; + + return ( +
+ {children} +
+ ); +}; + +export default TapArea; diff --git a/src/shared/ui/tap-area/index.tsx b/src/shared/ui/tap-area/index.tsx new file mode 100644 index 00000000..ad8d961a --- /dev/null +++ b/src/shared/ui/tap-area/index.tsx @@ -0,0 +1 @@ +export { default as TapArea } from "./TapArea"; diff --git a/src/shared/utils/isTouch.ts b/src/shared/utils/isTouch.ts new file mode 100644 index 00000000..dc99885d --- /dev/null +++ b/src/shared/utils/isTouch.ts @@ -0,0 +1,7 @@ +export const isTouch = () => { + if (typeof window === "undefined") { + return false; + } + + return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0; +};