-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7e33e96
commit 58f38b0
Showing
8 changed files
with
364 additions
and
1 deletion.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
packages/co-design-core/src/components/NumberBox/Count.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { RefObject, forwardRef, useImperativeHandle, useState } from 'react'; | ||
import { Typography, TypographyProps } from '../Typography'; | ||
import { useNumberBoxContext } from './NumberBoxContext'; | ||
import { useUpdateEffect } from '@co-design/hooks'; | ||
|
||
export interface CountRef { | ||
count: number; | ||
} | ||
|
||
interface CountProps extends TypographyProps<'span'> {} | ||
|
||
export const Count = forwardRef((props: CountProps, ref: RefObject<CountRef>) => { | ||
const { isDecrease, currentCount, deletedCount } = useNumberBoxContext(); | ||
const [count, setCount] = useState(currentCount); | ||
|
||
useImperativeHandle(ref, () => ({ | ||
count, | ||
})); | ||
|
||
useUpdateEffect(() => { | ||
if (isDecrease && count >= deletedCount) { | ||
setCount((prev) => prev - 1); | ||
} | ||
}, [currentCount]); | ||
|
||
return ( | ||
<Typography variant="caption" {...props}> | ||
{count} | ||
</Typography> | ||
); | ||
}); |
92 changes: 92 additions & 0 deletions
92
packages/co-design-core/src/components/NumberBox/NumberBox.style.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { createStyles } from '@co-design/styles'; | ||
|
||
interface NumberBoxStyles { | ||
checked?: boolean; | ||
disabled?: boolean; | ||
size?: 'small' | 'medium'; | ||
} | ||
|
||
export default createStyles((theme, { checked, disabled, size }: NumberBoxStyles, getRef) => { | ||
const checkmark = getRef('checkmark'); | ||
const wrapper = getRef('wrapper'); | ||
|
||
const positionSizes = { | ||
small: theme.foundations.tokens.size.size_02, | ||
medium: theme.foundations.tokens.size.size_04, | ||
}; | ||
|
||
const iconSizes = { | ||
small: theme.foundations.tokens.size.size_08, | ||
medium: theme.foundations.tokens.size.size_09, | ||
}; | ||
|
||
return { | ||
wrapper: { | ||
ref: wrapper, | ||
position: 'relative', | ||
cursor: 'pointer', | ||
userSelect: 'none', | ||
verticalAlign: 'middle', | ||
zIndex: 0, | ||
width: 'fit-content', | ||
...(!disabled | ||
? { | ||
'&:hover': { | ||
[`.${checkmark}`]: { | ||
fill: checked ? theme.foundations.tokens.color.bg.bg_primary_hover : theme.foundations.tokens.color.bg.bg_base_light, | ||
}, | ||
}, | ||
'&:focus': { | ||
[`.${checkmark}`]: { | ||
color: theme.foundations.tokens.color.border.border_primary, | ||
}, | ||
}, | ||
} | ||
: {}), | ||
}, | ||
input: { | ||
position: 'absolute', | ||
width: '0px', | ||
height: '0px', | ||
opacity: 0, | ||
}, | ||
childrenWrapper: { | ||
position: 'relative', | ||
width: 'fit-content', | ||
}, | ||
checkWrapper: { | ||
position: 'absolute', | ||
top: theme.fn.size({ size, sizes: positionSizes }), | ||
right: theme.fn.size({ size, sizes: positionSizes }), | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
checkmark: { | ||
ref: checkmark, | ||
color: theme.foundations.tokens.color.border.border_strong, | ||
fill: theme.foundations.tokens.color.bg.bg_surface_01, | ||
width: theme.fn.size({ size, sizes: iconSizes }), | ||
height: theme.fn.size({ size, sizes: iconSizes }), | ||
}, | ||
checked: { | ||
[`.${checkmark}`]: { | ||
color: theme.foundations.tokens.color.icon.icon_inverse_white, | ||
fill: theme.foundations.tokens.color.bg.bg_primary, | ||
}, | ||
}, | ||
disabled: { | ||
cursor: 'not-allowed', | ||
[`.${checkmark}`]: { | ||
// TODO: replace with color token | ||
color: checked ? 'rgba(0,0,0,0)' : theme.foundations.tokens.color.border.border_disabled, | ||
fill: theme.foundations.tokens.color.bg.bg_disabled, | ||
}, | ||
}, | ||
number: { | ||
position: 'absolute', | ||
color: disabled ? theme.foundations.tokens.color.icon.icon_disabled : theme.foundations.tokens.color.text.text_light, | ||
opacity: checked ? 1 : 0, | ||
}, | ||
}; | ||
}); |
97 changes: 97 additions & 0 deletions
97
packages/co-design-core/src/components/NumberBox/NumberBox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import React, { useEffect, useRef, useState } from 'react'; | ||
import { ClassNames, CoComponentProps } from '@co-design/styles'; | ||
import { NumberBoxIcon } from './NumberBoxIcon'; | ||
import { View } from '../View'; | ||
import useStyles from './NumberBox.style'; | ||
import { Count, CountRef } from './Count'; | ||
import { useNumberBoxContext } from './NumberBoxContext'; | ||
|
||
export type NumberBoxStylesNames = ClassNames<typeof useStyles>; | ||
|
||
export interface NumberBoxProps extends CoComponentProps<NumberBoxStylesNames>, Omit<React.ComponentPropsWithoutRef<'input'>, 'onClick' | 'size'> { | ||
/** NumberBox 를 감싸는 요소를 클릭했을 때 발생할 이벤트를 지정합니다. */ | ||
onClick?: React.MouseEventHandler<HTMLLabelElement>; | ||
|
||
size?: 'small' | 'medium'; | ||
|
||
/** NumberBox 를 감싸는 요소에 속성을 전달합니다. */ | ||
wrapperProps?: React.ComponentPropsWithoutRef<'label'> & { [key: string]: any }; | ||
} | ||
|
||
export const NumberBox = ({ | ||
name, | ||
value, | ||
checked = false, | ||
disabled = false, | ||
onChange, | ||
onClick, | ||
size = 'small', | ||
className = '', | ||
style, | ||
wrapperProps, | ||
co, | ||
overrideStyles, | ||
children, | ||
...props | ||
}: NumberBoxProps) => { | ||
const { actions } = useNumberBoxContext(); | ||
const countRef = useRef<CountRef>({ count: 0 }); | ||
|
||
const [check, setCheck] = useState(checked); | ||
const { classes, cx } = useStyles( | ||
{ checked: check, disabled, size }, | ||
{ | ||
overrideStyles, | ||
name: 'NumberBox', | ||
}, | ||
); | ||
|
||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const checked = event.target.checked; | ||
|
||
if (checked) { | ||
actions.increase(); | ||
} else { | ||
actions.delete(countRef.current.count); | ||
} | ||
|
||
setCheck(checked); | ||
onChange?.(event); | ||
}; | ||
|
||
useEffect(() => { | ||
setCheck(checked); | ||
}, [checked]); | ||
|
||
return ( | ||
<View | ||
component="label" | ||
onClick={onClick} | ||
className={cx(classes.wrapper, className, { | ||
[classes.checked]: check, | ||
[classes.disabled]: disabled, | ||
})} | ||
co={co} | ||
style={style} | ||
{...wrapperProps} | ||
> | ||
<input | ||
type="checkbox" | ||
className={classes.input} | ||
name={name} | ||
checked={check} | ||
disabled={disabled} | ||
value={value} | ||
onChange={handleChange} | ||
{...props} | ||
/> | ||
<View className={classes.childrenWrapper}> | ||
{children} | ||
<View className={classes.checkWrapper}> | ||
<NumberBoxIcon className={classes.checkmark} /> | ||
{check && <Count ref={countRef} className={classes.number} />} | ||
</View> | ||
</View> | ||
</View> | ||
); | ||
}; |
43 changes: 43 additions & 0 deletions
43
packages/co-design-core/src/components/NumberBox/NumberBoxContext.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { createContext, ReactNode, useContext, useState } from 'react'; | ||
|
||
interface NumberBoxActions { | ||
increase: () => void; | ||
delete: (count: number) => void; | ||
} | ||
|
||
type INumberBoxContext = { isDecrease: boolean; currentCount: number; deletedCount: number; actions: NumberBoxActions }; | ||
|
||
const NumberBoxContext = createContext<INumberBoxContext>(undefined as unknown as INumberBoxContext); | ||
|
||
export const useNumberBoxContext = () => { | ||
const context = useContext(NumberBoxContext); | ||
if (!context) { | ||
throw new Error('useMe must be used within a NumberBoxProvider'); | ||
} | ||
return context; | ||
}; | ||
|
||
interface Props { | ||
children: ReactNode; | ||
} | ||
|
||
export const NumberBoxProvider = ({ children }: Props) => { | ||
const [currentCount, setCurrentCount] = useState(0); | ||
const [deletedCount, setDeletedCount] = useState<number>(); | ||
const [isDecrease, setIsDecrease] = useState<boolean>(false); | ||
|
||
const actions = { | ||
increase: () => { | ||
setCurrentCount((prev) => prev + 1); | ||
setDeletedCount(undefined); | ||
setIsDecrease(false); | ||
}, | ||
delete: (count: number) => { | ||
setCurrentCount((prev) => prev - 1); | ||
setIsDecrease(true); | ||
setDeletedCount(count); | ||
}, | ||
}; | ||
|
||
return <NumberBoxContext.Provider value={{ isDecrease, currentCount, deletedCount, actions }}>{children}</NumberBoxContext.Provider>; | ||
}; |
9 changes: 9 additions & 0 deletions
9
packages/co-design-core/src/components/NumberBox/NumberBoxIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { SVGProps } from 'react'; | ||
|
||
export const NumberBoxIcon = (props: SVGProps<SVGSVGElement>) => { | ||
return ( | ||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
<rect x="1.6665" y="1" width="18" height="18" rx="9" fill="currentFill" stroke="currentColor" strokeWidth="2" /> | ||
</svg> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { NumberBox } from './NumberBox'; | ||
|
||
export type { NumberBoxProps } from './NumberBox'; |
89 changes: 89 additions & 0 deletions
89
packages/co-design-core/src/components/NumberBox/stories/NumberBox.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { useState } from 'react'; | ||
import { Meta, StoryObj } from '@storybook/react'; | ||
import { NumberBox } from '../NumberBox'; | ||
import { NumberBoxProvider } from '../NumberBoxContext'; | ||
import { Stack } from '../../Stack'; | ||
import { Button } from '../../Button'; | ||
|
||
type Story = StoryObj<typeof NumberBox>; | ||
|
||
export default { | ||
title: '@co-design/core/NumberBox', | ||
component: NumberBox, | ||
argTypes: { | ||
checked: { | ||
control: { type: 'boolean' }, | ||
}, | ||
disabled: { | ||
control: { type: 'boolean' }, | ||
}, | ||
size: { | ||
options: ['small', 'medium'], | ||
control: { type: 'inline-radio' }, | ||
}, | ||
}, | ||
} as Meta<typeof NumberBox>; | ||
|
||
export const Default: Story = { | ||
render: (props) => { | ||
return ( | ||
<NumberBoxProvider> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
</NumberBoxProvider> | ||
); | ||
}, | ||
}; | ||
|
||
export const MultiSelect: Story = { | ||
render: (props) => { | ||
return ( | ||
<NumberBoxProvider> | ||
<Stack> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
<NumberBox {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
</Stack> | ||
</NumberBoxProvider> | ||
); | ||
}, | ||
}; | ||
|
||
export const Disabled: Story = { | ||
render: (props) => { | ||
const [disabled, setDisabled] = useState(false); | ||
|
||
return ( | ||
<NumberBoxProvider> | ||
<Stack> | ||
<Button | ||
onClick={() => { | ||
setDisabled((prev) => !prev); | ||
}} | ||
> | ||
Toggle disabled | ||
</Button> | ||
<NumberBox disabled={disabled} {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
<NumberBox disabled={disabled} {...props}> | ||
<img src="https://via.placeholder.com/150" /> | ||
</NumberBox> | ||
</Stack> | ||
</NumberBoxProvider> | ||
); | ||
}, | ||
}; |
1 change: 0 additions & 1 deletion
1
packages/co-design-core/src/components/Radio/stories/Radio.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
import React from 'react'; | ||
import Radio from '../Radio'; | ||
import { Meta, StoryObj } from '@storybook/react'; | ||
|
||
|