mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-29 10:52:47 -04:00
refactor context menu data structure
This commit is contained in:
@@ -29,7 +29,7 @@ export const MenuContext = React.createContext<MenuContextData & MenuContextActi
|
||||
export const useMenu = () => React.useContext(MenuContext);
|
||||
|
||||
export const WithContextMenu: React.FC<{
|
||||
menu: ContextMenuProps['sections'];
|
||||
menu: ContextMenuProps['items'];
|
||||
children: React.ReactElement<{ onContextMenu: MouseEventHandler }>;
|
||||
}> = (props) => {
|
||||
const { menu: sections = [], children } = props;
|
||||
@@ -45,7 +45,7 @@ export const WithContextMenu: React.FC<{
|
||||
e.stopPropagation();
|
||||
|
||||
menu.showMenu(
|
||||
<ContextMenu sections={sections} />,
|
||||
<ContextMenu items={sections} />,
|
||||
{ x: e.clientX, y: e.clientY },
|
||||
e.target as HTMLElement
|
||||
);
|
||||
|
||||
@@ -16,30 +16,26 @@ const Template: ComponentStory<typeof ContextMenu> = (args) => <ContextMenu {...
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
sections: [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'New Item',
|
||||
icon: Plus,
|
||||
onClick: () => {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'View Info',
|
||||
icon: FileText,
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: Trash,
|
||||
danger: true,
|
||||
onClick: () => {}
|
||||
}
|
||||
]
|
||||
}
|
||||
items: [
|
||||
[
|
||||
{
|
||||
label: 'New Item',
|
||||
icon: Plus,
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'View Info',
|
||||
icon: FileText,
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: Trash,
|
||||
danger: true,
|
||||
onClick: () => {}
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -11,56 +11,59 @@ export interface ContextMenuItem {
|
||||
}
|
||||
|
||||
export interface ContextMenuProps {
|
||||
sections?: {
|
||||
heading?: string;
|
||||
items: ContextMenuItem[];
|
||||
}[];
|
||||
items?: (ContextMenuItem | string)[][];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ContextMenu: React.FC<ContextMenuProps> = (props) => {
|
||||
const { sections = [], className, ...rest } = props;
|
||||
const { items = [], className, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="menu"
|
||||
className={clsx(
|
||||
'shadow-2xl min-w-[15rem] shadow-gray-300 dark:shadow-gray-600 flex flex-col select-none cursor-default bg-gray-50 text-gray-800 border-gray-200 dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500 text-left text-sm rounded gap-1.5 border py-1.5',
|
||||
'shadow-2xl min-w-[15rem] shadow-gray-300 dark:shadow-gray-750 flex flex-col select-none cursor-default bg-gray-50 text-gray-800 border-gray-200 dark:bg-gray-650 dark:text-gray-100 dark:border-gray-550 text-left text-sm rounded gap-1.5 border py-1.5',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{sections.map((sec, i) => (
|
||||
{items.map((sec, i) => (
|
||||
<>
|
||||
{i !== 0 && (
|
||||
<hr className="border-0 border-b border-b-gray-300 dark:border-b-gray-500 mx-2" />
|
||||
<hr className="border-0 border-b border-b-gray-300 dark:border-b-gray-550 mx-2" />
|
||||
)}
|
||||
|
||||
<section key={i} className="flex items-stretch flex-col gap-0.5">
|
||||
{sec.heading && (
|
||||
<span className="text-xs ml-2 mt-1 uppercase text-gray-400">{sec.heading}</span>
|
||||
)}
|
||||
|
||||
<ul>
|
||||
{sec.items.map(({ icon: ItemIcon = Question, ...item }) => (
|
||||
<li key={item.label} className="flex">
|
||||
<button
|
||||
style={{
|
||||
font: 'inherit',
|
||||
textAlign: 'inherit'
|
||||
}}
|
||||
className={clsx('group cursor-default flex-1 px-1.5 py-0 group-first:pt-1.5', {
|
||||
'text-red-600 dark:text-red-400': item.danger
|
||||
})}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<div className="px-1.5 py-[0.4em] group-focus-visible:bg-gray-150 group-hover:bg-gray-150 dark:group-focus-visible:bg-gray-500 dark:group-hover:bg-gray-500 flex flex-row gap-2.5 items-center rounded-sm">
|
||||
{<ItemIcon size={18} />}
|
||||
<span className="leading-snug text-[14px] font-normal">{item.label}</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
{sec.map((item) => {
|
||||
if (typeof item === 'string')
|
||||
return <span className="text-xs ml-2 mt-1 uppercase text-gray-400">{item}</span>;
|
||||
|
||||
const { icon: ItemIcon = Question } = item;
|
||||
|
||||
return (
|
||||
<li key={item.label} className="flex">
|
||||
<button
|
||||
style={{
|
||||
font: 'inherit',
|
||||
textAlign: 'inherit'
|
||||
}}
|
||||
className={clsx(
|
||||
'group cursor-default flex-1 px-1.5 py-0 group-first:pt-1.5',
|
||||
{
|
||||
'text-red-600 dark:text-red-400': item.danger
|
||||
}
|
||||
)}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<div className="px-1.5 py-[0.4em] group-focus-visible:bg-gray-150 group-hover:bg-gray-150 dark:group-focus-visible:bg-gray-550 dark:group-hover:bg-gray-550 flex flex-row gap-2.5 items-center rounded-sm">
|
||||
{<ItemIcon size={18} />}
|
||||
<span className="leading-snug text-[14px] font-normal">{item.label}</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user