Skip to content

Commit d68883d

Browse files
feat: Menu docs added
1 parent ada7fe2 commit d68883d

9 files changed

Lines changed: 412 additions & 590 deletions

File tree

apps/showcase/demo/headless/menu/basic-demo.tsx

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,75 +10,95 @@ import { QuestionCircle } from '@primeicons/react/question-circle';
1010
import { Star } from '@primeicons/react/star';
1111
import { useMenu } from '@primereact/headless/menu';
1212
import { useMenuSub } from '@primereact/headless/menu/sub';
13-
import * as React from 'react';
14-
15-
const itemClass =
16-
'flex items-center gap-2 px-3 py-2 rounded-md text-sm cursor-pointer transition-colors text-surface-700 dark:text-surface-200 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed';
17-
18-
function SubMenu({ menu, icon, label }: { menu: ReturnType<typeof useMenu>; icon: React.ReactNode; label: string }) {
19-
const sub = useMenuSub({ defaultOpen: true });
20-
const { subProps, subtriggerProps, listProps, state } = sub;
21-
const { getSubTriggerProps, getItemProps, getListProps } = menu;
22-
23-
return (
24-
<li {...subProps}>
25-
<div {...subtriggerProps} {...getSubTriggerProps({ value: 'projects', sub })} className={itemClass}>
26-
{icon}
27-
<span className="flex-1">{label}</span>
28-
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${state.open ? 'rotate-180' : ''}`} />
29-
</div>
30-
<ul {...listProps} {...getListProps({ value: 'projects', sub })} className="pl-4 list-none m-0 p-0 outline-none">
31-
<li {...getItemProps({ value: 'active-projects' })} className={itemClass}>
32-
<Briefcase className="w-4 h-4" />
33-
Active Projects
34-
</li>
35-
<li {...getItemProps({ value: 'recent' })} className={itemClass}>
36-
<Clock className="w-4 h-4" />
37-
Recent
38-
</li>
39-
<li {...getItemProps({ value: 'favorites' })} className={itemClass}>
40-
<Star className="w-4 h-4" />
41-
Favorites
42-
</li>
43-
<li {...getItemProps({ value: 'completed' })} className={itemClass}>
44-
<CheckCircle className="w-4 h-4" />
45-
Completed
46-
</li>
47-
</ul>
48-
</li>
49-
);
50-
}
5113

5214
export default function BasicDemo() {
5315
const menu = useMenu();
54-
const { rootProps, getListProps, getItemProps, separatorProps, labelProps } = menu;
16+
const { rootProps, getListProps, getItemProps, labelProps, separatorProps } = menu;
17+
const projectsSub = useMenuSub({ defaultOpen: true });
18+
const projectsSubTriggerProps = menu.getSubTriggerProps({ value: 'projects', sub: projectsSub });
5519

5620
return (
5721
<div className="flex justify-center">
58-
<div {...rootProps} className="w-64 border border-surface-200 dark:border-surface-700 rounded-lg p-1">
59-
<ul {...getListProps()} className="list-none m-0 p-0 outline-none">
60-
<li {...getItemProps({ value: 'dashboard' })} className={itemClass}>
61-
<Home className="w-4 h-4" />
22+
<div
23+
{...rootProps}
24+
className="w-64 border border-surface-200 rounded-lg bg-surface-0 dark:bg-surface-900 dark:border-surface-700 overflow-hidden"
25+
>
26+
<ul {...getListProps()} className="list-none m-0 p-1 flex flex-col gap-0.5 outline-none">
27+
<li
28+
{...getItemProps({ value: 'dashboard' })}
29+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
30+
>
31+
<Home className="w-4! h-4! text-surface-500" />
6232
Dashboard
6333
</li>
6434

6535
<li {...separatorProps} className="border-t border-surface-200 dark:border-surface-700 my-1" />
6636

67-
<li {...labelProps} className="px-3 py-1.5 text-xs font-semibold text-surface-500 dark:text-surface-400 uppercase tracking-wider">
37+
<li {...labelProps} className="px-3 py-1.5 text-xs font-semibold text-surface-500 uppercase tracking-wide">
6838
Workspace
6939
</li>
7040

71-
<li {...getItemProps({ value: 'analytics' })} className={itemClass}>
72-
<ChartLine className="w-4 h-4" />
41+
<li
42+
{...getItemProps({ value: 'analytics' })}
43+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
44+
>
45+
<ChartLine className="w-4! h-4! text-surface-500" />
7346
Analytics
7447
</li>
7548

76-
<SubMenu menu={menu} label="Projects" icon={<Folder className="w-4 h-4" />} />
49+
<li {...projectsSub.subProps}>
50+
<div
51+
{...projectsSubTriggerProps}
52+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
53+
>
54+
<Folder className="w-4! h-4! text-surface-500" />
55+
Projects
56+
<span
57+
className="ml-auto transition-transform duration-200"
58+
style={{ transform: projectsSub.state.open ? 'rotate(180deg)' : 'rotate(0deg)' }}
59+
>
60+
<ChevronDown className="w-3.5 h-3.5 text-surface-400" />
61+
</span>
62+
</div>
63+
<ul {...menu.getListProps({ value: 'projects', sub: projectsSub })} className="list-none m-0 p-0 pl-4">
64+
<li
65+
{...getItemProps({ value: 'active-projects' })}
66+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
67+
>
68+
<Briefcase className="w-4! h-4! text-surface-500" />
69+
Active Projects
70+
</li>
71+
<li
72+
{...getItemProps({ value: 'recent' })}
73+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
74+
>
75+
<Clock className="w-4! h-4! text-surface-500" />
76+
Recent
77+
</li>
78+
<li
79+
{...getItemProps({ value: 'favorites' })}
80+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
81+
>
82+
<Star className="w-4! h-4! text-surface-500" />
83+
Favorites
84+
</li>
85+
<li
86+
{...getItemProps({ value: 'completed' })}
87+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
88+
>
89+
<CheckCircle className="w-4! h-4! text-surface-500" />
90+
Completed
91+
</li>
92+
</ul>
93+
</li>
7794

7895
<li {...separatorProps} className="border-t border-surface-200 dark:border-surface-700 my-1" />
7996

80-
<li {...getItemProps({ value: 'help' })} className={itemClass}>
81-
<QuestionCircle className="w-4 h-4" />
97+
<li
98+
{...getItemProps({ value: 'help' })}
99+
className="flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 data-[focused]:bg-surface-100 dark:data-[focused]:bg-surface-700 text-sm select-none"
100+
>
101+
<QuestionCircle className="w-4! h-4! text-surface-500" />
82102
Help & Support
83103
</li>
84104
</ul>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
import { Pencil } from '@primeicons/react/pencil';
3+
import { Plus } from '@primeicons/react/plus';
4+
import { Refresh } from '@primeicons/react/refresh';
5+
import { Trash } from '@primeicons/react/trash';
6+
import { Upload } from '@primeicons/react/upload';
7+
import { useSpeedDial } from '@primereact/headless/speeddial';
8+
9+
const items = [
10+
{ icon: Pencil, label: 'Edit' },
11+
{ icon: Refresh, label: 'Refresh' },
12+
{ icon: Trash, label: 'Delete' },
13+
{ icon: Upload, label: 'Upload' }
14+
];
15+
16+
export default function BasicDemo() {
17+
const { rootProps, triggerProps, listProps, getItemProps, actionProps } = useSpeedDial({ direction: 'up' });
18+
19+
return (
20+
<div style={{ position: 'relative', height: '240px' }}>
21+
<div
22+
{...rootProps}
23+
className="inline-flex flex-col-reverse items-center"
24+
style={{ ...rootProps.style, position: 'absolute', right: 0, bottom: 0 }}
25+
>
26+
<button
27+
{...triggerProps}
28+
className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-primary text-primary-contrast border-none cursor-pointer transition-transform duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary data-[open]:rotate-45"
29+
>
30+
<Plus />
31+
</button>
32+
<ul {...listProps} className="group flex flex-col-reverse items-center gap-2 list-none m-0 p-0 pb-2" style={listProps.style}>
33+
{items.map((item, index) => {
34+
const Icon = item.icon;
35+
const itemProps = getItemProps(index);
36+
37+
return (
38+
<li
39+
key={item.label}
40+
{...itemProps}
41+
className="opacity-0 scale-50 transition-all duration-200 group-data-[open]:opacity-100 group-data-[open]:scale-100"
42+
style={itemProps.style}
43+
>
44+
<button
45+
{...actionProps}
46+
className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-surface-0 border border-surface-200 text-surface-600 cursor-pointer hover:bg-surface-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
47+
aria-label={item.label}
48+
>
49+
<Icon />
50+
</button>
51+
</li>
52+
);
53+
})}
54+
</ul>
55+
</div>
56+
</div>
57+
);
58+
}

apps/showcase/demo/primitives/menu/basic-demo.module.css

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.wrapper {
1+
.container {
22
display: flex;
33
justify-content: center;
44
}
@@ -7,45 +7,105 @@
77
width: 16rem;
88
border: 1px solid var(--p-content-border-color);
99
border-radius: 0.5rem;
10-
padding: 0.25rem;
10+
overflow: hidden;
11+
background-color: light-dark(var(--p-surface-0), var(--p-surface-900));
1112
}
1213

1314
.list {
15+
display: flex;
16+
flex-direction: column;
17+
gap: 2px;
1418
list-style: none;
1519
margin: 0;
20+
padding: 0.25rem;
21+
outline: 0 none;
22+
}
23+
24+
.sub .list {
1625
padding: 0;
17-
outline: none;
26+
padding-left: 1rem;
1827
}
1928

20-
.item,
21-
.subTrigger {
29+
.item {
2230
display: flex;
2331
align-items: center;
2432
gap: 0.5rem;
2533
padding: 0.5rem 0.75rem;
2634
border-radius: 0.375rem;
35+
cursor: pointer;
2736
font-size: 0.875rem;
2837
color: var(--p-text-color);
38+
user-select: none;
39+
transition: background-color 200ms ease;
40+
}
41+
42+
.item:hover {
43+
background-color: light-dark(var(--p-surface-50), var(--p-surface-800));
44+
}
45+
46+
.item[data-focused] {
47+
background-color: light-dark(var(--p-surface-100), var(--p-surface-700));
48+
}
49+
50+
.item[data-disabled] {
51+
pointer-events: none;
52+
opacity: 0.6;
53+
}
54+
55+
.item:focus-visible {
56+
outline: 1px solid var(--p-primary-color);
57+
outline-offset: -1px;
58+
}
59+
60+
.subtrigger {
61+
display: flex;
62+
align-items: center;
63+
gap: 0.5rem;
64+
padding: 0.5rem 0.75rem;
65+
border-radius: 0.375rem;
2966
cursor: pointer;
30-
transition: background-color 150ms ease;
67+
font-size: 0.875rem;
68+
color: var(--p-text-color);
69+
user-select: none;
70+
transition: background-color 200ms ease;
71+
}
72+
73+
.subtrigger:hover {
74+
background-color: light-dark(var(--p-surface-50), var(--p-surface-800));
3175
}
3276

33-
.item[data-focused],
34-
.subTrigger[data-focused] {
35-
background-color: light-dark(var(--p-surface-100), var(--p-surface-800));
77+
.subtrigger[data-focused] {
78+
background-color: light-dark(var(--p-surface-100), var(--p-surface-700));
3679
}
3780

38-
.item:focus-visible,
39-
.subTrigger:focus-visible {
81+
.subtrigger:focus-visible {
4082
outline: 1px solid var(--p-primary-color);
4183
outline-offset: -1px;
4284
}
4385

44-
.item[data-disabled] {
86+
.subtrigger[data-disabled] {
4587
pointer-events: none;
4688
opacity: 0.6;
4789
}
4890

91+
.icon {
92+
width: 1rem;
93+
height: 1rem;
94+
color: var(--p-text-muted-color);
95+
}
96+
97+
.indicator {
98+
display: flex;
99+
align-items: center;
100+
margin-left: auto;
101+
color: var(--p-text-muted-color);
102+
transition: transform 200ms ease;
103+
}
104+
105+
.subtrigger[data-open] .indicator {
106+
transform: rotate(180deg);
107+
}
108+
49109
.label {
50110
padding: 0.375rem 0.75rem;
51111
font-size: 0.75rem;
@@ -60,21 +120,3 @@
60120
border-top: 1px solid var(--p-content-border-color);
61121
margin: 0.25rem 0;
62122
}
63-
64-
.indicator {
65-
margin-left: auto;
66-
display: flex;
67-
align-items: center;
68-
transition: transform 200ms ease;
69-
}
70-
71-
.subTrigger[data-open] .indicator {
72-
transform: rotate(180deg);
73-
}
74-
75-
.subList {
76-
list-style: none;
77-
margin: 0;
78-
padding: 0;
79-
padding-left: 1rem;
80-
}

0 commit comments

Comments
 (0)