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

feat(sds, css-utils): Checkbox 컴포넌트 추가 #128

Merged
merged 4 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading