Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkbox Component styling #178

Merged
merged 10 commits into from
Jun 21, 2024
4 changes: 2 additions & 2 deletions docs/blocks/administration/Team.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const TeamGeneral = () => {

const checkboxTheme: CheckboxTheme = {
...defaultCheckboxTheme,
check: 'stroke-white',
checkMark: 'stroke-white',
zinchenkoivan marked this conversation as resolved.
Show resolved Hide resolved
boxVariants: {
...defaultCheckboxTheme.boxVariants,
checked: {
Expand Down Expand Up @@ -459,7 +459,7 @@ export const TeamRoles = () => {

const checkboxTheme: CheckboxTheme = {
...defaultCheckboxTheme,
check: 'stroke-white',
checkMark: 'stroke-white',
boxVariants: {
...defaultCheckboxTheme.boxVariants,
checked: {
Expand Down
44 changes: 43 additions & 1 deletion src/form/Checkbox/Checkbox.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ export const Simple = () => {
return <Checkbox checked={state} label="Check me" onChange={setState} />;
};

export const WithoutLabel = () => {
const [state, setState] = useState(true);
return <Checkbox checked={state} onChange={setState} />;
};

export const LabelPosition = () => {
zinchenkoivan marked this conversation as resolved.
Show resolved Hide resolved
const [state, setState] = useState(true);
return (
<Fragment>
<div style={{ padding: 20 }}>
<Checkbox
checked={state}
label="Start label"
labelPosition="start"
onChange={setState}
/>
</div>
<div style={{ padding: 20 }}>
<Checkbox checked={state} label="End label" onChange={setState} />
</div>
</Fragment>
);
};

export const Intermediate = () => {
const [state, setState] = useState(true);
return (
Expand Down Expand Up @@ -81,8 +105,26 @@ export const Sizes = () => {

export const Disabled = () => {
const [state, setState] = useState(true);
const [state2, setState2] = useState(false);
return (
<Checkbox checked={state} label="Disabled" onChange={setState} disabled />
<>
<div style={{ padding: 20 }}>
<Checkbox
checked={state}
label="Disabled"
onChange={setState}
disabled
/>
</div>
<div style={{ padding: 20 }}>
<Checkbox
checked={state2}
label="Disabled"
onChange={setState2}
disabled
/>
</div>
</>
);
};

Expand Down
84 changes: 58 additions & 26 deletions src/form/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { FC, forwardRef, LegacyRef } from 'react';
import React, { FC, forwardRef, LegacyRef, useCallback } from 'react';
import { motion, useMotionValue, useTransform } from 'framer-motion';
import { twMerge } from 'tailwind-merge';
import { CheckboxTheme } from './CheckboxTheme';
import { useComponentTheme } from '@/utils';
import { CheckboxLabel } from './CheckboxLabel';

export interface CheckboxProps {
/**
Expand All @@ -20,6 +21,11 @@ export interface CheckboxProps {
*/
label?: string;

/**
* Label position of checkbox.
*/
labelPosition?: 'start' | 'end';

/**
* Whether the checkbox is disabled or not.
*/
Expand Down Expand Up @@ -91,13 +97,14 @@ export const Checkbox: FC<CheckboxProps & CheckboxRef> = forwardRef(
label,
disabled,
size = 'medium',
labelPosition = 'end',
onChange,
onBlur,
className,
containerClassName,
labelClassName,
borderPath = 'M 0 0 L 0 16 L 16 16 L 16 0 Z',
checkedPath = 'M 5.36396 8.17792 L 7.34236 9.91424 L 10.6044 5.832',
borderPath = 'M 1 0 L 16 0 C 16.552 0 17 0.448 17 1 L 17 15 C 17 15.552 16.552 16 16 16 L 1 16 C 0.448 16 0 15.552 0 15 L 0 1 C 0 0.448 0.448 0 1 0 Z',
checkedPath = 'M 4 8 L 8 12 L 12 4',
intermediatePath = 'M 5.36396 8.17792 L 10.6044 8.17792',
theme: customTheme,
...rest
Expand All @@ -114,15 +121,39 @@ export const Checkbox: FC<CheckboxProps & CheckboxRef> = forwardRef(
unchecked: { pathLength: 0 }
};

const handleOnChange = useCallback(() => {
if (!disabled && onChange) {
onChange(!checked);
}
}, [disabled, onChange, checked]);

return (
<div className={twMerge(theme.base, containerClassName)}>
<div
className={twMerge(
theme.base,
containerClassName,
checked && 'checked'
)}
>
{labelPosition === 'start' && label && (
<CheckboxLabel
label={label}
size={size}
checked={checked}
disabled={disabled}
onChange={handleOnChange}
labelClassName={twMerge('mr-2.5', labelClassName)}
theme={theme}
/>
)}
<motion.div
{...rest}
ref={ref}
tabIndex={disabled ? -1 : 0}
className={twMerge(
theme.checkbox,
disabled && theme.disabled,
theme.checkbox.base,
checked && theme.checkbox.checked,
disabled && theme.checkbox.disabled,
theme.sizes[size],
className
)}
Expand All @@ -148,7 +179,11 @@ export const Checkbox: FC<CheckboxProps & CheckboxRef> = forwardRef(
height={16}
>
<motion.path
className={theme.border}
className={twMerge(
theme.border.base,
checked && theme.border.checked,
disabled && theme.border.disabled
)}
d={borderPath}
variants={theme.boxVariants}
/>
Expand All @@ -157,7 +192,7 @@ export const Checkbox: FC<CheckboxProps & CheckboxRef> = forwardRef(
d={intermediatePath}
fill="transparent"
strokeWidth="1"
className={theme.check}
className={theme.checkMark.base}
variants={checkVariants}
style={{ pathLength, opacity }}
custom={checked}
Expand All @@ -167,31 +202,28 @@ export const Checkbox: FC<CheckboxProps & CheckboxRef> = forwardRef(
d={checkedPath}
fill="transparent"
strokeWidth="1"
className={theme.check}
className={twMerge(
theme.checkMark.base,
disabled && theme.checkMark.disabled,
checked && theme.checkMark.checked
)}
variants={checkVariants}
style={{ pathLength, opacity }}
custom={checked}
/>
)}
</motion.svg>
</motion.div>
{label && (
<span
className={twMerge(
theme.label.base,
theme.label.sizes[size],
disabled && theme.disabled,
!disabled && onChange && theme.label.clickable,
labelClassName
)}
onClick={() => {
if (!disabled && onChange) {
onChange?.(!checked);
}
}}
>
{label}
</span>
{labelPosition === 'end' && label && (
<CheckboxLabel
label={label}
size={size}
checked={checked}
disabled={disabled}
onChange={handleOnChange}
labelClassName={twMerge('ml-2.5', labelClassName)}
theme={theme}
/>
)}
</div>
);
Expand Down
43 changes: 43 additions & 0 deletions src/form/Checkbox/CheckboxLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { FC } from 'react';
import { twMerge } from 'tailwind-merge';
import { CheckboxTheme } from './CheckboxTheme';

interface CheckboxLabelProps {
label: string;
size: 'small' | 'medium' | 'large' | string;
disabled?: boolean;
checked?: boolean;
onChange?: () => void;
labelClassName?: string;
theme: CheckboxTheme;
}

export const CheckboxLabel: FC<CheckboxLabelProps> = ({
label,
size,
disabled,
checked,
onChange,
labelClassName,
theme
}) => {
return (
<span
zinchenkoivan marked this conversation as resolved.
Show resolved Hide resolved
className={twMerge(
theme.label.base,
theme.label.sizes[size],
checked && theme.label.checked,
disabled && theme.label.disabled,
!disabled && onChange && theme.label.clickable,
labelClassName
)}
onClick={() => {
if (!disabled && onChange) {
onChange();
}
}}
>
{label}
</span>
);
};
Loading
Loading