Skip to content

Commit

Permalink
add tap area to checkbox radio and toggle #242
Browse files Browse the repository at this point in the history
  • Loading branch information
ukorvl committed Apr 17, 2024
1 parent 1b5c8c8 commit cf26003
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 16 additions & 2 deletions src/components/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -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<CheckboxProps> = ({
labelPlacement = LABEL_PLACEMENT.right,
overrides: baseOverrides,
inputRef,
...props
}) => {
const checkboxOverrides = getCheckboxOverrides();
const overrides = getMergedOverrides(checkboxOverrides, baseOverrides);
const checkboxRef = useRef<HTMLInputElement>(null);
const finalRef = inputRef || checkboxRef;

return <BaseCheckbox {...props} overrides={overrides} labelPlacement={labelPlacement} checkmarkType="default" />;
return (
<TapArea onClick={() => finalRef.current?.click()}>
<BaseCheckbox
{...props}
overrides={overrides}
labelPlacement={labelPlacement}
checkmarkType="default"
inputRef={finalRef}
/>
</TapArea>
);
};

export { LABEL_PLACEMENT };
Expand Down
13 changes: 10 additions & 3 deletions src/components/radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -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<RadioProps> = ({ overrides: baseOverrides, ...props }) => {
const Radio: FC<RadioProps> = ({ overrides: baseOverrides, inputRef, ...props }) => {
const radioOverrides = getRadioOverrides();
const overrides = getMergedOverrides(radioOverrides, baseOverrides);
const radioRef = useRef<HTMLInputElement>(null);
const finalRef = inputRef || radioRef;

return <BaseRadio overrides={overrides} {...props} />;
return (
<TapArea onClick={() => finalRef.current?.click()}>
<BaseRadio overrides={overrides} inputRef={finalRef} {...props} />
</TapArea>
);
};

export default Radio;
22 changes: 14 additions & 8 deletions src/components/toggle/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -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<ToggleProps> = ({
labelPlacement = LABEL_PLACEMENT.right,
overrides: baseOverrides,
disabled,
inputRef,
...props
}) => {
const checkboxOverrides = getToggleOverrides(disabled);
const overrides = getMergedOverrides(checkboxOverrides, baseOverrides);
const toggleRef = useRef<HTMLInputElement>(null);
const finalRef = inputRef || toggleRef;

return (
<BaseCheckbox
{...props}
disabled={disabled}
overrides={overrides}
labelPlacement={labelPlacement}
checkmarkType="toggle"
/>
<TapArea onClick={() => finalRef.current?.click()}>
<BaseCheckbox
{...props}
overrides={overrides}
inputRef={finalRef}
labelPlacement={labelPlacement}
checkmarkType="toggle"
/>
</TapArea>
);
};

Expand Down
43 changes: 43 additions & 0 deletions src/shared/ui/tap-area/TapArea.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>) => {
onClick();
event.stopPropagation();
event.preventDefault();
};

return (
<div onClick={onClickWithStopPropagation} className={outerStyle}>
{children}
</div>
);
};

export default TapArea;
1 change: 1 addition & 0 deletions src/shared/ui/tap-area/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as TapArea } from "./TapArea";
7 changes: 7 additions & 0 deletions src/shared/utils/isTouch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const isTouch = () => {
if (typeof window === "undefined") {
return false;
}

return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0;
};

0 comments on commit cf26003

Please sign in to comment.