diff --git a/src/admin/AdminBatchEditPanel.tsx b/src/admin/AdminBatchEditPanel.tsx index 24638a59..708a2e50 100644 --- a/src/admin/AdminBatchEditPanel.tsx +++ b/src/admin/AdminBatchEditPanel.tsx @@ -1,53 +1,9 @@ -'use client'; +import { getUniqueTagsCached } from '@/photo/cache'; +import AdminBatchEditPanelClient from './AdminBatchEditPanelClient'; -import Note from '@/components/Note'; -import LoaderButton from '@/components/primitives/LoaderButton'; -import SiteGrid from '@/components/SiteGrid'; -import { useAppState } from '@/state/AppState'; -import { clsx } from 'clsx/lite'; -import { IoCloseSharp } from 'react-icons/io5'; -import DeleteButton from './DeleteButton'; - -export default function AdminBatchEditPanel() { - const { - isUserSignedIn, - selectedPhotoIds, - setSelectedPhotoIds, - } = useAppState(); - - return isUserSignedIn && selectedPhotoIds !== undefined - ? - {selectedPhotoIds.length > 0 && - <> - - Tag ... - - - } - } - onClick={() => setSelectedPhotoIds?.(undefined)} - /> - } - hideIcon - > - {selectedPhotoIds.length === 0 - ? 'Select photos below' - : <> - {selectedPhotoIds.length} - {selectedPhotoIds.length === 1 ? ' photo' : ' photos'} - {' '} - selected - } - } /> - : null; +export default async function AdminBatchEditPanel() { + const existingTags = await getUniqueTagsCached(); + return ( + + ); } diff --git a/src/admin/AdminBatchEditPanelClient.tsx b/src/admin/AdminBatchEditPanelClient.tsx new file mode 100644 index 00000000..b3b98b35 --- /dev/null +++ b/src/admin/AdminBatchEditPanelClient.tsx @@ -0,0 +1,96 @@ +'use client'; + +import Note from '@/components/Note'; +import LoaderButton from '@/components/primitives/LoaderButton'; +import SiteGrid from '@/components/SiteGrid'; +import { useAppState } from '@/state/AppState'; +import { clsx } from 'clsx/lite'; +import { IoCloseSharp } from 'react-icons/io5'; +import DeleteButton from './DeleteButton'; +import { useState } from 'react'; +import TagInput from '@/components/TagInput'; +import { convertTagsForForm, Tags } from '@/tag'; + +export default function AdminBatchEditPanelClient({ + existingTags, +}: { + existingTags: Tags +}) { + const { + isUserSignedIn, + selectedPhotoIds, + setSelectedPhotoIds, + } = useAppState(); + + const [tags, setTags] = useState(); + const isTagging = tags !== undefined; + + const photosPlural = selectedPhotoIds?.length === 1 ? 'photo' : 'photos'; + + const renderPhotoText = () => selectedPhotoIds?.length === 0 + ? 'Select photos below' + : `${selectedPhotoIds?.length ?? 0} ${photosPlural} selected`; + + const renderActions = () => isTagging + ? <> + setTags(undefined)} + > + Cancel + + + Apply Tags + + + : <> + {(selectedPhotoIds?.length ?? 0) > 0 && + <> + setTags('')}> + Tag ... + + + } + } + onClick={() => setSelectedPhotoIds?.(undefined)} + /> + ; + + return isUserSignedIn && selectedPhotoIds !== undefined + ? + {renderActions()} + } + spaceChildren={false} + hideIcon + > + {isTagging + ? + :
+ {renderPhotoText()} +
} + } /> + : null; +} diff --git a/src/components/Container.tsx b/src/components/Container.tsx index 4dad11b4..54d640f1 100644 --- a/src/components/Container.tsx +++ b/src/components/Container.tsx @@ -12,7 +12,12 @@ export default function Container({ children: ReactNode className?: string color?: 'gray' | 'blue' | 'red' | 'yellow' - padding?: 'loose' | 'normal' | 'tight' + padding?: + 'loose' | + 'normal' | + 'tight' | + 'tight-cta-right' | + 'tight-cta-right-left' centered?: boolean spaceChildren?: boolean } ) { @@ -46,6 +51,8 @@ export default function Container({ case 'loose': return 'p-4 md:p-24'; case 'normal': return 'p-4 md:p-8'; case 'tight': return 'py-1.5 px-2.5'; + case 'tight-cta-right': return 'py-1.5 pl-2.5 pr-1.5'; + case 'tight-cta-right-left': return 'py-1.5 px-1.5'; } }; diff --git a/src/components/Note.tsx b/src/components/Note.tsx index 719b5897..500313f7 100644 --- a/src/components/Note.tsx +++ b/src/components/Note.tsx @@ -4,32 +4,34 @@ import AnimateItems from './AnimateItems'; import { IoInformationCircleOutline } from 'react-icons/io5'; import { clsx } from 'clsx/lite'; -export default function Note({ - children, - className, - color = 'blue', - icon, - animate, - cta, - hideIcon, -}: { +export default function Note(props: { icon?: ReactNode animate?: boolean cta?: ReactNode hideIcon?: boolean } & ComponentProps) { + const { + icon, + animate, + cta, + hideIcon, + color = 'blue', + padding, + children, + ...rest + } = props; + return ( -
+
{!hideIcon && {cta && - + {cta} }
diff --git a/src/components/TagInput.tsx b/src/components/TagInput.tsx index 6303b776..910cf948 100644 --- a/src/components/TagInput.tsx +++ b/src/components/TagInput.tsx @@ -260,7 +260,8 @@ export default function TagInput({ className={clsx( 'grow !min-w-0 !p-0 -my-2 text-xl', '!border-none !ring-transparent', - 'placeholder:text-dim', + 'placeholder:text-dim placeholder:text-[15px]', + 'placeholder:translate-y-[-1.5px]', )} size={10} value={inputText} diff --git a/src/components/primitives/LoaderButton.tsx b/src/components/primitives/LoaderButton.tsx index 002a26c5..c63683cf 100644 --- a/src/components/primitives/LoaderButton.tsx +++ b/src/components/primitives/LoaderButton.tsx @@ -10,6 +10,7 @@ export default function LoaderButton(props: { spinnerColor?: SpinnerColor styleAs?: 'button' | 'link' | 'link-without-hover' hideTextOnMobile?: boolean + confirmText?: string shouldPreventDefault?: boolean primary?: boolean } & ButtonHTMLAttributes) { @@ -20,6 +21,7 @@ export default function LoaderButton(props: { spinnerColor, styleAs = 'button', hideTextOnMobile = true, + confirmText, shouldPreventDefault, primary, type = 'button', @@ -35,7 +37,9 @@ export default function LoaderButton(props: { type={type} onClick={e => { if (shouldPreventDefault) { e.preventDefault(); } - onClick?.(e); + if (!confirmText || confirm(confirmText)) { + onClick?.(e); + } }} className={clsx( ...(styleAs !== 'button'