Skip to content

Commit 2fe13ca

Browse files
authored
feat: styled dropdown (#1079)
1 parent b4f657d commit 2fe13ca

File tree

6 files changed

+162
-49
lines changed

6 files changed

+162
-49
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@
77
"editor.codeActionsOnSave": {
88
"source.removeUnusedImports": "explicit"
99
},
10+
"[mdx]": {
11+
"editor.codeActionsOnSave": {
12+
"source.removeUnusedImports": "never"
13+
}
14+
},
1015
"vitest.disableWorkspaceWarning": true
1116
}

apps/website/src/global.css

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,25 +118,32 @@
118118
--border: 0 0% 80.2%;
119119
--ring: 0 0% 80.2%;
120120
--input: 0 0% 80.2%;
121-
--shadow-base: -3px -3px 6px #bebebe, 3px 3px 6px #ffffff,
121+
--shadow-base:
122+
-3px -3px 6px #bebebe, 3px 3px 6px #ffffff,
122123
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
123124
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
124-
--shadow-sm: 3px 3px 6px #bebebe, -3px -3px 6px #ffffff,
125+
--shadow-sm:
126+
3px 3px 6px #bebebe, -3px -3px 6px #ffffff,
125127
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
126128
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
127-
--shadow: 4px 4px 8px #bebebe, -4px -4px 8px #ffffff,
129+
--shadow:
130+
4px 4px 8px #bebebe, -4px -4px 8px #ffffff,
128131
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
129132
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
130-
--shadow-md: 5px 5px 10px #bebebe, -5px -5px 10px #ffffff,
133+
--shadow-md:
134+
5px 5px 10px #bebebe, -5px -5px 10px #ffffff,
131135
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
132136
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
133-
--shadow-lg: 6px 6px 12px #bebebe, -6px -6px 12px #ffffff,
137+
--shadow-lg:
138+
6px 6px 12px #bebebe, -6px -6px 12px #ffffff,
134139
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
135140
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
136-
--shadow-xl: 8px 8px 15px #bebebe, -8px -8px 15px #ffffff,
141+
--shadow-xl:
142+
8px 8px 15px #bebebe, -8px -8px 15px #ffffff,
137143
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
138144
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
139-
--shadow-2xl: 10px 10px 20px #bebebe, -10px -10px 20px #ffffff,
145+
--shadow-2xl:
146+
10px 10px 20px #bebebe, -10px -10px 20px #ffffff,
140147
inset 1px 1px 2px rgba(190, 190, 190, 0.5),
141148
inset -1px -1px 2px rgba(255, 255, 255, 0.5);
142149
--shadow-inner: inset 4px 4px 8px #bebebe, inset -2px -2px 4px #ffffff;
@@ -155,20 +162,27 @@
155162
--border: 240 5.9% 20%;
156163
--ring: 240 5.9% 20%;
157164
--input: 240 5.9% 20%;
158-
--shadow-base: -3px -3px 6px #2c2c31, 3px 3px 6px #52525b, inset 1px 1px 2px #2c2c31,
165+
--shadow-base:
166+
-3px -3px 6px #2c2c31, 3px 3px 6px #52525b, inset 1px 1px 2px #2c2c31,
159167
inset -1px -1px 2px #52525b;
160-
--shadow-sm: 3px 3px 6px #2c2c31, -3px -3px 6px #52525b, inset 1px 1px 2px #2c2c31,
168+
--shadow-sm:
169+
3px 3px 6px #2c2c31, -3px -3px 6px #52525b, inset 1px 1px 2px #2c2c31,
161170
inset -1px -1px 2px #52525b;
162-
--shadow: 4px 4px 8px #2c2c31, -4px -4px 8px #52525b, inset 1px 1px 2px #2c2c31,
171+
--shadow:
172+
4px 4px 8px #2c2c31, -4px -4px 8px #52525b, inset 1px 1px 2px #2c2c31,
163173
inset -1px -1px 2px #52525b;
164-
--shadow-md: 5px 5px 10px #2c2c31, -5px -5px 10px #52525b, inset 1px 1px 2px #2c2c31,
174+
--shadow-md:
175+
5px 5px 10px #2c2c31, -5px -5px 10px #52525b, inset 1px 1px 2px #2c2c31,
165176
inset -1px -1px 2px #52525b;
166-
--shadow-lg: 6px 6px 12px #2c2c31, -6px -6px 12px #52525b, inset 1px 1px 2px #2c2c31,
177+
--shadow-lg:
178+
6px 6px 12px #2c2c31, -6px -6px 12px #52525b, inset 1px 1px 2px #2c2c31,
167179
inset -1px -1px 2px #52525b;
168-
--shadow-xl: 8px 8px 15px #2c2c31, -8px -8px 15px #52525b, inset 1px 1px 2px #2c2c31,
180+
--shadow-xl:
181+
8px 8px 15px #2c2c31, -8px -8px 15px #52525b, inset 1px 1px 2px #2c2c31,
182+
inset -1px -1px 2px #52525b;
183+
--shadow-2xl:
184+
10px 10px 20px #2c2c31, -10px -10px 20px #52525b, inset 1px 1px 2px #2c2c31,
169185
inset -1px -1px 2px #52525b;
170-
--shadow-2xl: 10px 10px 20px #2c2c31, -10px -10px 20px #52525b,
171-
inset 1px 1px 2px #2c2c31, inset -1px -1px 2px #52525b;
172186
--shadow-inner: inset 2px 2px 4px #2c2c31, inset -2px -2px 4px #52525b;
173187
--transform-press: scale(0.95);
174188
}

apps/website/src/routes/docs/headless/dropdown/index.mdx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ Customizable popover menu.
3333
description:
3434
'Container for the dropdown menu, responsible for positioning and visibility.',
3535
},
36-
{
37-
name: 'Dropdown.Arrow',
38-
description: 'Optional arrow pointing from the trigger to the dropdown menu.',
39-
},
40-
{
41-
name: 'Dropdown.Content',
42-
description: 'Contains the dropdown options and other interactive elements.',
43-
},
4436
{
4537
name: 'Dropdown.Group',
4638
description: 'Groups multiple dropdown items under a common label.',

apps/website/src/routes/docs/styled/dropdown/examples/hero.tsx

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,84 @@
11
import { component$ } from '@builder.io/qwik';
22
import { Dropdown } from '@qwik-ui/styled';
3+
import {
4+
LuCloud,
5+
LuCreditCard,
6+
LuGithub,
7+
LuKeyboard,
8+
LuLifeBuoy,
9+
LuLogOut,
10+
LuPlus,
11+
LuSettings,
12+
LuUser,
13+
LuUserPlus,
14+
LuUsers,
15+
} from '@qwikest/icons/lucide';
316

417
export default component$(() => {
5-
const actions = [
6-
{ label: 'Merge Branch ⌘+M', disabled: false },
7-
{ label: 'Rebase Branch ⌘+R', disabled: true },
8-
{ label: 'Stash Changes ⌘+S', disabled: false },
9-
{ label: 'Checkout Branch ⌘+B', disabled: false },
10-
{ label: 'Pull Changes ⌘+P', disabled: false },
11-
{ label: 'Create Tag ⌘+T', disabled: false },
12-
];
13-
1418
return (
1519
<Dropdown.Root>
1620
<Dropdown.Trigger>Git Settings</Dropdown.Trigger>
1721
<Dropdown.Popover gutter={8}>
18-
{actions.map((action) => (
19-
<Dropdown.Item key={action.label} disabled={action.disabled}>
20-
{action.label}
21-
</Dropdown.Item>
22-
))}
22+
<Dropdown.Group>
23+
<Dropdown.GroupLabel>My Account</Dropdown.GroupLabel>
24+
<Dropdown.Separator />
25+
{[
26+
{ label: 'Profile', disabled: false, shortcut: '⇧⌘P', icon: <LuUser /> },
27+
{ label: 'Billing', disabled: true, shortcut: '⌘B', icon: <LuCreditCard /> },
28+
{ label: 'Settings', disabled: false, shortcut: '⌘S', icon: <LuSettings /> },
29+
{
30+
label: 'Keyboard shortcuts',
31+
disabled: false,
32+
shortcut: '⌘K',
33+
icon: <LuKeyboard />,
34+
},
35+
].map((action) => (
36+
<Dropdown.Item key={action.label} disabled={action.disabled}>
37+
{action.icon}
38+
{action.label}
39+
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
40+
</Dropdown.Item>
41+
))}
42+
</Dropdown.Group>
43+
<Dropdown.Separator />
44+
<Dropdown.Group>
45+
{[
46+
{ label: 'Team', disabled: false, shortcut: '⌘+T', icon: <LuUsers /> },
47+
{
48+
label: 'Invite users',
49+
disabled: false,
50+
shortcut: '⌘+I',
51+
icon: <LuUserPlus />,
52+
},
53+
{ label: 'New Team', disabled: false, shortcut: '⌘+N', icon: <LuPlus /> },
54+
].map((action) => (
55+
<Dropdown.Item key={action.label} disabled={action.disabled}>
56+
{action.icon}
57+
{action.label}
58+
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
59+
</Dropdown.Item>
60+
))}
61+
</Dropdown.Group>
62+
<Dropdown.Separator />
63+
<Dropdown.Group>
64+
{[
65+
{ label: 'GitHub', disabled: false, shortcut: '⌘+G', icon: <LuGithub /> },
66+
{ label: 'Support', disabled: false, shortcut: '⌘+S', icon: <LuLifeBuoy /> },
67+
{ label: 'API', disabled: true, shortcut: '⌘+A', icon: <LuCloud /> },
68+
].map((action) => (
69+
<Dropdown.Item key={action.label} disabled={action.disabled}>
70+
{action.icon}
71+
{action.label}
72+
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
73+
</Dropdown.Item>
74+
))}
75+
</Dropdown.Group>
76+
<Dropdown.Separator />
77+
<Dropdown.Item>
78+
<LuLogOut />
79+
<span>Log out</span>
80+
<Dropdown.Shortcut>⇧⌘Q</Dropdown.Shortcut>
81+
</Dropdown.Item>
2382
</Dropdown.Popover>
2483
</Dropdown.Root>
2584
);

packages/kit-styled/src/components/breadcrumb/breadcrumb.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const List = component$<PropsOf<'ol'>>((props) => {
1616
<ol
1717
{...props}
1818
class={cn(
19-
' flex flex-wrap items-center gap-1.5 break-words text-sm sm:gap-2.5',
19+
'flex flex-wrap items-center gap-1.5 break-words text-sm sm:gap-2.5',
2020
props.class,
2121
)}
2222
>

packages/kit-styled/src/components/dropdown/dropdown.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { component$, PropsOf, Slot } from '@builder.io/qwik';
22
import { Dropdown as HDropdown } from '@qwik-ui/headless';
3+
import { cn } from '@qwik-ui/utils';
4+
import { buttonVariants } from '../button/button';
5+
import { VariantProps } from 'class-variance-authority';
36

47
type RootProps = PropsOf<typeof HDropdown.Root>;
58

@@ -11,11 +14,12 @@ const Root = (props: RootProps) => {
1114
);
1215
};
1316

14-
type TriggerProps = PropsOf<typeof HDropdown.Trigger>;
17+
type TriggerProps = PropsOf<typeof HDropdown.Trigger> &
18+
VariantProps<typeof buttonVariants>;
1519

16-
const Trigger = component$((props: TriggerProps) => {
20+
const Trigger = component$<TriggerProps>(({ size, look, ...props }) => {
1721
return (
18-
<HDropdown.Trigger {...props}>
22+
<HDropdown.Trigger {...props} class={cn(buttonVariants({ size, look }), props.class)}>
1923
<Slot />
2024
</HDropdown.Trigger>
2125
);
@@ -25,17 +29,37 @@ type PopoverProps = PropsOf<typeof HDropdown.Popover>;
2529

2630
const Popover = component$((props: PopoverProps) => {
2731
return (
28-
<HDropdown.Popover {...props}>
32+
<HDropdown.Popover
33+
{...props}
34+
class={cn(
35+
'min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
36+
'data-[open]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:fade-in-0 data-[closed]:zoom-out-95 data-[open]:zoom-in-95',
37+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
38+
props.class,
39+
)}
40+
>
2941
<Slot />
3042
</HDropdown.Popover>
3143
);
3244
});
3345

34-
type ItemProps = PropsOf<typeof HDropdown.Item>;
46+
type ItemProps = PropsOf<typeof HDropdown.Item> & {
47+
inset?: boolean;
48+
};
3549

36-
const Item = component$(({ ...props }: ItemProps) => {
50+
const Item = component$(({ inset, ...props }: ItemProps) => {
3751
return (
38-
<HDropdown.Item {...props}>
52+
<HDropdown.Item
53+
{...props}
54+
class={cn(
55+
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
56+
'focus:bg-accent focus:text-accent-foreground',
57+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
58+
'[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
59+
inset && 'pl-8',
60+
props.class,
61+
)}
62+
>
3963
<Slot />
4064
</HDropdown.Item>
4165
);
@@ -54,7 +78,9 @@ const ItemIndicator = component$((props: ItemIndicatorProps) => {
5478
type SeparatorProps = PropsOf<typeof HDropdown.Separator>;
5579

5680
const Separator = component$((props: SeparatorProps) => {
57-
return <HDropdown.Separator {...props} />;
81+
return (
82+
<HDropdown.Separator {...props} class={cn('-mx-1 my-1 h-px bg-muted', props.class)} />
83+
);
5884
});
5985

6086
type GroupProps = PropsOf<typeof HDropdown.Group>;
@@ -67,16 +93,32 @@ export const Group = component$((props: GroupProps) => {
6793
);
6894
});
6995

70-
type GroupLabelProps = PropsOf<typeof HDropdown.GroupLabel>;
96+
type GroupLabelProps = PropsOf<typeof HDropdown.GroupLabel> & {
97+
inset?: boolean;
98+
};
7199

72-
const GroupLabel = component$((props: GroupLabelProps) => {
100+
const GroupLabel = component$(({ inset, ...props }: GroupLabelProps) => {
73101
return (
74-
<HDropdown.GroupLabel {...props}>
102+
<HDropdown.GroupLabel
103+
{...props}
104+
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)}
105+
>
75106
<Slot />
76107
</HDropdown.GroupLabel>
77108
);
78109
});
79110

111+
const Shortcut = component$((props: PropsOf<'span'>) => {
112+
return (
113+
<span
114+
{...props}
115+
class={cn('ml-auto text-xs tracking-widest opacity-60', props.class)}
116+
>
117+
<Slot />
118+
</span>
119+
);
120+
});
121+
80122
export const Dropdown = {
81123
Root,
82124
Trigger,
@@ -86,4 +128,5 @@ export const Dropdown = {
86128
ItemIndicator,
87129
Group,
88130
GroupLabel,
131+
Shortcut,
89132
};

0 commit comments

Comments
 (0)