Add photo chooser empty states
This commit is contained in:
parent
f244b8ce94
commit
8a6131d539
@ -11,7 +11,7 @@ import { FiMoreHorizontal } from 'react-icons/fi';
|
|||||||
import MoreMenuItem from './MoreMenuItem';
|
import MoreMenuItem from './MoreMenuItem';
|
||||||
import { clearGlobalFocus } from '@/utility/dom';
|
import { clearGlobalFocus } from '@/utility/dom';
|
||||||
import { FaChevronRight } from 'react-icons/fa6';
|
import { FaChevronRight } from 'react-icons/fa6';
|
||||||
import { menuSurfaceStyles } from '../primitives/surface';
|
import { MENU_SURFACE_STYLES } from '../primitives/surface';
|
||||||
|
|
||||||
export type MoreMenuSection = {
|
export type MoreMenuSection = {
|
||||||
label?: string
|
label?: string
|
||||||
@ -101,7 +101,10 @@ export default function MoreMenu({
|
|||||||
onCloseAutoFocus={e => e.preventDefault()}
|
onCloseAutoFocus={e => e.preventDefault()}
|
||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={menuSurfaceStyles(className)}
|
className={clsx(
|
||||||
|
MENU_SURFACE_STYLES,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{header && <div className={clsx(
|
{header && <div className={clsx(
|
||||||
'px-3 pt-3 pb-2 text-dim uppercase',
|
'px-3 pt-3 pb-2 text-dim uppercase',
|
||||||
@ -157,7 +160,7 @@ export default function MoreMenu({
|
|||||||
</DropdownMenu.SubTrigger>
|
</DropdownMenu.SubTrigger>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.SubContent
|
<DropdownMenu.SubContent
|
||||||
className={menuSurfaceStyles()}
|
className={MENU_SURFACE_STYLES}
|
||||||
>
|
>
|
||||||
{item.items.map(item =>
|
{item.items.map(item =>
|
||||||
<div key={item.label} className="px-1">
|
<div key={item.label} className="px-1">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx/lite';
|
import clsx from 'clsx/lite';
|
||||||
|
|
||||||
export const menuSurfaceStyles = (className?: string) => clsx(
|
export const MENU_SURFACE_STYLES = clsx(
|
||||||
'z-10',
|
'z-10',
|
||||||
'min-w-[8rem]',
|
'min-w-[8rem]',
|
||||||
'component-surface',
|
'component-surface',
|
||||||
@ -12,5 +12,4 @@ export const menuSurfaceStyles = (className?: string) => clsx(
|
|||||||
'data-[side=top]:animate-fade-in-from-bottom',
|
'data-[side=top]:animate-fade-in-from-bottom',
|
||||||
'data-[side=bottom]:animate-fade-in-from-top',
|
'data-[side=bottom]:animate-fade-in-from-top',
|
||||||
'data-[side=right]:animate-fade-in-from-top',
|
'data-[side=right]:animate-fade-in-from-top',
|
||||||
className,
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,17 +9,16 @@ import {
|
|||||||
import clsx from 'clsx/lite';
|
import clsx from 'clsx/lite';
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||||
import ImageMedium from '@/components/image/ImageMedium';
|
import ImageMedium from '@/components/image/ImageMedium';
|
||||||
import { menuSurfaceStyles } from '@/components/primitives/surface';
|
import { MENU_SURFACE_STYLES } from '@/components/primitives/surface';
|
||||||
import { IoSearch } from 'react-icons/io5';
|
import { IoSearch } from 'react-icons/io5';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import usePhotoQuery from '../usePhotoQuery';
|
import usePhotoQuery from '../usePhotoQuery';
|
||||||
import { BiChevronDown } from 'react-icons/bi';
|
import { BiChevronRight } from 'react-icons/bi';
|
||||||
import SegmentMenu from '@/components/SegmentMenu';
|
import SegmentMenu from '@/components/SegmentMenu';
|
||||||
import IconFavs from '@/components/icons/IconFavs';
|
import IconFavs from '@/components/icons/IconFavs';
|
||||||
import InfinitePhotoScroll from '../InfinitePhotoScroll';
|
import InfinitePhotoScroll from '../InfinitePhotoScroll';
|
||||||
|
import AdminEmptyState from '@/admin/AdminEmptyState';
|
||||||
// TODO:
|
import { TbPhotoSearch } from 'react-icons/tb';
|
||||||
// Create empty state for all modes, including no search results
|
|
||||||
|
|
||||||
type Mode = 'all' | 'favs' | 'search';
|
type Mode = 'all' | 'favs' | 'search';
|
||||||
|
|
||||||
@ -66,6 +65,7 @@ export default function FieldsetPhotoChooser({
|
|||||||
photos: photosQuery,
|
photos: photosQuery,
|
||||||
isLoading: isLoadingPhotoQuery,
|
isLoading: isLoadingPhotoQuery,
|
||||||
reset: resetPhotoQuery,
|
reset: resetPhotoQuery,
|
||||||
|
resultsNotFound,
|
||||||
} = usePhotoQuery({ query, isPrivate: true });
|
} = usePhotoQuery({ query, isPrivate: true });
|
||||||
|
|
||||||
const reset = useCallback((resetMenu?: boolean) => {
|
const reset = useCallback((resetMenu?: boolean) => {
|
||||||
@ -125,11 +125,18 @@ export default function FieldsetPhotoChooser({
|
|||||||
'font-sans',
|
'font-sans',
|
||||||
'text-xs text-medium font-medium uppercase tracking-wider',
|
'text-xs text-medium font-medium uppercase tracking-wider',
|
||||||
'py-1',
|
'py-1',
|
||||||
|
'select-none',
|
||||||
)}>
|
)}>
|
||||||
<span className="grow truncate text-left">
|
<span className="grow truncate text-left">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<BiChevronDown size={18} />
|
<BiChevronRight
|
||||||
|
size={18}
|
||||||
|
className={clsx(
|
||||||
|
'transition-transform ',
|
||||||
|
isOpen && 'rotate-90',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
'flex size-[6rem]',
|
'flex size-[6rem]',
|
||||||
@ -144,8 +151,11 @@ export default function FieldsetPhotoChooser({
|
|||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
onCloseAutoFocus={e => e.preventDefault()}
|
onCloseAutoFocus={e => e.preventDefault()}
|
||||||
align="start"
|
align="start"
|
||||||
sideOffset={10}
|
sideOffset={-80}
|
||||||
className={menuSurfaceStyles('z-20 rounded-2xl pb-0 overflow-auto')}
|
className={clsx(
|
||||||
|
MENU_SURFACE_STYLES,
|
||||||
|
'z-20 rounded-2xl pb-0 overflow-auto',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<SegmentMenu
|
<SegmentMenu
|
||||||
className="pt-1 pb-2 px-1.5"
|
className="pt-1 pb-2 px-1.5"
|
||||||
@ -169,25 +179,45 @@ export default function FieldsetPhotoChooser({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
'flex items-center transition-all overflow-hidden',
|
'w-[18rem] h-[20rem] overflow-y-auto',
|
||||||
showQuery ? 'h-12 opacity-100' : 'h-0 opacity-0',
|
|
||||||
)}>
|
|
||||||
<div className="p-1 border-t border-medium w-full">
|
|
||||||
<input
|
|
||||||
id="query"
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
placeholder="Search for a photo"
|
|
||||||
className="block w-full m-0 border-none outline-none"
|
|
||||||
value={query}
|
|
||||||
onChange={e => setQuery(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={clsx(
|
|
||||||
'w-[18rem] max-h-[20rem] overflow-y-auto',
|
|
||||||
'space-y-0.5',
|
'space-y-0.5',
|
||||||
)}>
|
)}>
|
||||||
|
<div className={clsx(
|
||||||
|
'flex items-center transition-all overflow-hidden',
|
||||||
|
showQuery ? 'h-12 opacity-100' : 'h-0 opacity-0',
|
||||||
|
)}>
|
||||||
|
<div className="w-full px-1.5">
|
||||||
|
<input
|
||||||
|
id="query"
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search for a photo"
|
||||||
|
className={clsx(
|
||||||
|
'block w-full m-0 outline-none',
|
||||||
|
'rounded-full border border-dim',
|
||||||
|
'mb-2',
|
||||||
|
)}
|
||||||
|
value={query}
|
||||||
|
onChange={e => setQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showQuery && resultsNotFound &&
|
||||||
|
<AdminEmptyState
|
||||||
|
icon={<IoSearch className="text-dim" />}
|
||||||
|
className="translate-y-8"
|
||||||
|
includeContainer={false}
|
||||||
|
>
|
||||||
|
No photos found
|
||||||
|
</AdminEmptyState>}
|
||||||
|
{!showQuery && photosToShow.length === 0 &&
|
||||||
|
<AdminEmptyState
|
||||||
|
icon={<TbPhotoSearch className="text-dim" />}
|
||||||
|
className="translate-y-16"
|
||||||
|
includeContainer={false}
|
||||||
|
>
|
||||||
|
No photos
|
||||||
|
</AdminEmptyState>}
|
||||||
<div className={CLASSNAME_GRID}>
|
<div className={CLASSNAME_GRID}>
|
||||||
{photosToShow.map(photo => renderPhotoButton(photo))}
|
{photosToShow.map(photo => renderPhotoButton(photo))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,6 +28,11 @@ export default function usePhotoQuery({
|
|||||||
|
|
||||||
const [photos, setPhotos] = useState<Photo[]>([]);
|
const [photos, setPhotos] = useState<Photo[]>([]);
|
||||||
|
|
||||||
|
const resultsNotFound =
|
||||||
|
queryDebounced.length >= minimumQueryLength &&
|
||||||
|
!isLoading &&
|
||||||
|
photos.length === 0;
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
setPhotos([]);
|
setPhotos([]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -62,6 +67,7 @@ export default function usePhotoQuery({
|
|||||||
queryFormatted,
|
queryFormatted,
|
||||||
photos,
|
photos,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
resultsNotFound,
|
||||||
reset,
|
reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user