Skip to content

Commit

Permalink
global modal store and shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
zaknesler committed Jun 3, 2024
1 parent 1f3ac3b commit 9fb364c
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 46 deletions.
2 changes: 1 addition & 1 deletion ui/src/components/feed/feed-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { HiSolidRss } from 'solid-icons/hi';
import { type Component, type JSX, Match, type Setter, Show, Switch, createMemo, createSignal } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { Transition } from 'solid-transition-group';
import { useNotifications } from '~/contexts/notifications-context';
import { useNotifications } from '~/contexts/notification-context';
import { useQueryState } from '~/contexts/query-state-context';
import { useFeedsStats } from '~/hooks/queries/use-feeds-stats';
import type { Feed } from '~/types/bindings';
Expand Down
27 changes: 8 additions & 19 deletions ui/src/components/menus/menu-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import {
HiSolidPlus,
} from 'solid-icons/hi';
import { TiCog } from 'solid-icons/ti';
import { type Component, createSignal, mergeProps } from 'solid-js';
import { type Component, mergeProps } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { setModalStore } from '~/stores/modal';
import { LogoSquare } from '../../constants/ui/logo';
import { CreateFeedModal } from '../modals/create-feed-modal';
import { Menu, type MenuProps } from './menu';

export const AppMenu: Component<MenuProps> = props => {
const [createFeedModalOpen, setCreateFeedModalOpen] = createSignal(false);

const local = mergeProps(
{
triggerClass: 'size-6 rounded-md',
Expand All @@ -27,14 +25,15 @@ export const AppMenu: Component<MenuProps> = props => {
);

const handleAddFeed = () => {
setCreateFeedModalOpen(true);
setModalStore('addFeed', true);
};

return (
<>
<Menu
{...local}
shift={4}
size="lg"
trigger={() => (
<DropdownMenu.Trigger
as={(polyProps: DropdownMenuTriggerProps) => (
Expand All @@ -49,21 +48,11 @@ export const AppMenu: Component<MenuProps> = props => {
/>
)}
>
<Menu.Item onSelect={handleAddFeed} icon={HiSolidPlus}>
Add feed
</Menu.Item>
<Menu.Item disabled icon={HiSolidArrowDownTray}>
Import/export
</Menu.Item>
<Menu.Item disabled icon={HiSolidCog6Tooth}>
Settings
</Menu.Item>
<Menu.Item disabled icon={HiSolidArrowRightOnRectangle}>
Sign out
</Menu.Item>
<Menu.Item label="Add feed" kbd="Alt ⇧ A" icon={HiSolidPlus} onSelect={handleAddFeed} />
<Menu.Item label="Import/export" icon={HiSolidArrowDownTray} disabled />
<Menu.Item label="Settings" icon={HiSolidCog6Tooth} disabled />
<Menu.Item label="Sign out" icon={HiSolidArrowRightOnRectangle} disabled />
</Menu>

<CreateFeedModal open={createFeedModalOpen()} setOpen={setCreateFeedModalOpen} />
</>
);
};
13 changes: 9 additions & 4 deletions ui/src/components/menus/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { type VariantProps, cx } from 'class-variance-authority';
import type { IconTypes } from 'solid-icons';
import { HiSolidEllipsisHorizontal } from 'solid-icons/hi';
import { type JSX, type ParentComponent, type Setter, Show, splitProps } from 'solid-js';
import { type Component, type JSX, type ParentComponent, type Setter, Show, splitProps } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import * as menuClasses from '~/constants/ui/menu';

Expand Down Expand Up @@ -68,16 +68,21 @@ const MenuTrigger: ParentComponent<MenuTrigger> = props => {
};

type MenuItemProps = DropdownMenuItemProps & {
label: string;
kbd?: string;
icon?: IconTypes;
};

const MenuItem: ParentComponent<MenuItemProps> = props => {
const [local, rest] = splitProps(props, ['icon']);
const MenuItem: Component<MenuItemProps> = props => {
const [local, rest] = splitProps(props, ['icon', 'label', 'kbd']);

return (
<DropdownMenu.Item {...rest} class={menuClasses.item()}>
<Dynamic component={local.icon} class="size-4 text-gray-400 dark:text-gray-500" />
{props.children}
{local.label}
<Show when={local.kbd}>
<kbd class="ml-auto font-mono text-gray-400 text-xs dark:text-gray-500">{local.kbd}</kbd>
</Show>
</DropdownMenu.Item>
);
};
Expand Down
16 changes: 6 additions & 10 deletions ui/src/components/modals/create-feed-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@ import { Dialog } from '@kobalte/core/dialog';
import { useNavigate } from '@solidjs/router';
import { createMutation, useQueryClient } from '@tanstack/solid-query';
import { HiSolidXMark } from 'solid-icons/hi';
import { type Component, type Setter, Show, createEffect, createSignal } from 'solid-js';
import { Show, createEffect, createSignal } from 'solid-js';
import { getErrorMessage } from '~/api';
import { addFeed } from '~/api/feeds';
import { QUERY_KEYS } from '~/constants/query';
import { modalOpen, setModalStore } from '~/stores/modal';
import { Button } from '../ui/button';
import { TextInput } from '../ui/input';
import { Spinner } from '../ui/spinner';

type CreateFeedProps = {
open: boolean;
setOpen: Setter<boolean>;
};

export const CreateFeedModal: Component<CreateFeedProps> = props => {
export const CreateFeedModal = () => {
const queryClient = useQueryClient();
const navigate = useNavigate();

Expand All @@ -41,7 +37,7 @@ export const CreateFeedModal: Component<CreateFeedProps> = props => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.FEEDS] });
navigate(`/feeds/${feed.uuid}`);

props.setOpen(false);
setModalStore('addFeed', false);
};

const handleOpenAutoFocus = (event: Event) => {
Expand All @@ -51,7 +47,7 @@ export const CreateFeedModal: Component<CreateFeedProps> = props => {

// Reset form state on close
createEffect(() => {
if (props.open) return;
if (modalOpen('addFeed')) return;

// Delay 150ms to let animation play out
setTimeout(() => {
Expand All @@ -61,7 +57,7 @@ export const CreateFeedModal: Component<CreateFeedProps> = props => {
});

return (
<Dialog open={props.open} onOpenChange={props.setOpen}>
<Dialog open={modalOpen('addFeed')} onOpenChange={value => setModalStore('addFeed', value)}>
<Dialog.Portal>
<Dialog.Overlay class="fixed inset-0 z-50 animate-overlay-hide bg-black/25 backdrop-blur ui-expanded:animate-overlay-show" />

Expand Down
2 changes: 1 addition & 1 deletion ui/src/constants/ui/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const content = cva(
size: {
sm: 'min-w-36 md:min-w-28',
md: 'min-w-48 md:min-w-36',
lg: 'min-w-64 md:min-w-40',
lg: 'min-w-64 md:min-w-48',
},
},
defaultVariants: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import { wsUrl } from '~/utils/url';
import { useInvalidateFeed } from '../hooks/queries/use-invalidate-feed';
import { useQueryState } from './query-state-context';

type NotificationsContextType = ReturnType<typeof makeNotificationsContext>;
export const NotificationsContext = createContext<NotificationsContextType>();
type NotificationContextType = ReturnType<typeof makeNotificationContext>;
export const NotificationContext = createContext<NotificationContextType>();

export const useNotifications = () => {
const notifications = useContext(NotificationsContext);
if (!notifications) throw new Error('NotificationsContext has not been initialized.');
const notification = useContext(NotificationContext);
if (!notification) throw new Error('NotificationContext has not been initialized.');

return notifications;
return notification;
};

export const makeNotificationsContext = () => {
export const makeNotificationContext = () => {
const state = useQueryState();

const currentEntry = useEntry(() => ({ entry_uuid: state.params.entry_uuid }));
Expand Down
12 changes: 12 additions & 0 deletions ui/src/hooks/use-shortcuts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createShortcut } from '@solid-primitives/keyboard';
import { setModalStore } from '~/stores/modal';

export const useShortcuts = () => {
createShortcut(
['Alt', 'Shift', 'A'],
() => {
setModalStore('addFeed', true);
},
{ preventDefault: true, requireReset: true },
);
};
15 changes: 10 additions & 5 deletions ui/src/routes/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Sidebar } from '~/components/layout/sidebar';
import { CreateFeedModal } from '~/components/modals/create-feed-modal';
import { EntryPanel } from '~/components/panels/entry-panel';
import { ListPanel } from '~/components/panels/list-panel';
import { NotificationsContext, makeNotificationsContext } from '~/contexts/notifications-context';
import { NotificationContext, makeNotificationContext } from '~/contexts/notification-context';
import { QueryStateContext, makeQueryStateContext } from '~/contexts/query-state-context';
import { useShortcuts } from '~/hooks/use-shortcuts';

export default () => {
const queryState = makeQueryStateContext();
Expand All @@ -15,16 +17,19 @@ export default () => {
};

const Inner = () => {
const notifications = makeNotificationsContext();
const notifications = makeNotificationContext();

useShortcuts();

return (
<NotificationsContext.Provider value={notifications}>
<NotificationContext.Provider value={notifications}>
<Sidebar class="hidden xl:flex xl:w-sidebar xl:shrink-0" />

<div class="flex size-full flex-1 flex-col overflow-hidden md:flex-row md:gap-4 md:p-4">
<ListPanel />
<EntryPanel />
</div>
</NotificationsContext.Provider>

<CreateFeedModal />
</NotificationContext.Provider>
);
};
7 changes: 7 additions & 0 deletions ui/src/stores/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createStore } from 'solid-js/store';

export const [modalStore, setModalStore] = createStore({
addFeed: false,
});

export const modalOpen = (modal: keyof typeof modalStore) => modalStore[modal];

0 comments on commit 9fb364c

Please sign in to comment.