Create cmd-k affordance in nav
This commit is contained in:
parent
0d9ba09dee
commit
02cfa4ee52
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -2,6 +2,7 @@
|
|||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"ABCDEFGHIJKLMNOP",
|
"ABCDEFGHIJKLMNOP",
|
||||||
"Acros",
|
"Acros",
|
||||||
|
"affordance",
|
||||||
"ARROWLEFT",
|
"ARROWLEFT",
|
||||||
"ARROWRIGHT",
|
"ARROWRIGHT",
|
||||||
"Astia",
|
"Astia",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { clsx } from 'clsx/lite';
|
|||||||
import { IBM_Plex_Mono } from 'next/font/google';
|
import { IBM_Plex_Mono } from 'next/font/google';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { BASE_URL, SITE_DESCRIPTION, SITE_TITLE } from '@/site/config';
|
import { BASE_URL, SITE_DESCRIPTION, SITE_TITLE } from '@/site/config';
|
||||||
import StateProvider from '@/state/AppStateProvider';
|
import AppStateProvider from '@/state/AppStateProvider';
|
||||||
import ThemeProviderClient from '@/site/ThemeProviderClient';
|
import ThemeProviderClient from '@/site/ThemeProviderClient';
|
||||||
import Nav from '@/site/Nav';
|
import Nav from '@/site/Nav';
|
||||||
import ToasterWithThemes from '@/toast/ToasterWithThemes';
|
import ToasterWithThemes from '@/toast/ToasterWithThemes';
|
||||||
@ -73,7 +73,7 @@ export default function RootLayout({
|
|||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body className={ibmPlexMono.variable}>
|
<body className={ibmPlexMono.variable}>
|
||||||
<StateProvider>
|
<AppStateProvider>
|
||||||
<ThemeProviderClient>
|
<ThemeProviderClient>
|
||||||
<main className={clsx(
|
<main className={clsx(
|
||||||
'mx-3 mb-3',
|
'mx-3 mb-3',
|
||||||
@ -94,7 +94,7 @@ export default function RootLayout({
|
|||||||
</main>
|
</main>
|
||||||
<CommandK />
|
<CommandK />
|
||||||
</ThemeProviderClient>
|
</ThemeProviderClient>
|
||||||
</StateProvider>
|
</AppStateProvider>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<SpeedInsights />
|
<SpeedInsights />
|
||||||
<PhotoEscapeHandler />
|
<PhotoEscapeHandler />
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
import { ReactNode, useEffect, useState } from 'react';
|
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
@ -10,6 +10,8 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi';
|
import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi';
|
||||||
import { IoInvertModeSharp } from 'react-icons/io5';
|
import { IoInvertModeSharp } from 'react-icons/io5';
|
||||||
|
import { useAppState } from '@/state';
|
||||||
|
import { parameterize } from '@/utility/string';
|
||||||
|
|
||||||
const LISTENER_KEYDOWN = 'keydown';
|
const LISTENER_KEYDOWN = 'keydown';
|
||||||
const MINIMUM_QUERY_LENGTH = 2;
|
const MINIMUM_QUERY_LENGTH = 2;
|
||||||
@ -34,9 +36,22 @@ export default function CommandKClient({
|
|||||||
onQueryChange?: (query: string) => Promise<CommandKSection[]>
|
onQueryChange?: (query: string) => Promise<CommandKSection[]>
|
||||||
sections?: CommandKSection[]
|
sections?: CommandKSection[]
|
||||||
}) {
|
}) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const {
|
||||||
const [queryRaw, setQueryRaw] = useState('');
|
isCommandKOpen: isOpen,
|
||||||
const [queryDebounced] = useDebounce(queryRaw, 500, { trailing: true });
|
setIsCommandKOpen: setIsOpen,
|
||||||
|
} = useAppState();
|
||||||
|
|
||||||
|
// Raw query values
|
||||||
|
const [queryLiveRaw, setQueryLive] = useState('');
|
||||||
|
const [queryDebouncedRaw] =
|
||||||
|
useDebounce(queryLiveRaw, 500, { trailing: true });
|
||||||
|
const isPlaceholderVisible = queryLiveRaw === '';
|
||||||
|
|
||||||
|
// Parameterized query values
|
||||||
|
const queryLive = useMemo(() =>
|
||||||
|
parameterize(queryLiveRaw), [queryLiveRaw]);
|
||||||
|
const queryDebounced = useMemo(() =>
|
||||||
|
parameterize(queryDebouncedRaw), [queryDebouncedRaw]);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [queriedSections, setQueriedSections] = useState<CommandKSection[]>([]);
|
const [queriedSections, setQueriedSections] = useState<CommandKSection[]>([]);
|
||||||
@ -49,12 +64,12 @@ export default function CommandKClient({
|
|||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsOpen((open) => !open);
|
setIsOpen?.((open) => !open);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener(LISTENER_KEYDOWN, down);
|
document.addEventListener(LISTENER_KEYDOWN, down);
|
||||||
return () => document.removeEventListener(LISTENER_KEYDOWN, down);
|
return () => document.removeEventListener(LISTENER_KEYDOWN, down);
|
||||||
}, []);
|
}, [setIsOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryDebounced.length >= MINIMUM_QUERY_LENGTH) {
|
if (queryDebounced.length >= MINIMUM_QUERY_LENGTH) {
|
||||||
@ -67,16 +82,17 @@ export default function CommandKClient({
|
|||||||
}, [queryDebounced, onQueryChange]);
|
}, [queryDebounced, onQueryChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryRaw === '') {
|
if (queryLive === '') {
|
||||||
setQueriedSections([]);
|
setQueriedSections([]);
|
||||||
} else if (queryRaw.length >= MINIMUM_QUERY_LENGTH) {
|
setIsLoading(false);
|
||||||
|
} else if (queryLive.length >= MINIMUM_QUERY_LENGTH) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
}
|
}
|
||||||
}, [queryRaw]);
|
}, [queryLive]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setQueryRaw('');
|
setQueryLive('');
|
||||||
setQueriedSections([]);
|
setQueriedSections([]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -114,30 +130,37 @@ export default function CommandKClient({
|
|||||||
>
|
>
|
||||||
<Modal
|
<Modal
|
||||||
anchor='top'
|
anchor='top'
|
||||||
onClose={() => setIsOpen(false)}
|
onClose={() => setIsOpen?.(false)}
|
||||||
fast
|
fast
|
||||||
>
|
>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="relative">
|
<div className="relative w-full max-w-full min-w-0">
|
||||||
<Command.Input
|
<Command.Input
|
||||||
onChangeCapture={(e) => setQueryRaw(e.currentTarget.value)}
|
onChangeCapture={(e) => setQueryLive(e.currentTarget.value)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-full',
|
'w-full !max-w-full !min-w-0',
|
||||||
'focus:ring-0',
|
'focus:ring-0',
|
||||||
|
isPlaceholderVisible || isLoading && '!pr-8',
|
||||||
'!border-gray-200 dark:!border-gray-800',
|
'!border-gray-200 dark:!border-gray-800',
|
||||||
'focus:border-gray-200 focus:dark:border-gray-800',
|
'focus:border-gray-200 focus:dark:border-gray-800',
|
||||||
'placeholder:text-gray-400/80',
|
'placeholder:text-gray-400/80',
|
||||||
'placeholder:dark:text-gray-700',
|
'placeholder:dark:text-gray-700',
|
||||||
)}
|
)}
|
||||||
style={{ paddingRight: '2rem' }}
|
|
||||||
placeholder="Search photos, views, settings ..."
|
placeholder="Search photos, views, settings ..."
|
||||||
/>
|
/>
|
||||||
{isLoading &&
|
{isLoading &&
|
||||||
<span className="absolute top-2.5 right-3">
|
<span className={clsx(
|
||||||
|
'absolute top-2.5 right-0 w-8',
|
||||||
|
'flex items-center justify-center translate-y-[2px]',
|
||||||
|
)}>
|
||||||
<Spinner size={16} />
|
<Spinner size={16} />
|
||||||
</span>}
|
</span>}
|
||||||
</div>
|
</div>
|
||||||
<Command.List className="relative max-h-72 overflow-y-scroll">
|
<Command.List className={clsx(
|
||||||
|
'relative overflow-y-scroll',
|
||||||
|
'h-36 sm:h-auto',
|
||||||
|
'sm:max-h-72',
|
||||||
|
)}>
|
||||||
<Command.Empty className="mt-1 pl-3 text-dim">
|
<Command.Empty className="mt-1 pl-3 text-dim">
|
||||||
{isLoading ? 'Searching ...' : 'No results found'}
|
{isLoading ? 'Searching ...' : 'No results found'}
|
||||||
</Command.Empty>
|
</Command.Empty>
|
||||||
@ -185,7 +208,7 @@ export default function CommandKClient({
|
|||||||
'data-[selected=true]:dark:bg-gray-900/75',
|
'data-[selected=true]:dark:bg-gray-900/75',
|
||||||
)}
|
)}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setIsOpen(false);
|
setIsOpen?.(false);
|
||||||
action?.();
|
action?.();
|
||||||
if (path) {
|
if (path) {
|
||||||
router.push(path);
|
router.push(path);
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export default function Modal({
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'fixed inset-0 z-50 flex justify-center',
|
'fixed inset-0 z-50 flex justify-center',
|
||||||
anchor === 'top'
|
anchor === 'top'
|
||||||
? 'items-start pt-4 sm:pt-24'
|
? 'items-start pt-4 xs:pt-12 sm:pt-24'
|
||||||
: 'items-center',
|
: 'items-center',
|
||||||
'bg-black',
|
'bg-black',
|
||||||
)}
|
)}
|
||||||
@ -70,16 +70,16 @@ export default function Modal({
|
|||||||
<AnimateItems
|
<AnimateItems
|
||||||
duration={fast ? 0.1 : 0.3}
|
duration={fast ? 0.1 : 0.3}
|
||||||
items={[<div
|
items={[<div
|
||||||
|
ref={contentRef}
|
||||||
key="modalContent"
|
key="modalContent"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
'w-[calc(100vw-1.5rem)] xs:w-[min(500px,90vw)]',
|
||||||
'p-3 rounded-lg',
|
'p-3 rounded-lg',
|
||||||
|
'md:p-4 md:rounded-xl',
|
||||||
'bg-white dark:bg-black',
|
'bg-white dark:bg-black',
|
||||||
'dark:border dark:border-gray-800',
|
'dark:border dark:border-gray-800',
|
||||||
'md:p-4 md:rounded-xl',
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ width: 'min(500px, 90vw)' }}
|
|
||||||
ref={contentRef}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>]}
|
</div>]}
|
||||||
|
|||||||
27
src/site/IconSearch.tsx
Normal file
27
src/site/IconSearch.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
|
const INTRINSIC_WIDTH = 28;
|
||||||
|
const INTRINSIC_HEIGHT = 24;
|
||||||
|
|
||||||
|
export default function IconSearch({
|
||||||
|
width = INTRINSIC_WIDTH,
|
||||||
|
includeTitle = true,
|
||||||
|
}: {
|
||||||
|
width?: number;
|
||||||
|
includeTitle?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
height={(INTRINSIC_HEIGHT * width) / INTRINSIC_WIDTH}
|
||||||
|
viewBox="0 0 28 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{includeTitle && <title>Search</title>}
|
||||||
|
<circle cx="13.5" cy="11.5" r="4.875" strokeWidth="1.25" />
|
||||||
|
<path d="M17 15L21 19" strokeWidth="1.25" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,31 +0,0 @@
|
|||||||
/* eslint-disable max-len */
|
|
||||||
|
|
||||||
const INTRINSIC_WIDTH = 28;
|
|
||||||
const INTRINSIC_HEIGHT = 24;
|
|
||||||
|
|
||||||
export default function IconSets({
|
|
||||||
width = INTRINSIC_WIDTH,
|
|
||||||
includeTitle = true,
|
|
||||||
}: {
|
|
||||||
width?: number
|
|
||||||
includeTitle?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
width={width}
|
|
||||||
height={INTRINSIC_HEIGHT * width / INTRINSIC_WIDTH}
|
|
||||||
viewBox="0 0 28 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
{includeTitle && <title>Photo Sets</title>}
|
|
||||||
<path d="M18.5 16.375L9.75 16.375" strokeWidth="1.25"/>
|
|
||||||
<path d="M22.25 12.125L9.75 12.125" strokeWidth="1.25"/>
|
|
||||||
<path d="M20.5 7.875L9.75 7.875" strokeWidth="1.25"/>
|
|
||||||
<path d="M7.25 16.375L6.25 16.375" strokeWidth="1.25" strokeLinecap="round"/>
|
|
||||||
<path d="M7.25 12.125L6.25 12.125" strokeWidth="1.25" strokeLinecap="round"/>
|
|
||||||
<path d="M7.25 7.875L6.25 7.875" strokeWidth="1.25" strokeLinecap="round"/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -11,7 +11,6 @@ import {
|
|||||||
isPathAdmin,
|
isPathAdmin,
|
||||||
isPathGrid,
|
isPathGrid,
|
||||||
isPathProtected,
|
isPathProtected,
|
||||||
isPathSets,
|
|
||||||
isPathSignIn,
|
isPathSignIn,
|
||||||
} from '@/site/paths';
|
} from '@/site/paths';
|
||||||
import AnimateItems from '../components/AnimateItems';
|
import AnimateItems from '../components/AnimateItems';
|
||||||
@ -40,8 +39,6 @@ export default function NavClient({
|
|||||||
return 'full-frame';
|
return 'full-frame';
|
||||||
} else if (isPathGrid(pathname)) {
|
} else if (isPathGrid(pathname)) {
|
||||||
return 'grid';
|
return 'grid';
|
||||||
} else if (isPathSets(pathname)) {
|
|
||||||
return 'sets';
|
|
||||||
} else if (isPathProtected(pathname)) {
|
} else if (isPathProtected(pathname)) {
|
||||||
return 'admin';
|
return 'admin';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import Switcher from '@/components/Switcher';
|
|||||||
import SwitcherItem from '@/components/SwitcherItem';
|
import SwitcherItem from '@/components/SwitcherItem';
|
||||||
import IconFullFrame from '@/site/IconFullFrame';
|
import IconFullFrame from '@/site/IconFullFrame';
|
||||||
import IconGrid from '@/site/IconGrid';
|
import IconGrid from '@/site/IconGrid';
|
||||||
import { PATH_ADMIN_PHOTOS, PATH_GRID, PATH_SETS } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS, PATH_GRID } from '@/site/paths';
|
||||||
import { BiLockAlt } from 'react-icons/bi';
|
import { BiLockAlt } from 'react-icons/bi';
|
||||||
import IconSets from './IconSets';
|
import { useAppState } from '@/state';
|
||||||
|
import IconSearch from './IconSearch';
|
||||||
|
|
||||||
export type SwitcherSelection = 'full-frame' | 'grid' | 'sets' | 'admin';
|
export type SwitcherSelection = 'full-frame' | 'grid' | 'sets' | 'admin';
|
||||||
|
|
||||||
@ -15,7 +16,10 @@ export default function ViewSwitcher({
|
|||||||
currentSelection?: SwitcherSelection
|
currentSelection?: SwitcherSelection
|
||||||
showAdmin?: boolean
|
showAdmin?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
const { setIsCommandKOpen } = useAppState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
<Switcher>
|
<Switcher>
|
||||||
<SwitcherItem
|
<SwitcherItem
|
||||||
icon={<IconFullFrame />}
|
icon={<IconFullFrame />}
|
||||||
@ -29,13 +33,6 @@ export default function ViewSwitcher({
|
|||||||
active={currentSelection === 'grid'}
|
active={currentSelection === 'grid'}
|
||||||
noPadding
|
noPadding
|
||||||
/>
|
/>
|
||||||
<SwitcherItem
|
|
||||||
className="md:hidden"
|
|
||||||
icon={<IconSets />}
|
|
||||||
href={PATH_SETS}
|
|
||||||
active={currentSelection === 'sets'}
|
|
||||||
noPadding
|
|
||||||
/>
|
|
||||||
{showAdmin &&
|
{showAdmin &&
|
||||||
<SwitcherItem
|
<SwitcherItem
|
||||||
icon={<BiLockAlt size={16} className="translate-y-[-0.5px]" />}
|
icon={<BiLockAlt size={16} className="translate-y-[-0.5px]" />}
|
||||||
@ -43,5 +40,12 @@ export default function ViewSwitcher({
|
|||||||
active={currentSelection === 'admin'}
|
active={currentSelection === 'admin'}
|
||||||
/>}
|
/>}
|
||||||
</Switcher>
|
</Switcher>
|
||||||
|
<Switcher>
|
||||||
|
<SwitcherItem
|
||||||
|
icon={<IconSearch />}
|
||||||
|
onClick={() => setIsCommandKOpen?.(true)}
|
||||||
|
/>
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { FilmSimulation } from '@/simulation';
|
|||||||
// Core paths
|
// Core paths
|
||||||
export const PATH_ROOT = '/';
|
export const PATH_ROOT = '/';
|
||||||
export const PATH_GRID = '/grid';
|
export const PATH_GRID = '/grid';
|
||||||
export const PATH_SETS = '/sets';
|
|
||||||
export const PATH_ADMIN = '/admin';
|
export const PATH_ADMIN = '/admin';
|
||||||
export const PATH_API = '/api';
|
export const PATH_API = '/api';
|
||||||
export const PATH_SIGN_IN = '/sign-in';
|
export const PATH_SIGN_IN = '/sign-in';
|
||||||
@ -55,7 +54,6 @@ export const PATHS_ADMIN = [
|
|||||||
export const PATHS_TO_CACHE = [
|
export const PATHS_TO_CACHE = [
|
||||||
PATH_ROOT,
|
PATH_ROOT,
|
||||||
PATH_GRID,
|
PATH_GRID,
|
||||||
PATH_SETS,
|
|
||||||
PATH_OG,
|
PATH_OG,
|
||||||
PATH_PHOTO_DYNAMIC,
|
PATH_PHOTO_DYNAMIC,
|
||||||
PATH_TAG_DYNAMIC,
|
PATH_TAG_DYNAMIC,
|
||||||
@ -236,9 +234,6 @@ export const checkPathPrefix = (pathname = '', prefix: string) =>
|
|||||||
export const isPathGrid = (pathname?: string) =>
|
export const isPathGrid = (pathname?: string) =>
|
||||||
checkPathPrefix(pathname, PATH_GRID);
|
checkPathPrefix(pathname, PATH_GRID);
|
||||||
|
|
||||||
export const isPathSets = (pathname?: string) =>
|
|
||||||
checkPathPrefix(pathname, PATH_SETS);
|
|
||||||
|
|
||||||
export const isPathSignIn = (pathname?: string) =>
|
export const isPathSignIn = (pathname?: string) =>
|
||||||
checkPathPrefix(pathname, PATH_SIGN_IN);
|
checkPathPrefix(pathname, PATH_SIGN_IN);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { AppStateContext } from '.';
|
|||||||
import { AnimationConfig } from '@/components/AnimateItems';
|
import { AnimationConfig } from '@/components/AnimateItems';
|
||||||
import usePathnames from '@/utility/usePathnames';
|
import usePathnames from '@/utility/usePathnames';
|
||||||
|
|
||||||
export default function StateProvider({
|
export default function AppStateProvider({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -17,6 +17,8 @@ export default function StateProvider({
|
|||||||
const [nextPhotoAnimation, setNextPhotoAnimation] =
|
const [nextPhotoAnimation, setNextPhotoAnimation] =
|
||||||
useState<AnimationConfig>();
|
useState<AnimationConfig>();
|
||||||
|
|
||||||
|
const [isCommandKOpen, setIsCommandKOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setHasLoaded?.(true);
|
setHasLoaded?.(true);
|
||||||
}, [setHasLoaded]);
|
}, [setHasLoaded]);
|
||||||
@ -28,6 +30,8 @@ export default function StateProvider({
|
|||||||
hasLoaded,
|
hasLoaded,
|
||||||
setHasLoaded,
|
setHasLoaded,
|
||||||
nextPhotoAnimation,
|
nextPhotoAnimation,
|
||||||
|
isCommandKOpen,
|
||||||
|
setIsCommandKOpen,
|
||||||
setNextPhotoAnimation,
|
setNextPhotoAnimation,
|
||||||
clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined),
|
clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { Dispatch, SetStateAction, createContext, useContext } from 'react';
|
||||||
import { AnimationConfig } from '@/components/AnimateItems';
|
import { AnimationConfig } from '@/components/AnimateItems';
|
||||||
|
|
||||||
export interface AppStateContext {
|
export interface AppStateContext {
|
||||||
previousPathname?: string
|
previousPathname?: string
|
||||||
hasLoaded?: boolean
|
hasLoaded?: boolean
|
||||||
setHasLoaded?: (hasLoaded: boolean) => void
|
setHasLoaded?: Dispatch<SetStateAction<boolean>>
|
||||||
nextPhotoAnimation?: AnimationConfig
|
nextPhotoAnimation?: AnimationConfig
|
||||||
|
isCommandKOpen?: boolean
|
||||||
|
setIsCommandKOpen?: Dispatch<SetStateAction<boolean>>
|
||||||
setNextPhotoAnimation?: (animation?: AnimationConfig) => void
|
setNextPhotoAnimation?: (animation?: AnimationConfig) => void
|
||||||
clearNextPhotoAnimation?: () => void
|
clearNextPhotoAnimation?: () => void
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user