Skip to content

Commit 0ff77c3

Browse files
feat(dropdown): adds test cases to dropdown menu
1 parent bda241a commit 0ff77c3

File tree

12 files changed

+665
-486
lines changed

12 files changed

+665
-486
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { component$, useStyles$ } from '@builder.io/qwik';
2+
3+
import { Dropdown } from '@qwik-ui/headless';
4+
import { LuCheck } from '@qwikest/icons/lucide';
5+
import styles from '../snippets/dropdown.css?inline';
6+
7+
export default component$(() => {
8+
useStyles$(styles);
9+
10+
const actions = [
11+
{ label: 'Commit ⌘+K', disabled: false },
12+
{ label: 'Push ⇧+⌘+K', disabled: false },
13+
{ label: 'Update Project ⌘+T', disabled: true },
14+
];
15+
16+
const checkboxItems = ['Show Git Log', 'Show History'];
17+
18+
const radioItems = ['main', 'develop'];
19+
20+
return (
21+
<Dropdown.Root data-testid="dropdown">
22+
<Dropdown.Trigger class="dropdown-trigger">Git Settings</Dropdown.Trigger>
23+
<Dropdown.Popover>
24+
<Dropdown.Content class="dropdown-content">
25+
<Dropdown.Group class="dropdown-group">
26+
<Dropdown.GroupLabel class="dropdown-group-label">
27+
Actions
28+
</Dropdown.GroupLabel>
29+
{actions.map((action) => (
30+
<Dropdown.Item
31+
key={action.label}
32+
class="dropdown-item"
33+
disabled={action.disabled}
34+
>
35+
{action.label}
36+
</Dropdown.Item>
37+
))}
38+
</Dropdown.Group>
39+
<Dropdown.Separator />
40+
{checkboxItems.map((item) => {
41+
return (
42+
<Dropdown.CheckboxItem key={item} class="dropdown-item">
43+
<Dropdown.ItemIndicator>
44+
<LuCheck />
45+
</Dropdown.ItemIndicator>
46+
{item}
47+
</Dropdown.CheckboxItem>
48+
);
49+
})}
50+
<Dropdown.Separator />
51+
<Dropdown.RadioGroup class="dropdown-group" defaultValue="main">
52+
{radioItems.map((item) => {
53+
return (
54+
<Dropdown.RadioItem key={item} class="dropdown-item" value={item}>
55+
<Dropdown.ItemIndicator>
56+
<LuCheck />
57+
</Dropdown.ItemIndicator>
58+
{item}
59+
</Dropdown.RadioItem>
60+
);
61+
})}
62+
</Dropdown.RadioGroup>
63+
</Dropdown.Content>
64+
</Dropdown.Popover>
65+
</Dropdown.Root>
66+
);
67+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
:root {
2+
--dropdown-width: 14rem;
3+
}
4+
5+
.dropdown {
6+
min-width: var(--dropdown-width);
7+
}
8+
9+
.dropdown-trigger {
10+
width: 100%;
11+
height: 100%;
12+
border: 2px dotted black;
13+
min-height: 44px;
14+
max-width: var(--dropdown-width);
15+
padding-block: 0.5rem;
16+
display: flex;
17+
justify-content: center;
18+
align-items: center;
19+
margin-top: 0.25rem;
20+
padding: 0.5rem 1rem;
21+
}
22+
23+
.dropdown-trigger:hover {
24+
background: rgba(54, 25, 25, 0.08);
25+
}
26+
27+
.dropdown-trigger:focus-visible {
28+
outline: 2px solid black;
29+
outline-offset: 2px;
30+
}
31+
32+
.dropdown-content {
33+
min-width: var(--dropdown-width);
34+
border: 2px dotted black;
35+
margin-top: 0.5rem;
36+
flex-direction: column;
37+
display: flex;
38+
row-gap: 0.25rem;
39+
padding: 1rem 1rem 1rem 1.5rem;
40+
}
41+
42+
.dropdown-item {
43+
display: flex;
44+
align-items: center;
45+
position: relative;
46+
}
47+
48+
.dropdown-item [data-indicator] {
49+
position: absolute;
50+
right: 0.5rem;
51+
}
52+
53+
.dropdown-group {
54+
display: flex;
55+
flex-direction: column;
56+
row-gap: 0.25rem;
57+
}
58+
59+
.dropdown-group-label {
60+
font-size: 0.875rem;
61+
line-height: 1.25rem;
62+
color: rgba(54, 25, 25, 0.9);
63+
padding-top: 0.5rem;
64+
}
65+
66+
[data-highlighted] {
67+
background-color: rgba(54, 25, 25, 0.08);
68+
outline: 2px dotted black;
69+
}
70+
71+
[data-disabled] {
72+
opacity: 0.6;
73+
background: hsl(var(--foreground) / 0.05);
74+
}

packages/kit-headless/src/components/dropdown/dropdown-checkbox-item.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ export const HDropdownCheckboxItem = component$((props: DropdownCheckboxItemProp
7777
ref={itemRef}
7878
aria-disabled={disabled === true ? 'true' : 'false'}
7979
data-disabled={disabled}
80+
aria-checked={checkedSig.value ? 'true' : 'false'}
8081
data-highlighted={isHighlightedSig.value}
8182
data-checked={checkedSig.value}
83+
data-close-on-select={props.closeOnSelect}
8284
data-menu-item
8385
>
8486
<CheckboxRoot

packages/kit-headless/src/components/dropdown/dropdown-content.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const HDropdownContent = component$<DropdownContentProps>((props) => {
6161

6262
return (
6363
<div
64+
data-content
6465
{...props}
6566
id={contentId}
6667
role="content"

packages/kit-headless/src/components/dropdown/dropdown-item.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type DropdownItemProps = {
2121

2222
export const HDropdownItem = component$((props: DropdownItemProps) => {
2323
/* look at dropdown-inline on how we get the index. */
24-
const { _index, disabled, ...rest } = props;
24+
const { _index, disabled, closeOnSelect = true, ...rest } = props;
2525

2626
const {
2727
handleClick$,
@@ -30,7 +30,7 @@ export const HDropdownItem = component$((props: DropdownItemProps) => {
3030
itemId,
3131
itemRef,
3232
isHighlightedSig,
33-
} = useDropdownItem(props);
33+
} = useDropdownItem({ closeOnSelect, ...props });
3434

3535
// Prevent default behavior for certain keys. This needs to be sync to prevent default behavior and can't be implemented in useDropdownItem.
3636
const handleKeyDownSync$ = sync$((e: KeyboardEvent) => {
@@ -54,6 +54,7 @@ export const HDropdownItem = component$((props: DropdownItemProps) => {
5454
data-disabled={disabled}
5555
data-highlighted={isHighlightedSig.value}
5656
data-menu-item
57+
data-close-on-select={closeOnSelect}
5758
>
5859
<Slot />
5960
</div>

packages/kit-headless/src/components/dropdown/dropdown-radio-group.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const HDropdownRadioGroup = component$((props: DropdownRadioGroupProps) =
5757
useContextProvider(dropdownRadioGroupContextId, radioGroupContext);
5858

5959
return (
60-
<div role="group" {...rest} data-disabled={disabled}>
60+
<div role="radiogroup" {...rest} data-disabled={disabled}>
6161
<Slot />
6262
</div>
6363
);
Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,86 @@
1-
import { Slot, component$, useContext, useSignal, useTask$ } from '@builder.io/qwik';
1+
import {
2+
$,
3+
Slot,
4+
component$,
5+
sync$,
6+
useContext,
7+
useSignal,
8+
useTask$,
9+
} from '@builder.io/qwik';
210

11+
import { CheckboxRoot } from '../checkbox/checkbox';
312
import { DropdownCheckboxItemProps } from './dropdown-checkbox-item';
4-
import { HDropdownCheckboxItem } from './dropdown-checkbox-item';
513
import { dropdownRadioGroupContextId } from './dropdown-context';
14+
import { useDropdownItem } from './use-dropdown-item';
615

716
type DropdownRadioItemProps = {
817
/** Value of the item. */
918
value: string;
1019
} & DropdownCheckboxItemProps;
1120

1221
export const HDropdownRadioItem = component$((props: DropdownRadioItemProps) => {
13-
const { disabled = false, value, closeOnSelect = false, ...rest } = props;
22+
const { disabled = false, value, closeOnSelect = false, onChange$, ...rest } = props;
1423

1524
const groupContext = useContext(dropdownRadioGroupContextId);
1625

1726
const checkedSig = useSignal<boolean>(value === groupContext.valueSig.value);
27+
const checkboxRef = useSignal<HTMLDivElement>();
1828

1929
useTask$(function trackGroupValue({ track }) {
2030
track(() => groupContext.valueSig.value);
2131

2232
checkedSig.value = value === groupContext.valueSig.value;
33+
34+
onChange$?.(checkedSig.value);
35+
});
36+
37+
//Prevent default behavior for certain keys. This needs to be sync to prevent default behavior and can't be implemented in useDropdownItem.
38+
const handleKeyDownSync$ = sync$((e: KeyboardEvent) => {
39+
const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'Home', 'End', 'Enter', ' '];
40+
if (keys.includes(e.key)) {
41+
e.preventDefault();
42+
}
2343
});
2444

45+
const onItemSelect$ = $(() => {
46+
groupContext.valueSig.value = value;
47+
});
48+
49+
const {
50+
handleClick$,
51+
handleKeyDown$,
52+
handlePointerOver$,
53+
itemId,
54+
itemRef,
55+
isHighlightedSig,
56+
} = useDropdownItem({ ...props, onItemSelect: onItemSelect$, closeOnSelect });
57+
2558
return (
26-
<HDropdownCheckboxItem
27-
bind:checked={checkedSig}
28-
onChange$={(checked) => {
29-
if (checked) {
30-
groupContext.valueSig.value = value;
31-
}
32-
}}
33-
{...rest}
59+
<div
60+
onClick$={[handleClick$, props.onClick$]}
61+
tabIndex={-1}
62+
id={itemId}
63+
onKeyDown$={[handleKeyDownSync$, handleKeyDown$, props.onKeyDown$]}
64+
onPointerOver$={[handlePointerOver$, props.onPointerOver$]}
65+
ref={itemRef}
3466
role="menuitemradio"
35-
closeOnSelect={closeOnSelect}
36-
disabled={disabled || groupContext.disabled}
3767
aria-checked={checkedSig.value ? 'true' : 'false'}
3868
aria-disabled={disabled || groupContext.disabled}
3969
data-disabled={disabled || groupContext.disabled}
70+
data-highlighted={isHighlightedSig.value}
4071
data-checked={checkedSig.value}
72+
data-close-on-select={props.closeOnSelect}
73+
data-menu-item
4174
>
42-
<Slot />
43-
</HDropdownCheckboxItem>
75+
<CheckboxRoot
76+
bind:checked={checkedSig}
77+
preventdefault:click
78+
ref={checkboxRef}
79+
style={{ pointerEvents: 'none' }}
80+
{...rest}
81+
>
82+
<Slot />
83+
</CheckboxRoot>
84+
</div>
4485
);
4586
});

packages/kit-headless/src/components/dropdown/dropdown-root.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export const HDropdownImpl = component$<DropdownProps & InternalDropdownProps>(
135135
return (
136136
<div
137137
role="dropdown"
138+
data-dropdown
138139
ref={rootRef}
139140
data-open={context.isOpenSig.value ? true : undefined}
140141
data-closed={!context.isOpenSig.value ? true : undefined}

packages/kit-headless/src/components/dropdown/dropdown-trigger.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const HDropdownTrigger = component$<DropdownTriggerProps>((props) => {
7979

8080
return (
8181
<button
82+
data-trigger
8283
{...props}
8384
id={triggerId}
8485
ref={context.triggerRef}

0 commit comments

Comments
 (0)