From 86d43d40a31721789a2ad6b484e0929760af31d6 Mon Sep 17 00:00:00 2001 From: Doeun Kim Date: Fri, 23 Aug 2024 23:29:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(sds,=20css-utils):=20Checkbox=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(#128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: check 아이콘 추가 * feat: getCssVar * fix: sds 의존성 정리 * feat: Checkbox --- packages/core/css-utils/src/getCssVar.ts | 3 + packages/core/css-utils/src/index.ts | 1 + packages/core/sds/package.json | 14 +++-- .../sds/src/components/Checkbox/Checkbox.tsx | 62 +++++++++++++++++++ .../core/sds/src/components/Checkbox/index.ts | 2 + .../sds/src/components/Checkbox/styles.ts | 58 +++++++++++++++++ .../sds/src/components/Icon/assets/Check.tsx | 17 +++++ .../core/sds/src/components/Icon/constants.ts | 2 + packages/core/sds/src/components/index.ts | 1 + pnpm-lock.yaml | 23 +++++-- 10 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 packages/core/css-utils/src/getCssVar.ts create mode 100644 packages/core/sds/src/components/Checkbox/Checkbox.tsx create mode 100644 packages/core/sds/src/components/Checkbox/index.ts create mode 100644 packages/core/sds/src/components/Checkbox/styles.ts create mode 100644 packages/core/sds/src/components/Icon/assets/Check.tsx diff --git a/packages/core/css-utils/src/getCssVar.ts b/packages/core/css-utils/src/getCssVar.ts new file mode 100644 index 00000000..dd3af14f --- /dev/null +++ b/packages/core/css-utils/src/getCssVar.ts @@ -0,0 +1,3 @@ +export const getCssVar = (cssVariable: string, initial?: unknown) => { + return initial == null ? `var(${cssVariable})` : `var(${cssVariable}, ${initial})`; +}; diff --git a/packages/core/css-utils/src/index.ts b/packages/core/css-utils/src/index.ts index 3d00379f..2805d287 100644 --- a/packages/core/css-utils/src/index.ts +++ b/packages/core/css-utils/src/index.ts @@ -1 +1,2 @@ export { getBorder } from './getBorder'; +export { getCssVar } from './getCssVar'; diff --git a/packages/core/sds/package.json b/packages/core/sds/package.json index dba93ede..5ae865e8 100644 --- a/packages/core/sds/package.json +++ b/packages/core/sds/package.json @@ -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", @@ -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" } } diff --git a/packages/core/sds/src/components/Checkbox/Checkbox.tsx b/packages/core/sds/src/components/Checkbox/Checkbox.tsx new file mode 100644 index 00000000..93b870dd --- /dev/null +++ b/packages/core/sds/src/components/Checkbox/Checkbox.tsx @@ -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 { + label?: ReactNode; + onCheckedChange?: (checked: boolean) => void; +} + +export const Checkbox = forwardRef((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 ( +
+ { + setChecked((prev) => !prev); + })} + css={inputCss} + aria-checked={checked} + aria-disabled={disabled} + tabIndex={0} + {...restProps} + /> + + {checked && } + {label != null && ( + + + + )} +
+ ); +}); + +Checkbox.displayName = 'Checkbox'; diff --git a/packages/core/sds/src/components/Checkbox/index.ts b/packages/core/sds/src/components/Checkbox/index.ts new file mode 100644 index 00000000..43aaf925 --- /dev/null +++ b/packages/core/sds/src/components/Checkbox/index.ts @@ -0,0 +1,2 @@ +export { Checkbox } from './Checkbox'; +export type { CheckboxProps } from './Checkbox'; diff --git a/packages/core/sds/src/components/Checkbox/styles.ts b/packages/core/sds/src/components/Checkbox/styles.ts new file mode 100644 index 00000000..b3fbc37b --- /dev/null +++ b/packages/core/sds/src/components/Checkbox/styles.ts @@ -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', +}); diff --git a/packages/core/sds/src/components/Icon/assets/Check.tsx b/packages/core/sds/src/components/Icon/assets/Check.tsx new file mode 100644 index 00000000..3b043145 --- /dev/null +++ b/packages/core/sds/src/components/Icon/assets/Check.tsx @@ -0,0 +1,17 @@ +import { IconAssetProps } from '../types'; + +export const CheckIcon = (props: IconAssetProps) => { + const { color, size } = props; + + return ( + + + + ); +}; diff --git a/packages/core/sds/src/components/Icon/constants.ts b/packages/core/sds/src/components/Icon/constants.ts index f964d483..bad039c8 100644 --- a/packages/core/sds/src/components/Icon/constants.ts +++ b/packages/core/sds/src/components/Icon/constants.ts @@ -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'; @@ -30,4 +31,5 @@ export const iconMap = { 'x-icon': XIcon, stats: Stats, upload: Upload, + check: CheckIcon, }; diff --git a/packages/core/sds/src/components/index.ts b/packages/core/sds/src/components/index.ts index 307939f8..35e50f8a 100644 --- a/packages/core/sds/src/components/index.ts +++ b/packages/core/sds/src/components/index.ts @@ -7,3 +7,4 @@ export * from './SegmentedControl'; export * from './Accordion'; export * from './Loader'; export * from './Skeleton'; +export * from './Checkbox'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f35cd045..d4c2780c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@emotion/react': specifier: ^11.11.4 - version: 11.11.4(react@18.2.0) + version: 11.11.4(@types/react@18.3.3)(react@18.2.0) '@tanstack/react-query': specifier: ^5.50.1 version: 5.51.1(react@18.2.0) @@ -188,9 +188,24 @@ importers: packages/core/sds: dependencies: + '@emotion/react': + specifier: ^11 + version: 11.11.4(@types/react@18.3.3)(react@18.2.0) '@radix-ui/react-toggle-group': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0)(react@18.2.0) + '@sambad/css-utils': + specifier: workspace:* + version: link:../css-utils + '@sambad/react-utils': + specifier: workspace:* + version: link:../react-utils + react: + specifier: ^18 + version: 18.2.0 + react-dom: + specifier: ^18 + version: 18.2.0(react@18.2.0) devDependencies: '@chromatic-com/storybook': specifier: ^1.6.1 @@ -198,9 +213,6 @@ importers: '@sambad/eslint-config': specifier: workspace:* version: link:../eslint-config - '@sambad/react-utils': - specifier: workspace:^ - version: link:../react-utils '@sambad/types-utils': specifier: workspace:* version: link:../types-utils @@ -2191,7 +2203,7 @@ packages: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: false - /@emotion/react@11.11.4(react@18.2.0): + /@emotion/react@11.11.4(@types/react@18.3.3)(react@18.2.0): resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} peerDependencies: '@types/react': '*' @@ -2207,6 +2219,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 + '@types/react': 18.3.3 hoist-non-react-statics: 3.3.2 react: 18.2.0 transitivePeerDependencies: