diff --git a/app/admin/components/page.tsx b/app/admin/components/page.tsx index 5df02c44..9869f3ff 100644 --- a/app/admin/components/page.tsx +++ b/app/admin/components/page.tsx @@ -1,111 +1,10 @@ -'use client'; +import AdminComponentPageClient from '@/admin/AdminComponentPageClient'; +import { getPhotosCached } from '@/photo/cache'; -import FieldsetTag from '@/tag/FieldsetTag'; -import AppGrid from '@/components/AppGrid'; -import FieldsetWithStatus from '@/components/FieldsetWithStatus'; -import IconHidden from '@/components/icons/IconHidden'; -import IconLock from '@/components/icons/IconLock'; -import SelectMenu from '@/components/SelectMenu'; -import StatusIcon from '@/components/StatusIcon'; -import clsx from 'clsx/lite'; -import { useState } from 'react'; +export default async function ComponentsPage() { + const photos = await getPhotosCached({ limit: 1}); -export default function ComponentsPage() { - const [value, setValue] = useState('visible'); return ( - -
- - - - -
-
- {}} - onError={() => {}} - openOnLoad={false} - /> -
-
- {}} - /> -
-
- , - label: 'Always visible', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'hidden', - accessoryStart: , - label: 'Hide from feeds', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'private', - accessoryStart: , - label: 'Private', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'private1', - accessoryStart: , - label: 'Private', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'private4', - accessoryStart: , - label: 'Private', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'private2', - accessoryStart: , - label: 'Private', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }, { - value: 'private3', - accessoryStart: , - label: 'Private', - accessoryEnd: '× 2', - note: 'Exclude photo from core feeds', - }]} - /> -
- } - /> + ); } diff --git a/src/admin/AdminComponentPageClient.tsx b/src/admin/AdminComponentPageClient.tsx new file mode 100644 index 00000000..bd05c38c --- /dev/null +++ b/src/admin/AdminComponentPageClient.tsx @@ -0,0 +1,138 @@ +'use client'; + +import FieldsetTag from '@/tag/FieldsetTag'; +import AppGrid from '@/components/AppGrid'; +import FieldsetWithStatus from '@/components/FieldsetWithStatus'; +import IconHidden from '@/components/icons/IconHidden'; +import IconLock from '@/components/icons/IconLock'; +import SelectMenu from '@/components/SelectMenu'; +import StatusIcon from '@/components/StatusIcon'; +import clsx from 'clsx/lite'; +import { useState } from 'react'; +import { Photo } from '@/photo'; +import FieldsetPhotoQuery from '@/photo/FieldsetPhotoQuery'; +import FieldsetPhotoChooser from '@/photo/FieldsetPhotoChooser'; + +export default function ComponentsPageClient({ + photo, +}: { + photo: Photo +}) { + const [valuePhoto, setValuePhoto] = useState(photo?.id ?? ''); + const [valuePhotoChooser, setValuePhotoChooser] = useState(photo?.id ?? ''); + + const [value, setValue] = useState('visible'); + + return ( + +
+ + + + +
+
+ +
+
+ +
+
+ {}} + onError={() => {}} + openOnLoad={false} + /> +
+
+ {}} + /> +
+
+ , + label: 'Always visible', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'hidden', + accessoryStart: , + label: 'Hide from feeds', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'private', + accessoryStart: , + label: 'Private', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'private1', + accessoryStart: , + label: 'Private', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'private4', + accessoryStart: , + label: 'Private', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'private2', + accessoryStart: , + label: 'Private', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }, { + value: 'private3', + accessoryStart: , + label: 'Private', + accessoryEnd: '× 2', + note: 'Exclude photo from core feeds', + }]} + /> +
+ } + /> + ); +} diff --git a/src/components/FieldsetWithStatus.tsx b/src/components/FieldsetWithStatus.tsx index 7e0cc779..30bc5554 100644 --- a/src/components/FieldsetWithStatus.tsx +++ b/src/components/FieldsetWithStatus.tsx @@ -71,7 +71,7 @@ export default function FieldsetWithStatus({ tagOptionsShouldParameterize?: boolean tagOptionsDefaultIcon?: ReactNode tagOptionsDefaultIconSelected?: ReactNode - tagOptionsLabelOverride?: (value: string) => string + tagOptionsLabelOverride?: (value: string) => string | undefined tagOptionsAllowNewValues?: boolean tagOptionsAccessory?: ReactNode tagOptionsOnInputTextChange?: (value: string) => void diff --git a/src/components/TagInput.tsx b/src/components/TagInput.tsx index 361d03b5..32c6a6d5 100644 --- a/src/components/TagInput.tsx +++ b/src/components/TagInput.tsx @@ -41,7 +41,7 @@ export default function TagInput({ name: string value?: string options?: AnnotatedTag[] - labelForValueOverride?: (value: string) => string + labelForValueOverride?: (value: string) => string | undefined defaultIcon?: ReactNode defaultIconSelected?: ReactNode accessory?: ReactNode @@ -416,7 +416,7 @@ export default function TagInput({
void + photo?: Photo +}) { + return ( + <> + + + + + + + e.preventDefault()} + align="start" + sideOffset={10} + // alignOffset={-10} + className={clsx( + 'z-20', + 'min-w-[8rem]', + 'component-surface', + 'p-1.5', + 'not-dark:shadow-lg not-dark:shadow-gray-900/10', + 'data-[side=top]:dark:shadow-[0_0px_40px_rgba(0,0,0,0.6)]', + 'data-[side=bottom]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]', + 'data-[side=right]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]', + 'data-[side=top]:animate-fade-in-from-bottom', + 'data-[side=bottom]:animate-fade-in-from-top', + 'data-[side=right]:animate-fade-in-from-top', + )}> +
+ +
+
+
+
+ + ); +} \ No newline at end of file diff --git a/src/photo/FieldsetPhotoQuery.tsx b/src/photo/FieldsetPhotoQuery.tsx new file mode 100644 index 00000000..80138cf9 --- /dev/null +++ b/src/photo/FieldsetPhotoQuery.tsx @@ -0,0 +1,69 @@ +'use client'; + +import FieldsetWithStatus from '@/components/FieldsetWithStatus'; +import { Photo } from '.'; +import { useEffect, useState } from 'react'; +import { AnnotatedTag } from './form'; +import { useDebounce } from 'use-debounce'; +import PhotoSmall from './PhotoSmall'; +import { getPhotosAction } from './actions'; + +const convertPhotoToAnnotatedTag = (photo: Photo): AnnotatedTag => ({ + value: photo.id, + label: photo.title, + icon:
+ +
, +}); + +export default function FieldsetPhotoQuery({ + label, + photos = [], + value, + onChange, +}: { + label: string + photos?: Photo[] + value: string + onChange: (value: string) => void +}) { + const [query, setQuery] = useState(''); + const [queryDebounced] = useDebounce(query, 500); + const [isQuerying, setIsQuerying] = useState(false); + + const [photoOptions, setPhotoOptions] = useState(photos + .map(convertPhotoToAnnotatedTag), + ); + + useEffect(() => { + if (queryDebounced) { + // eslint-disable-next-line react-hooks/set-state-in-effect + setIsQuerying(true); + getPhotosAction({ query: queryDebounced }) + .then(photos => { + setPhotoOptions(photos.map(convertPhotoToAnnotatedTag)); + }) + .finally(() => { + setIsQuerying(false); + }); + } else { + setPhotoOptions([]); + } + }, [queryDebounced]); + + return ( + + photoOptions.find(option => option.value === value)?.label} + tagOptionsAllowNewValues={false} + tagOptionsShouldParameterize={false} + tagOptionsLimit={1} + loading={isQuerying} + /> + ); +}