Skip to content

Commit

Permalink
feat(sds, css-utils): Checkbox 컴포넌트 추가 (#128)
Browse files Browse the repository at this point in the history
* feat: check 아이콘 추가

* feat: getCssVar

* fix: sds 의존성 정리

* feat: Checkbox
  • Loading branch information
Doeunnkimm authored Aug 23, 2024
1 parent b322842 commit 86d43d4
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 9 deletions.
3 changes: 3 additions & 0 deletions packages/core/css-utils/src/getCssVar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const getCssVar = (cssVariable: string, initial?: unknown) => {
return initial == null ? `var(${cssVariable})` : `var(${cssVariable}, ${initial})`;
};
1 change: 1 addition & 0 deletions packages/core/css-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { getBorder } from './getBorder';
export { getCssVar } from './getCssVar';
14 changes: 10 additions & 4 deletions packages/core/sds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@
"storybook": "storybook dev -p 6007",
"build-storybook": "storybook build"
},
"peerDependencies": {
"react": "^18",
"react-dom": "^18",
"@emotion/react": "^11"
},
"dependencies": {
"@radix-ui/react-toggle-group": "^1.1.0",
"@sambad/react-utils": "workspace:*",
"@sambad/css-utils": "workspace:*"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.6.1",
"@sambad/eslint-config": "workspace:*",
"@sambad/react-utils": "workspace:^",
"@sambad/types-utils": "workspace:*",
"@sambad/typescript-config": "workspace:*",
"@storybook/addon-essentials": "^8.2.2",
Expand All @@ -37,8 +46,5 @@
"eslint-plugin-storybook": "^0.8.0",
"storybook": "^8.2.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@radix-ui/react-toggle-group": "^1.1.0"
}
}
62 changes: 62 additions & 0 deletions packages/core/sds/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useId } from '@sambad/react-utils';
import { CSSProperties, forwardRef, InputHTMLAttributes, ReactNode } from 'react';

import { useControllableState } from '@sds/hooks';
import { colors } from '@sds/theme';
import { composeEventHandlers } from '@sds/utils';

import { Icon } from '../Icon';
import { Txt } from '../Typography';

import { checkboxRootCss, checkedVariants, iconCss, indicatorCss, inputCss, labelCss } from './styles';

export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
label?: ReactNode;
onCheckedChange?: (checked: boolean) => void;
}

export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
const { label, checked: checkedFromProps, defaultChecked, onCheckedChange, disabled, ...restProps } = props;

const id = useId();

const [checked = false, setChecked] = useControllableState({
prop: checkedFromProps,
defaultProp: defaultChecked,
onChange: onCheckedChange,
});

const rootStyle = {
...checkedVariants[checked ? 'checked' : 'default'],
} as CSSProperties;

return (
<div css={checkboxRootCss} style={rootStyle}>
<input
ref={ref}
id={id}
type="checkbox"
role="checkbox"
checked={checked}
disabled={disabled}
onClick={composeEventHandlers(props.onClick, () => {
setChecked((prev) => !prev);
})}
css={inputCss}
aria-checked={checked}
aria-disabled={disabled}
tabIndex={0}
{...restProps}
/>
<span css={indicatorCss} />
{checked && <Icon css={iconCss} name="check" size={12} color={colors.white} />}
{label != null && (
<Txt typography="subtitle1" css={labelCss}>
<label htmlFor={id}>{label}</label>
</Txt>
)}
</div>
);
});

Checkbox.displayName = 'Checkbox';
2 changes: 2 additions & 0 deletions packages/core/sds/src/components/Checkbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Checkbox } from './Checkbox';
export type { CheckboxProps } from './Checkbox';
58 changes: 58 additions & 0 deletions packages/core/sds/src/components/Checkbox/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { css } from '@emotion/react';
import { getCssVar } from '@sambad/css-utils';
import { CSSProperties } from 'react';

import { colors, size } from '@sds/theme';

const inputBackgroundColorVar = '--sambad-checkbox-input-background-color';
const inputBoxShadowVar = '--sambad-checkbox-input-box-shadow';

interface CheckedVariants {
[inputBackgroundColorVar]: CSSProperties['backgroundColor'];
[inputBoxShadowVar]: CSSProperties['boxShadow'];
}

export const checkedVariants: Record<'checked' | 'default', CheckedVariants> = {
default: {
[inputBackgroundColorVar]: 'transparent',
[inputBoxShadowVar]: `inset 0 0 0 1.5px ${colors.grey500}`,
},
checked: {
[inputBackgroundColorVar]: colors.black,
[inputBoxShadowVar]: 'none',
},
};

export const inputCss = css({
// NOTE: hidden 요소로 기능을 담당하고, 스타일은 indicator에서 담당하므로 width, height를 동일하게
width: '18px',
height: '18px',
opacity: 0,
});

export const indicatorCss = css({
width: '18px',
height: '18px',
boxShadow: getCssVar(inputBoxShadowVar),
backgroundColor: getCssVar(inputBackgroundColorVar),
borderRadius: '3px',
pointerEvents: 'none',
position: 'absolute',
transition: 'background-color 0.1s ease-in-out',
});

export const labelCss = css({
marginLeft: size['6xs'],
});

export const checkboxRootCss = css({
display: 'inline-flex',
alignItems: 'center',
position: 'relative',
});

export const iconCss = css({
position: 'absolute',
pointerEvents: 'none',
left: '3px',
});
17 changes: 17 additions & 0 deletions packages/core/sds/src/components/Icon/assets/Check.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IconAssetProps } from '../types';

export const CheckIcon = (props: IconAssetProps) => {
const { color, size } = props;

return (
<svg width={size} height={size} viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
id="Vector 5492 (Stroke)"
fillRule="evenodd"
clipRule="evenodd"
d="M11.7193 1.06225C12.0496 1.33661 12.0949 1.8268 11.8206 2.1571L6.74016 8.27332C5.84693 9.34866 4.21491 9.40215 3.25319 8.38762L0.213232 5.18074C-0.0821752 4.86911 -0.0690255 4.37701 0.242603 4.0816C0.554231 3.78619 1.04633 3.79934 1.34174 4.11097L4.38169 7.31785C4.70227 7.65603 5.24628 7.6382 5.54402 7.27975L10.6244 1.16353C10.8988 0.833228 11.389 0.787881 11.7193 1.06225Z"
fill={color}
/>
</svg>
);
};
2 changes: 2 additions & 0 deletions packages/core/sds/src/components/Icon/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AngleSmallDownIcon } from './assets/AngleSmallDown';
import { AngleSmallUpIcon } from './assets/AngleSmallUp';
import { AngleUpIcon } from './assets/AngleUp';
import { CaretDownIcon } from './assets/CaretDown';
import { CheckIcon } from './assets/Check';
import { CommentsIcon } from './assets/CommentsIcon';
import { CrownIcon } from './assets/Crown';
import { Landscape } from './assets/Landscape';
Expand All @@ -30,4 +31,5 @@ export const iconMap = {
'x-icon': XIcon,
stats: Stats,
upload: Upload,
check: CheckIcon,
};
1 change: 1 addition & 0 deletions packages/core/sds/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './SegmentedControl';
export * from './Accordion';
export * from './Loader';
export * from './Skeleton';
export * from './Checkbox';
23 changes: 18 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 86d43d4

Please sign in to comment.