Skip to content

Commit 7da5e90

Browse files
committed
Create basic Select structure
1 parent 239bb98 commit 7da5e90

File tree

2 files changed

+162
-6
lines changed

2 files changed

+162
-6
lines changed

packages/components/src/components/Select/Select.stories.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Meta, StoryObj } from '@storybook/react-vite'
22

3-
import { Select } from '.'
3+
import {
4+
Select,
5+
SelectContainer,
6+
SelectDivider,
7+
SelectOption,
8+
SelectTrigger,
9+
} from '.'
410

511
type Story = StoryObj<typeof meta>
612

@@ -18,9 +24,19 @@ const meta: Meta<typeof Select> = {
1824
}
1925

2026
export const Default: Story = {
21-
args: {
22-
placeholder: 'Input text',
23-
},
27+
args: {},
28+
render: (args) => (
29+
<Select {...args}>
30+
<SelectTrigger>Select</SelectTrigger>
31+
<SelectContainer>
32+
<SelectOption>Option 1</SelectOption>
33+
<SelectOption>Option 2</SelectOption>
34+
<SelectDivider />
35+
<SelectOption>Option 3</SelectOption>
36+
<SelectOption disabled>Option 4</SelectOption>
37+
</SelectContainer>
38+
</Select>
39+
),
2440
}
2541

2642
export default meta
Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,143 @@
1-
export function Select() {
2-
return <></>
1+
'use client'
2+
3+
import { Box, css, Flex, VStack } from '@devup-ui/react'
4+
import clsx from 'clsx'
5+
import { ComponentProps, createContext, useContext, useState } from 'react'
6+
7+
import { Button } from '../Button'
8+
9+
interface SelectProps {
10+
open?: boolean
11+
onOpenChange?: (open: boolean) => void
12+
children: React.ReactNode
13+
}
14+
15+
const SelectContext = createContext<{
16+
open: boolean
17+
setOpen: (open: boolean) => void
18+
} | null>(null)
19+
20+
export const useSelect = () => {
21+
const context = useContext(SelectContext)
22+
if (!context) {
23+
throw new Error('useSelect must be used within a Select')
24+
}
25+
return context
26+
}
27+
28+
export function Select({
29+
children,
30+
open: openProp,
31+
onOpenChange,
32+
}: SelectProps) {
33+
const [open, setOpen] = useState(openProp ?? false)
34+
const handleOpenChange = (open: boolean) => {
35+
setOpen(open)
36+
onOpenChange?.(open)
37+
}
38+
return (
39+
<SelectContext.Provider value={{ open, setOpen: handleOpenChange }}>
40+
<Box display="inline-block" pos="relative">
41+
{children}
42+
</Box>
43+
</SelectContext.Provider>
44+
)
45+
}
46+
47+
export function SelectTrigger({
48+
className,
49+
children,
50+
...props
51+
}: ComponentProps<typeof Button>) {
52+
const { open, setOpen } = useSelect()
53+
const handleClick = () => {
54+
setOpen(!open)
55+
}
56+
57+
return (
58+
<Button
59+
className={clsx(
60+
css({
61+
pos: 'relative',
62+
borderRadius: '8px',
63+
}),
64+
className,
65+
)}
66+
onClick={handleClick}
67+
{...props}
68+
>
69+
{children}
70+
</Button>
71+
)
72+
}
73+
74+
export function SelectContainer({ children, ...props }: ComponentProps<'div'>) {
75+
const { open } = useSelect()
76+
if (!open) return null
77+
return (
78+
<VStack
79+
bg="$inputBg"
80+
border="1px solid $border"
81+
borderRadius="8px"
82+
bottom="-4px"
83+
boxShadow="0 2px 2px 0 $base10"
84+
gap="6px"
85+
p="10px"
86+
pos="absolute"
87+
styleOrder={1}
88+
transform="translateY(100%)"
89+
userSelect="none"
90+
w="232px"
91+
{...props}
92+
>
93+
{children}
94+
</VStack>
95+
)
96+
}
97+
98+
interface SelectOptionProps extends ComponentProps<'div'> {
99+
disabled?: boolean
100+
}
101+
102+
export function SelectOption({
103+
disabled,
104+
onClick,
105+
children,
106+
...props
107+
}: SelectOptionProps) {
108+
const { setOpen } = useSelect()
109+
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
110+
if (onClick) {
111+
onClick(e)
112+
return
113+
}
114+
setOpen(false)
115+
}
116+
117+
return (
118+
<Flex
119+
_hover={
120+
!disabled && {
121+
bg: '$primaryBg',
122+
}
123+
}
124+
alignItems="center"
125+
borderRadius="8px"
126+
color={disabled ? '$selectDisabled' : '$title'}
127+
cursor={disabled ? 'default' : 'pointer'}
128+
h="40px"
129+
onClick={disabled ? undefined : handleClick}
130+
px="10px"
131+
styleOrder={1}
132+
transition="background-color 0.1s ease-in-out"
133+
typography="inputText"
134+
{...props}
135+
>
136+
{children}
137+
</Flex>
138+
)
139+
}
140+
141+
export function SelectDivider({ ...props }: ComponentProps<'div'>) {
142+
return <Box bg="$border" h="1px" styleOrder={1} w="100%" {...props} />
3143
}

0 commit comments

Comments
 (0)