diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d6b3c6e7..7cfc1bc8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -41,6 +41,7 @@ module.exports = { 'no-relative-import-paths/no-relative-import-paths': [ 'warn', { 'allowSameFolder': true, 'prefix': '@', 'rootDir': 'src' } - ] + ], + 'arrow-body-style': ['warn', 'as-needed'], } }; diff --git a/src/form/Checkbox/Checkbox.story.tsx b/src/form/Checkbox/Checkbox.story.tsx index 9b0fbd1d..4d1c3909 100644 --- a/src/form/Checkbox/Checkbox.story.tsx +++ b/src/form/Checkbox/Checkbox.story.tsx @@ -11,6 +11,30 @@ export const Simple = () => { return ; }; +export const WithoutLabel = () => { + const [state, setState] = useState(true); + return ; +}; + +export const LabelPosition = () => { + const [state, setState] = useState(true); + return ( + +
+ +
+
+ +
+
+ ); +}; + export const Intermediate = () => { const [state, setState] = useState(true); return ( @@ -81,8 +105,26 @@ export const Sizes = () => { export const Disabled = () => { const [state, setState] = useState(true); + const [state2, setState2] = useState(false); return ( - + <> +
+ +
+
+ +
+ ); }; diff --git a/src/form/Checkbox/Checkbox.tsx b/src/form/Checkbox/Checkbox.tsx index 89d48716..b4e2602e 100644 --- a/src/form/Checkbox/Checkbox.tsx +++ b/src/form/Checkbox/Checkbox.tsx @@ -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 { /** @@ -20,6 +21,11 @@ export interface CheckboxProps { */ label?: string; + /** + * Label position of checkbox. + */ + labelPosition?: 'start' | 'end'; + /** * Whether the checkbox is disabled or not. */ @@ -91,13 +97,14 @@ export const Checkbox: FC = 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 @@ -114,15 +121,39 @@ export const Checkbox: FC = forwardRef( unchecked: { pathLength: 0 } }; + const handleOnChange = useCallback(() => { + if (!disabled && onChange) { + onChange(!checked); + } + }, [disabled, onChange, checked]); + return ( -
+
+ {labelPosition === 'start' && label && ( + + )} = forwardRef( height={16} > @@ -157,7 +192,7 @@ export const Checkbox: FC = forwardRef( d={intermediatePath} fill="transparent" strokeWidth="1" - className={theme.check} + className={theme.check.base} variants={checkVariants} style={{ pathLength, opacity }} custom={checked} @@ -167,7 +202,11 @@ export const Checkbox: FC = forwardRef( d={checkedPath} fill="transparent" strokeWidth="1" - className={theme.check} + className={twMerge( + theme.check.base, + disabled && theme.check.disabled, + checked && theme.check.checked + )} variants={checkVariants} style={{ pathLength, opacity }} custom={checked} @@ -175,23 +214,16 @@ export const Checkbox: FC = forwardRef( )} - {label && ( - { - if (!disabled && onChange) { - onChange?.(!checked); - } - }} - > - {label} - + {labelPosition === 'end' && label && ( + )}
); diff --git a/src/form/Checkbox/CheckboxLabel.tsx b/src/form/Checkbox/CheckboxLabel.tsx new file mode 100644 index 00000000..7f42afbc --- /dev/null +++ b/src/form/Checkbox/CheckboxLabel.tsx @@ -0,0 +1,41 @@ +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 = ({ + label, + size, + disabled, + checked, + onChange, + labelClassName, + theme +}) => ( + { + if (!disabled && onChange) { + onChange(); + } + }} + > + {label} + +); diff --git a/src/form/Checkbox/CheckboxTheme.ts b/src/form/Checkbox/CheckboxTheme.ts index f4046591..afa0180f 100644 --- a/src/form/Checkbox/CheckboxTheme.ts +++ b/src/form/Checkbox/CheckboxTheme.ts @@ -1,10 +1,10 @@ -import TWConfig from '@/utils/Theme/config'; - export interface CheckboxTheme { base: string; label: { base: string; clickable: string; + disabled: string; + checked: string; sizes: { small: string; medium: string; @@ -12,10 +12,21 @@ export interface CheckboxTheme { [key: string]: string; }; }; - border: string; - check: string; - checkbox: string; - disabled: string; + border: { + base: string; + disabled: string; + checked: string; + }; + check: { + base: string; + disabled: string; + checked: string; + }; + checkbox: { + base: string; + disabled: string; + checked: string; + }; sizes: { small: string; medium: string; @@ -43,9 +54,11 @@ export interface CheckboxTheme { } const baseTheme: Partial = { - base: 'inline-flex items-center w-full', + base: 'inline-flex items-center w-full group', label: { - base: 'ml-2.5 w-full', + base: 'dark:text-gray-400 light:text-gray-700 ml-2.5 w-full', + checked: 'checked dark:text-gray-100 light:text-gray-900', + disabled: 'cursor-not-allowed dark:text-gray-600 light:text-gray-400', clickable: 'cursor-pointer', sizes: { small: 'text-sm', @@ -53,47 +66,101 @@ const baseTheme: Partial = { large: 'text-lg' } }, - check: '', - border: '', - checkbox: - 'flex items-center justify-center cursor-pointer focus-visible:outline-none', - disabled: 'opacity-60 cursor-not-allowed', + check: { + base: 'stroke-white', + checked: '', + disabled: 'cursor-not-allowed' + }, + border: { + base: 'stroke-gray-400 light:stroke-gray-700', + checked: 'stroke-blue-500', + disabled: 'cursor-not-allowed stroke-gray-500' + }, + checkbox: { + base: 'fill-transparent flex items-center justify-center cursor-pointer focus-visible:outline-none', + checked: 'fill-blue-500 checked', + disabled: 'fill-transparent disabled' + }, sizes: { small: '[&>svg]:w-3 [&>svg]:h-3', medium: '[&>svg]:w-4 [&>svg]:h-4', large: '[&>svg]:w-5 [&>svg]:h-5' } }; - export const checkboxTheme: CheckboxTheme = { ...baseTheme, - checkbox: [baseTheme.checkbox, 'fill-transparent border border-surface'].join( - ' ' - ), - check: [baseTheme.check, 'stroke-primary'].join(' '), + checkbox: { + ...baseTheme.checkbox, + base: [ + baseTheme.checkbox.base, + 'border border-surface', + '[&.checked.disabled]:fill-gray-400' + ].join(' '), + checked: [ + baseTheme.checkbox.checked, + 'group-hover:fill-blue-400', + 'light:group-hover:fill-blue-600', + 'light:group-hover:[&.disabled]:fill-gray-400' + ].join(' '), + disabled: [ + baseTheme.checkbox.disabled, + 'group-hover:transparent', + 'light:group-hover:transparent' + ].join(' ') + }, + check: { + ...baseTheme.check, + base: [ + baseTheme.check.base, + 'group-hover:stroke-black light:group-hover:stroke-white' + ].join(' '), + disabled: [ + baseTheme.check.disabled, + 'stroke-black light:stroke-white group-hover:stroke-black ' + ].join(' ') + }, + border: { + ...baseTheme.border, + base: [ + baseTheme.border.base, + 'dark:group-hover:stroke-blue-300', + 'light:group-hover:stroke-blue-600' + ].join(' '), + disabled: [ + baseTheme.border.disabled, + 'dark:group-hover:stroke-gray-500', + 'light:group-hover:stroke-gray-400' + ].join(' ') + }, label: { ...baseTheme.label, - base: [baseTheme.label.base, 'text-text-primary'].join(' ') + base: [ + baseTheme.label.base, + 'text-text-primary dark:group-hover:text-blue-300 light:group-hover:text-blue-400' + ].join(' '), + checked: [baseTheme.label.checked, 'group-hover:text-gray-100'].join(' '), + disabled: [ + baseTheme.label.disabled, + 'light:group-hover:text-gray-400', + 'dark:group-hover:text-gray-600' + ].join(' ') }, boxVariants: { hover: { - strokeWidth: 2, - stroke: TWConfig.colors.slate[400] - }, - pressed: { scale: 0.95 }, - checked: { - stroke: TWConfig.colors.primary['active'] + strokeWidth: 1 }, - unchecked: { - stroke: TWConfig.colors.slate[500] - } + pressed: { scale: 0.95 } } } as CheckboxTheme; export const legacyCheckboxTheme: CheckboxTheme = { ...baseTheme, - checkbox: [baseTheme.checkbox, 'fill-transparent'].join(' '), - check: [baseTheme.check, 'stroke-[var(--checkbox-check-stroke)]'].join(' '), + checkbox: { base: [baseTheme.checkbox, 'fill-transparent'].join(' ') }, + check: { + base: [baseTheme.check.base, 'stroke-[var(--checkbox-check-stroke)]'].join( + ' ' + ) + }, label: { ...baseTheme.label, base: [