Skip to content

Commit 709200d

Browse files
authored
Merge pull request #237 from qualcomm/olaf/menu-button
feat(menu): menu button & icon menu button
2 parents 12874ac + c79a68e commit 709200d

18 files changed

Lines changed: 403 additions & 123 deletions

File tree

.changeset/common-camels-wear.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@qualcomm-ui/core": minor
3+
"@qualcomm-ui/qds-core": minor
4+
"@qualcomm-ui/angular": minor
5+
"@qualcomm-ui/react": minor
6+
---
7+
8+
feat(menu): menu button & icon menu button
9+
10+
commit: 0508c4d

packages/common/core/src/menu/menu.machine.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,6 @@ export const menuMachine: MachineConfig<MenuSchema> = createMachine<MenuSchema>(
612612
placement: "bottom-start",
613613
...props.positioning,
614614
},
615-
...props,
616615
}
617616
},
618617
refs() {

packages/common/qds-core/src/menu/menu.api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
QdsMenuCheckboxItemControlBindings,
1616
QdsMenuContentBindings,
1717
QdsMenuDescriptionBindings,
18+
QdsMenuIndicatorBindings,
1819
QdsMenuItemAccessoryBindings,
1920
QdsMenuItemBindings,
2021
QdsMenuItemCommandBindings,
@@ -55,6 +56,11 @@ export function createQdsMenuApi(
5556
"data-size": size,
5657
})
5758
},
59+
getIndicatorBindings(): QdsMenuIndicatorBindings {
60+
return normalize.element({
61+
className: menuClasses.indicator,
62+
})
63+
},
5864
getItemBindings(): QdsMenuItemBindings {
5965
return normalize.element({
6066
className: menuItemClasses.root,

packages/common/qds-core/src/menu/menu.classes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
export const menuClasses = {
55
button: "qui-menu__button",
66
content: "qui-menu__content",
7+
indicator: "qui-menu__indicator",
78
separator: "qui-menu__separator",
89
} as const

packages/common/qds-core/src/menu/menu.types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,18 @@ export interface QdsMenuButtonBindings {
9494
className: MenuClasses["button"]
9595
}
9696

97+
export interface QdsMenuIndicatorBindings {
98+
className: MenuClasses["indicator"]
99+
}
100+
97101
export interface QdsMenuApi {
98102
size: QdsMenuSize
99103

100104
// group: bindings
101105
getButtonBindings(): QdsMenuButtonBindings
102106
getCheckboxItemControlBindings(): QdsMenuCheckboxItemControlBindings
103107
getContentBindings(): QdsMenuContentBindings
108+
getIndicatorBindings(): QdsMenuIndicatorBindings
104109
getItemBindings(): QdsMenuItemBindings
105110
getItemCommandBindings(): QdsMenuItemCommandBindings
106111
getItemGroupBindings(): QdsMenuItemGroupBindings

packages/common/qds-core/src/menu/qds-menu.css

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,52 @@
11
.qui-menu__button {
22
background: transparent;
3-
.qui-button__icon {
4-
&[data-placement="end"],
5-
&[data-icon-button-part="root"] {
6-
transition: transform var(--animation-duration-faster) ease-out;
7-
}
3+
.qui-menu__indicator {
4+
transition: transform var(--animation-duration-faster) ease-out;
85
}
96
&[data-menu-part="trigger"][data-state="open"],
107
&[data-state="open"][aria-haspopup="menu"] {
11-
.qui-button__icon {
12-
&[data-placement="end"],
13-
&[data-icon-button-part="root"] {
14-
transform: rotate(180deg);
15-
}
8+
.qui-menu__indicator {
9+
transform: rotate(180deg);
1610
}
1711
}
1812
}
1913

14+
/* icon-only menu button stuff */
15+
16+
.qui-menu__button .qui-menu__indicator {
17+
&[data-size="sm"] {
18+
--icon-size: 12px;
19+
}
20+
&[data-size="md"] {
21+
--icon-size: 16px;
22+
}
23+
&[data-size="lg"] {
24+
--icon-size: 20px;
25+
}
26+
}
27+
28+
.qui-menu__button[data-kind="icon"]:has(.qui-button__icon) {
29+
gap: var(--spacing-10);
30+
&[data-size="sm"] {
31+
padding-inline: var(--spacing-70);
32+
width: auto;
33+
}
34+
&[data-size="md"] {
35+
padding-inline: var(--spacing-80);
36+
width: auto;
37+
}
38+
&[data-size="lg"] {
39+
padding-inline: var(--spacing-90);
40+
width: auto;
41+
}
42+
43+
.qui-menu__indicator {
44+
--icon-size: 12px;
45+
}
46+
}
47+
48+
/* general stuff */
49+
2050
.qui-menu__content {
2151
background: var(--color-surface-overlay);
2252
border: solid 1px var(--color-border-neutral-02);

packages/docs/angular-docs/src/routes/components+/menu+/_menu.mdx

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,27 @@ import {Menu} from "@qualcomm-ui/react/menu"
2222
### Item Customization
2323

2424
::: terms
25-
hotkeys
26-
keyboard shortcuts
27-
action list
28-
overflow menu
25+
hotkeys, keyboard shortcuts, action list, overflow menu
2926
:::
3027

3128
Compose the menu to include icons and commands.
3229

3330
<QdsDemo name="MenuItemCustomizationDemo" />
3431

32+
### Menu Button
33+
34+
::: terms
35+
menu trigger, dropdown button, icon menu button
36+
:::
37+
38+
Use [`q-menu-button`](./#q-menu-button) for a labeled trigger and [`q-menu-icon-button`](./#q-menu-icon-button) for an icon-only trigger. Both automatically render a chevron indicator. Always provide an `aria-label` on `q-menu-icon-button` to give assistive technologies an accessible name. Control the menu position with [positioning.placement](./#positioning).
39+
40+
<QdsDemo name="MenuButtonDemo" />
41+
3542
### Context Menu
3643

3744
::: terms
38-
oncontextmenu
39-
right-click menu
45+
oncontextmenu, right-click menu
4046
:::
4147

4248
Demonstrates how to trigger a menu on right-click or other context events.
@@ -46,9 +52,7 @@ Demonstrates how to trigger a menu on right-click or other context events.
4652
### Nested Menus
4753

4854
::: terms
49-
submenu
50-
cascading
51-
hierarchical
55+
submenu, cascading, hierarchical
5256
:::
5357

5458
Compose nested menus by nesting `<Menu.Root>` components inside each other.
@@ -58,9 +62,7 @@ Compose nested menus by nesting `<Menu.Root>` components inside each other.
5862
### Links
5963

6064
::: terms
61-
anchor
62-
navigation
63-
navigation menu
65+
anchor, navigation, navigation menu
6466
:::
6567

6668
Menu items can be links.
@@ -82,8 +84,7 @@ There are three item types, each corresponding to one of the three types of aria
8284
#### Checkbox Items
8385

8486
::: terms
85-
multi-select
86-
toggle
87+
multi-select, toggle
8788
:::
8889

8990
Use checkbox items when you need options that are individually selectable.
@@ -103,8 +104,7 @@ Checkbox item state is controlled individually on each item. Use the `useCheckbo
103104
#### Radio Group
104105

105106
::: terms
106-
single-select
107-
exclusive selection
107+
single-select, exclusive selection
108108
:::
109109

110110
Radio items are grouped such that only one can be selected at a time.
@@ -114,8 +114,7 @@ Radio items are grouped such that only one can be selected at a time.
114114
#### Radio Group State
115115

116116
::: terms
117-
controlled radio
118-
radio selection
117+
controlled radio, radio selection
119118
:::
120119

121120
Radio item state can be controlled via the [value](./#value) property on the parent `menu-radio-item-group` element.
@@ -125,11 +124,10 @@ Radio item state can be controlled via the [value](./#value) property on the par
125124
### Controlled State
126125

127126
::: terms
128-
signal
129-
two-way binding
127+
signal, two-way binding
130128
:::
131129

132-
The menu's visibility can be controlled via the [open](./#open), [openChanged](./#openChanged), and [defaultOpen](./#defaultOpen) properties.
130+
The menu's visibility can be controlled via the [`open`](./#open), [`openChanged`](./#openchanged), and [`defaultOpen`](./#default-open) properties.
133131

134132
- Use `open` to explicitly set whether the menu is open (controlled mode).
135133
- Use `openChanged` to be notified when the menu requests to open or close.
@@ -142,8 +140,7 @@ In controlled mode, the `open` prop should be updated in response to `openChange
142140
### Placement
143141

144142
::: terms
145-
position
146-
popover placement
143+
position, popover placement
147144
:::
148145

149146
Configure the menu's placement using the [positioning.placement](./#positioning) prop.
@@ -163,9 +160,7 @@ Use the [positioning.anchorPoint](./#positioning) prop to control the anchor poi
163160
### Avatar
164161

165162
::: terms
166-
profile menu
167-
user menu
168-
account dropdown
163+
profile menu, user menu, account dropdown
169164
:::
170165

171166
Here's an example that composes the `Menu` with the [Avatar](/components/avatar) component to display a user account menu.
@@ -175,20 +170,17 @@ Here's an example that composes the `Menu` with the [Avatar](/components/avatar)
175170
### Within Dialog
176171

177172
::: terms
178-
modal
179-
overlay composition
173+
modal, overlay composition
180174
:::
181175

182-
When using the `Menu` inside a `Dialog`, don't render the `q-menu-positioner` within a portal element.
176+
When using the `Menu` inside a [Dialog](/components/dialog), don't render the `q-menu-positioner` within a portal element.
183177

184178
<QdsDemo name="MenuWithinDialogDemo" />
185179

186180
### Hide When Detached
187181

188182
::: terms
189-
auto-hide
190-
scroll behavior
191-
overflow
183+
auto-hide, scroll behavior, overflow
192184
:::
193185

194186
Use the [positioning.hideWhenDetached](./#positioning) prop to hide the menu when it's detached from the trigger.
@@ -201,6 +193,14 @@ Use the [positioning.hideWhenDetached](./#positioning) prop to hide the menu whe
201193

202194
<TypeDocProps name="MenuComponent" />
203195

196+
### q-menu-button
197+
198+
<TypeDocProps name="MenuButtonComponent" />
199+
200+
### q-menu-icon-button
201+
202+
<TypeDocProps name="MenuIconButtonComponent" />
203+
204204
### q-menu-item
205205

206206
<TypeDocProps name="MenuItemDirective" />
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {Component} from "@angular/core"
2+
import {
3+
Copy,
4+
Ellipsis,
5+
File,
6+
FileText,
7+
FolderOpen,
8+
Pencil,
9+
Trash2,
10+
} from "lucide-angular"
11+
12+
import {MenuModule} from "@qualcomm-ui/angular/menu"
13+
import {provideIcons} from "@qualcomm-ui/angular-core/lucide"
14+
import {PortalComponent} from "@qualcomm-ui/angular-core/portal"
15+
16+
@Component({
17+
imports: [MenuModule, PortalComponent],
18+
providers: [
19+
provideIcons({Copy, Ellipsis, File, FileText, FolderOpen, Pencil, Trash2}),
20+
],
21+
selector: "menu-button-demo",
22+
template: `
23+
<div class="flex gap-2.5">
24+
<q-menu [positioning]="{placement: 'bottom-end'}">
25+
<button emphasis="primary" q-menu-button startIcon="File">File</button>
26+
<q-portal>
27+
<div q-menu-positioner>
28+
<div q-menu-content>
29+
<button q-menu-item value="new-text-file">
30+
<div icon="FileText" q-menu-item-start-icon></div>
31+
New Text File
32+
</button>
33+
<button q-menu-item value="new-file">
34+
<div icon="File" q-menu-item-start-icon></div>
35+
New File...
36+
</button>
37+
<button q-menu-item value="open-file">
38+
<div icon="FolderOpen" q-menu-item-start-icon></div>
39+
Open File...
40+
</button>
41+
</div>
42+
</div>
43+
</q-portal>
44+
</q-menu>
45+
46+
<q-menu>
47+
<button
48+
aria-label="More actions"
49+
emphasis="primary"
50+
icon="Ellipsis"
51+
q-menu-icon-button
52+
></button>
53+
<q-portal>
54+
<div q-menu-positioner>
55+
<div q-menu-content>
56+
<button q-menu-item value="rename">
57+
<div icon="Pencil" q-menu-item-start-icon></div>
58+
Rename
59+
</button>
60+
<button q-menu-item value="duplicate">
61+
<div icon="Copy" q-menu-item-start-icon></div>
62+
Duplicate
63+
</button>
64+
<button q-menu-item value="delete">
65+
<div icon="Trash2" q-menu-item-start-icon></div>
66+
Delete
67+
</button>
68+
</div>
69+
</div>
70+
</q-portal>
71+
</q-menu>
72+
</div>
73+
`,
74+
})
75+
export class MenuButtonDemo {}

0 commit comments

Comments
 (0)