Skip to content

Commit 48760a6

Browse files
committed
Add value selection in Select
1 parent 7da5e90 commit 48760a6

File tree

3 files changed

+157
-5
lines changed

3 files changed

+157
-5
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Box, css } from '@devup-ui/react'
2+
3+
import { IconCheck } from './IconCheck'
4+
5+
interface CheckboxProps {
6+
isChecked: boolean
7+
}
8+
9+
export function Checkbox({ isChecked }: CheckboxProps) {
10+
return (
11+
<Box
12+
bg={isChecked ? '$primary' : '$border'}
13+
borderRadius="4px"
14+
boxSize="18px"
15+
pos="relative"
16+
transition="background-color 0.1s ease-in-out"
17+
>
18+
{isChecked && (
19+
<IconCheck
20+
className={css({
21+
position: 'absolute',
22+
top: '55%',
23+
left: '50%',
24+
transform: 'translate(-50%, -50%)',
25+
})}
26+
/>
27+
)}
28+
</Box>
29+
)
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentProps } from 'react'
2+
3+
export function IconCheck({ ...props }: ComponentProps<'svg'>) {
4+
return (
5+
<svg
6+
color="white"
7+
fill="none"
8+
height="10"
9+
viewBox="0 0 12 10"
10+
width="12"
11+
xmlns="http://www.w3.org/2000/svg"
12+
{...props}
13+
>
14+
<path
15+
clipRule="evenodd"
16+
d="M11.6044 0.825663C12.1054 1.28367 12.1344 2.05481 11.6692 2.54805L5.4787 9.11055C5.24011 9.36348 4.90375 9.50497 4.55315 9.49987C4.20254 9.49477 3.87058 9.34356 3.63968 9.0838L0.30635 5.3338C-0.143922 4.82725 -0.0917767 4.05729 0.42282 3.61405C0.937417 3.17081 1.7196 3.22214 2.16987 3.7287L4.59876 6.4612L9.85463 0.889455C10.3199 0.396214 11.1033 0.367654 11.6044 0.825663Z"
17+
fill="currentColor"
18+
fillRule="evenodd"
19+
/>
20+
</svg>
21+
)
22+
}

packages/components/src/components/Select/index.tsx

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ import clsx from 'clsx'
55
import { ComponentProps, createContext, useContext, useState } from 'react'
66

77
import { Button } from '../Button'
8+
import { IconCheck } from './IconCheck'
9+
10+
type SelectType = 'default' | 'radio' | 'checkbox'
11+
type SelectValue<T extends SelectType> = T extends 'radio' ? string : string[]
812

913
interface SelectProps {
1014
open?: boolean
1115
onOpenChange?: (open: boolean) => void
1216
children: React.ReactNode
17+
type?: SelectType
1318
}
1419

1520
const SelectContext = createContext<{
1621
open: boolean
1722
setOpen: (open: boolean) => void
23+
value: SelectValue<SelectType>
24+
setValue: (value: string) => void
25+
type: SelectType
1826
} | null>(null)
1927

2028
export const useSelect = () => {
@@ -26,17 +34,44 @@ export const useSelect = () => {
2634
}
2735

2836
export function Select({
37+
type = 'default',
2938
children,
3039
open: openProp,
3140
onOpenChange,
3241
}: SelectProps) {
3342
const [open, setOpen] = useState(openProp ?? false)
43+
const [value, setValue] = useState<SelectValue<typeof type>>(
44+
type === 'checkbox' ? [] : '',
45+
)
46+
3447
const handleOpenChange = (open: boolean) => {
3548
setOpen(open)
3649
onOpenChange?.(open)
3750
}
51+
52+
const handleValueChange = (nextValue: string) => {
53+
if (type === 'default') return
54+
if (type === 'radio') {
55+
setValue(nextValue)
56+
return
57+
}
58+
if (Array.isArray(value) && value.includes(nextValue)) {
59+
setValue(value.filter((v) => v !== nextValue))
60+
} else {
61+
setValue([...value, nextValue])
62+
}
63+
}
64+
3865
return (
39-
<SelectContext.Provider value={{ open, setOpen: handleOpenChange }}>
66+
<SelectContext.Provider
67+
value={{
68+
open,
69+
setOpen: handleOpenChange,
70+
value,
71+
setValue: handleValueChange,
72+
type,
73+
}}
74+
>
4075
<Box display="inline-block" pos="relative">
4176
{children}
4277
</Box>
@@ -105,15 +140,26 @@ export function SelectOption({
105140
children,
106141
...props
107142
}: SelectOptionProps) {
108-
const { setOpen } = useSelect()
143+
const { setOpen, setValue, value, type } = useSelect()
144+
145+
const handleClose = () => {
146+
if (type === 'checkbox') return
147+
setOpen(false)
148+
}
149+
109150
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
110151
if (onClick) {
111152
onClick(e)
112153
return
113154
}
114-
setOpen(false)
155+
setValue(children as string)
156+
handleClose()
115157
}
116158

159+
const isChecked = Array.isArray(value)
160+
? value.includes(children as string)
161+
: value === children
162+
117163
return (
118164
<Flex
119165
_hover={
@@ -123,16 +169,70 @@ export function SelectOption({
123169
}
124170
alignItems="center"
125171
borderRadius="8px"
126-
color={disabled ? '$selectDisabled' : '$title'}
172+
color={disabled ? '$selectDisabled' : isChecked ? '$primary' : '$title'}
127173
cursor={disabled ? 'default' : 'pointer'}
174+
gap={
175+
{
176+
checkbox: '10px',
177+
radio: '6px',
178+
default: '0',
179+
}[type]
180+
}
128181
h="40px"
129182
onClick={disabled ? undefined : handleClick}
130183
px="10px"
131184
styleOrder={1}
132185
transition="background-color 0.1s ease-in-out"
133-
typography="inputText"
186+
typography={isChecked ? 'inputBold' : 'inputText'}
134187
{...props}
135188
>
189+
{
190+
{
191+
checkbox: (
192+
<Box
193+
bg={isChecked ? '$primary' : '$border'}
194+
borderRadius="4px"
195+
boxSize="18px"
196+
pos="relative"
197+
transition="background-color 0.1s ease-in-out"
198+
>
199+
{isChecked && (
200+
<IconCheck
201+
className={css({
202+
position: 'absolute',
203+
top: '55%',
204+
left: '50%',
205+
transform: 'translate(-50%, -50%)',
206+
})}
207+
/>
208+
)}
209+
</Box>
210+
),
211+
radio: (
212+
<>
213+
{isChecked && (
214+
<Box
215+
borderRadius="4px"
216+
boxSize="18px"
217+
pos="relative"
218+
transition="background-color 0.1s ease-in-out"
219+
>
220+
<IconCheck
221+
className={css({
222+
position: 'absolute',
223+
top: '55%',
224+
left: '50%',
225+
transform: 'translate(-50%, -50%)',
226+
color: '$primary',
227+
})}
228+
/>
229+
</Box>
230+
)}
231+
</>
232+
),
233+
default: null,
234+
}[type]
235+
}
136236
{children}
137237
</Flex>
138238
)

0 commit comments

Comments
 (0)