refactor: replace Menu with new Popup component

This commit is contained in:
emirsassan 2025-07-07 09:21:33 +03:00
parent 5f78b6aa15
commit cd668fc33a
9 changed files with 104 additions and 80 deletions

View file

@ -1,8 +1,8 @@
import type { ToOptions } from '@tanstack/react-router';
import { bindings } from '@/main';
import useNotifications from '@/hooks/useNotification';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { Button, Menu } from '@onelauncher/common/components';
import { Button, Menu, Popup } from '@onelauncher/common/components';
import { Link } from '@tanstack/react-router';
import { Bell01Icon, Cloud01Icon, TerminalBrowserIcon } from '@untitled-theme/icons-react';
import { MenuTrigger } from 'react-aria-components';
@ -40,7 +40,7 @@ function Navbar() {
{/* Notification Manager Button */}
<div className="relative">
<MenuTrigger>
<Popup.Trigger>
<Button className="relative" color="ghost" size="icon">
<Bell01Icon />
{notificationCount > 0 && (
@ -49,22 +49,21 @@ function Navbar() {
</span>
)}
</Button>
<Menu.Popover placement="bottom right">
<Popup>
<NotificationPopup />
</Menu.Popover>
</MenuTrigger>
</Popup>
</Popup.Trigger>
</div>
<MenuTrigger>
<Popup.Trigger>
<Button className="p-0" color="ghost" size="icon">
<PlayerHead className="h-full rounded-md hover:opacity-70" uuid={defaultUser.data?.id} />
</Button>
<Menu.Popover>
<Popup>
<AccountPopup />
</Menu.Popover>
</MenuTrigger>
</Popup>
</Popup.Trigger>
</div>
</div>
);

View file

@ -7,9 +7,8 @@ import Modal from '@/components/overlay/Modal';
import ScrollableContainer from '@/components/ScrollableContainer';
import { bindings } from '@/main';
import { LAUNCHER_IMPORT_TYPES, LOADERS, upperFirst } from '@/utils';
import { useCommand } from '@onelauncher/common';
import { useCommand, useCommandMut } from '@onelauncher/common';
import { Button, Dropdown, SelectList, TextField } from '@onelauncher/common/components';
import { useQueryClient } from '@tanstack/react-query';
import { ArrowRightIcon, PlusIcon, SearchMdIcon, Server01Icon, TextInputIcon, User03Icon } from '@untitled-theme/icons-react';
import { useCallback, useEffect, useState } from 'react';
import { Checkbox } from 'react-aria-components';
@ -34,29 +33,22 @@ export function NewClusterCreate() {
clusterVersion: '',
});
const create = useCommand('createCluster', () => bindings.core.createCluster({
const create = useCommandMut(() => bindings.core.createCluster({
icon: DefaultClusterBanner,
mc_loader: formData.loader as GameLoader,
mc_loader_version: null,
mc_version: formData.clusterVersion,
name: formData.clusterName,
}), {
enabled: false,
subscribed: false,
});
const queryClient = useQueryClient();
}));
function handleCreate() {
if (!isLastInputStep)
return;
create.refetch();
create.mutate();
if (create.isError)
console.error(create.error.message);
queryClient.invalidateQueries({ queryKey: ['getClusters'], exact: true });
}
const handleNext = useCallback(async () => {

View file

@ -2,58 +2,61 @@ import type { MinecraftCredentials } from '@/bindings.gen';
import PlayerHead from '@/components/content/PlayerHead';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { Button, Menu } from '@onelauncher/common/components';
import { Button } from '@onelauncher/common/components';
import { Link } from '@tanstack/react-router';
import { PlusIcon, Settings01Icon } from '@untitled-theme/icons-react';
import { Separator } from 'react-aria-components';
import { twMerge } from 'tailwind-merge';
function AccountPopup() {
const users = useCommand('getUsers', bindings.core.getUsers);
const defaultUser = useCommand('getDefaultUser', () => bindings.core.getDefaultUser(false));
const addAccount = useCommand('openMsaLogin', bindings.core.openMsaLogin, {
enabled: false,
subscribed: false,
});
const setDefaultUser = (user: MinecraftCredentials) => {
bindings.core.setDefaultUser(user.id);
};
return (
<Menu className="min-w-3xs">
<div className="min-w-3xs">
{defaultUser.data && (
<Menu.Item>
<div>
<AccountEntry
loggedIn
onClick={() => {}}
onClick={() => { }}
user={defaultUser.data}
/>
</Menu.Item>
</div>
)}
{users.data?.filter(user => user.id === defaultUser.data?.id).map(user => (
<Menu.Item key={user.id}>
{users.data?.filter(user => user.id !== defaultUser.data?.id).map(user => (
<div key={user.id}>
<AccountEntry
onClick={() => setDefaultUser(user)}
user={user}
/>
</Menu.Item>
</div>
))}
{(users.data?.length ?? 0) > 0 && (
<Separator />
)}
<Menu.Item className="flex flex-row justify-between">
<div>
<Button color="ghost">
<div className="flex flex-row justify-between">
<div className="self-center">
<Button color="ghost" onClick={() => addAccount.refetch()}>
<PlusIcon />
Add Account
</Button>
</div>
<div className="flex flex-row">
<Button color="ghost" size="iconLarge">
<Settings01Icon className="stroke-fg-primary" />
</Button>
<Link to="/app/settings/accounts">
<Button color="ghost" size="iconLarge">
<Settings01Icon className="stroke-fg-primary" />
</Button>
</Link>
</div>
</Menu.Item>
</Menu>
</div>
</div>
);
}

View file

@ -1,7 +1,8 @@
import type { NotificationData } from '@/hooks/useNotification';
import useNotifications from '@/hooks/useNotification';
import { Button, Menu } from '@onelauncher/common/components';
import { Button, Menu, Popup } from '@onelauncher/common/components';
import { XIcon } from '@untitled-theme/icons-react';
import { Popover } from 'react-aria-components';
interface NotificationItemProps {
id: string;
@ -53,35 +54,29 @@ function NotificationPopup() {
if (notificationEntries.length === 0)
return (
<Menu className="min-w-80 max-w-96">
<Menu.Item>
<div className="p-4 text-center">
<p className="text-fg-secondary text-sm">No notifications</p>
</div>
</Menu.Item>
</Menu>
<div className="p-3 text-center">
<p className="text-fg-secondary text-sm">No notifications</p>
</div>
);
return (
<Menu className="min-w-80 max-w-96 max-h-96 overflow-y-auto">
<Menu.Item>
<div className="flex items-center justify-between p-3 border-b border-component-border">
<h3 className="font-semibold text-fg-primary">Notifications</h3>
{notificationEntries.length > 0 && (
<Button
className="text-xs"
color="ghost"
onClick={clear}
size="normal"
>
Clear All
</Button>
)}
</div>
</Menu.Item>
<Popup className="min-w-80 w-full max-w-96 max-h-96 overflow-y-auto">
<div className="flex items-center justify-between border-b border-component-border">
<h3 className="font-semibold text-fg-primary">Notifications</h3>
{notificationEntries.length > 0 && (
<Button
className="text-xs"
color="ghost"
onClick={clear}
size="normal"
>
Clear All
</Button>
)}
</div>
{notificationEntries.map(([id, data]) => (
<Menu.Item key={id}>
<div key={id}>
<div className="p-2">
<NotificationItem
data={data}
@ -89,9 +84,9 @@ function NotificationPopup() {
onRemove={removeNotification}
/>
</div>
</Menu.Item>
</div>
))}
</Menu>
</Popup>
);
}

View file

@ -43,13 +43,8 @@ export function NotificationProvider({ children }: NotificationProviderProps) {
}));
}).then(u => u());
const ctx = {
notifications,
setNotifications,
};
return (
<NotificationContext.Provider value={ctx}>
<NotificationContext.Provider value={{ notifications, setNotifications }}>
{children}
</NotificationContext.Provider>
);

View file

@ -4,6 +4,7 @@ import DefaultInstancePhoto from '@/assets/images/default_instance_cover.jpg';
import LoaderIcon from '@/components/launcher/LoaderIcon';
import ScrollableContainer from '@/components/ScrollableContainer';
import SettingsRow from '@/components/SettingsRow';
import useNotifications from '@/hooks/useNotification';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { Button, Show, TextField } from '@onelauncher/common/components';
@ -129,6 +130,8 @@ function Banner({
setNewName,
setNewCover,
}: BannerProps) {
const { set, list } = useNotifications();
async function launchFilePicker() {
const selected = await open({
multiple: false,
@ -153,8 +156,17 @@ function Banner({
const handleLaunch = () => {
launch.refetch();
if (launch.error)
console.error(launch.error.message);
// eslint-disable-next-line no-console -- debug
console.log(list);
if (launch.isError)
set('launch_cluster', {
title: 'Failed to launch cluster',
message: launch.error.message,
});
// eslint-disable-next-line no-console -- debug
console.log(list);
};
function updateName(name: string) {

View file

@ -21,6 +21,11 @@ function RouteComponent() {
const { data: usersData, isLoading: usersLoading } = useCommand('getUsers', bindings.core.getUsers);
const { data: defaultUserData, isLoading: defaultUserLoading } = useCommand('getDefaultUser', () => bindings.core.getDefaultUser(false));
const addAccount = useCommand('openMsaLogin', bindings.core.openMsaLogin, {
enabled: false,
subscribed: false,
});
if (usersLoading || defaultUserLoading)
return (
<Sidebar.Page>
@ -47,7 +52,7 @@ function RouteComponent() {
</ScrollableContainer>
<div className="mt-auto pt-2 flex flex-row items-end justify-end">
<Button color="primary">
<Button color="primary" onClick={() => addAccount.refetch()}>
<UserPlus02Icon />
{' '}
Add Account

View file

@ -0,0 +1,22 @@
import type { PopoverProps } from 'react-aria-components';
import { Dialog, DialogTrigger, Popover } from 'react-aria-components';
interface MyPopoverProps extends Omit<PopoverProps, 'children'> {
children: React.ReactNode;
}
export function Popup({ children, ...props }: MyPopoverProps) {
return (
<Popover {...props} className="flex flex-col gap-0.5 rounded-lg border border-component-border">
<Dialog className="bg-page-elevated p-2 shadow-md rounded-lg">
{children}
</Dialog>
</Popover>
);
}
function Trigger({ children, ...props }: any) {
return <DialogTrigger children={children} {...props} />;
}
Popup.Trigger = Trigger;

View file

@ -3,6 +3,7 @@ export * from './Button';
export * from './ContextMenu';
export * from './Dropdown';
export * from './Menu';
export * from './Popup';
export * from './SelectList';
export * from './Show';
export * from './Switch';