Centralize icon system

This commit is contained in:
Sam Becker 2025-03-16 15:44:55 -05:00
parent 4849d591d3
commit 0ca8823dae
44 changed files with 252 additions and 124 deletions

View File

@ -11,19 +11,20 @@ import {
PATH_GRID_INFERRED, PATH_GRID_INFERRED,
} from '@/app/paths'; } from '@/app/paths';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
import { ImCheckboxUnchecked } from 'react-icons/im'; import { IoArrowDown, IoArrowUp } from 'react-icons/io5';
import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { TbChecklist, TbPhoto } from 'react-icons/tb';
import { FiTag } from 'react-icons/fi';
import { BiLockAlt } from 'react-icons/bi';
import AdminAppInfoIcon from './AdminAppInfoIcon'; import AdminAppInfoIcon from './AdminAppInfoIcon';
import { PiSignOutBold } from 'react-icons/pi';
import { signOutAction } from '@/auth/actions'; import { signOutAction } from '@/auth/actions';
import { ComponentProps } from 'react'; import { ComponentProps } from 'react';
import { FaRegFolderOpen } from 'react-icons/fa';
import { FiUploadCloud } from 'react-icons/fi';
import useIsKeyBeingPressed from '@/utility/useIsKeyBeingPressed'; import useIsKeyBeingPressed from '@/utility/useIsKeyBeingPressed';
import IconSelectMultiple from '@/components/icons/IconSelectMultiple';
import IconPhoto from '@/components/icons/IconPhoto';
import IconUpload from '@/components/icons/IconUpload';
import IconRecipe from '@/components/icons/IconRecipe';
import IconTag from '@/components/icons/IconTag';
import IconFolder from '@/components/icons/IconFolder';
import IconSignOut from '@/components/icons/IconSignOut';
import IconLock from '@/components/icons/IconLock';
export default function AdminAppMenu({ export default function AdminAppMenu({
active, active,
@ -54,7 +55,7 @@ export default function AdminAppMenu({
const items: ComponentProps<typeof MoreMenu>['items'] = [{ const items: ComponentProps<typeof MoreMenu>['items'] = [{
label: 'Upload Photos', label: 'Upload Photos',
icon: <FiUploadCloud icon: <IconUpload
size={15} size={15}
className="translate-x-[0.5px] translate-y-[0.5px]" className="translate-x-[0.5px] translate-y-[0.5px]"
/>, />,
@ -73,7 +74,7 @@ export default function AdminAppMenu({
...photosCountTotal && { ...photosCountTotal && {
annotation: `${photosCountTotal}`, annotation: `${photosCountTotal}`,
}, },
icon: <TbPhoto icon: <IconPhoto
size={15} size={15}
className="translate-x-[-0.5px] translate-y-[0.5px]" className="translate-x-[-0.5px] translate-y-[0.5px]"
/>, />,
@ -85,7 +86,7 @@ export default function AdminAppMenu({
items.push({ items.push({
label: 'Uploads', label: 'Uploads',
annotation: `${uploadsCount}`, annotation: `${uploadsCount}`,
icon: <FaRegFolderOpen icon: <IconFolder
size={16} size={16}
className="translate-y-[0.5px]" className="translate-y-[0.5px]"
/>, />,
@ -97,7 +98,7 @@ export default function AdminAppMenu({
items.push({ items.push({
label: 'Manage Tags', label: 'Manage Tags',
annotation: `${tagsCount}`, annotation: `${tagsCount}`,
icon: <FiTag icon: <IconTag
size={15} size={15}
className="translate-y-[0.5px]" className="translate-y-[0.5px]"
/>, />,
@ -109,7 +110,7 @@ export default function AdminAppMenu({
items.push({ items.push({
label: 'Manage Recipes', label: 'Manage Recipes',
annotation: `${recipesCount}`, annotation: `${recipesCount}`,
icon: <TbChecklist icon: <IconRecipe
size={17} size={17}
className="translate-x-[-0.5px] translate-y-[0.5px]" className="translate-x-[-0.5px] translate-y-[0.5px]"
/>, />,
@ -122,13 +123,7 @@ export default function AdminAppMenu({
label: isSelecting label: isSelecting
? 'Exit Select' ? 'Exit Select'
: 'Edit Multiple', : 'Edit Multiple',
icon: isSelecting icon: <IconSelectMultiple {...{ isSelecting }} />,
? <IoCloseSharp
className="text-[18px] translate-x-[-1px] translate-y-[1px]"
/>
: <ImCheckboxUnchecked
className="translate-x-[-0.5px] text-[0.75rem]"
/>,
href: PATH_GRID_INFERRED, href: PATH_GRID_INFERRED,
action: () => { action: () => {
if (isSelecting) { if (isSelecting) {
@ -157,14 +152,17 @@ export default function AdminAppMenu({
: PATH_ADMIN_CONFIGURATION, : PATH_ADMIN_CONFIGURATION,
}, { }, {
label: 'Sign Out', label: 'Sign Out',
icon: <PiSignOutBold size={15} />, icon: <IconSignOut size={15} />,
action: () => signOutAction().then(clearAuthStateAndRedirect), action: () => signOutAction().then(clearAuthStateAndRedirect),
}); });
return ( return (
<MoreMenu <MoreMenu
header={<div className="flex items-center select-none"> header={<div className="flex items-center select-none">
<BiLockAlt size={17} className="inline-block w-5 mr-2" /> <IconLock
size={15}
className="inline-block w-5 mr-2 translate-x-[1px]"
/>
<span className="grow">Admin menu</span> <span className="grow">Admin menu</span>
</div>} </div>}
icon={<div className={clsx( icon={<div className={clsx(

View File

@ -15,8 +15,10 @@ import { tagMultiplePhotosAction } from '@/photo/actions';
import { toastSuccess } from '@/toast'; import { toastSuccess } from '@/toast';
import DeletePhotosButton from './DeletePhotosButton'; import DeletePhotosButton from './DeletePhotosButton';
import { photoQuantityText } from '@/photo'; import { photoQuantityText } from '@/photo';
import { FaArrowDown, FaCheck, FaRegStar } from 'react-icons/fa6'; import { FaArrowDown, FaCheck } from 'react-icons/fa6';
import ResponsiveText from '@/components/primitives/ResponsiveText'; import ResponsiveText from '@/components/primitives/ResponsiveText';
import IconFavs from '@/components/icons/IconFavs';
import IconTag from '@/components/icons/IconTag';
export default function AdminBatchEditPanelClient({ export default function AdminBatchEditPanelClient({
uniqueTags, uniqueTags,
@ -113,7 +115,7 @@ export default function AdminBatchEditPanelClient({
onFinish={() => setIsPerformingSelectEdit?.(false)} onFinish={() => setIsPerformingSelectEdit?.(false)}
/> />
<LoaderButton <LoaderButton
icon={<FaRegStar />} icon={<IconFavs />}
disabled={isPerformingSelectEdit} disabled={isPerformingSelectEdit}
confirmText={`Are you sure you want to favorite ${photosText}?`} confirmText={`Are you sure you want to favorite ${photosText}?`}
onClick={() => { onClick={() => {
@ -132,10 +134,9 @@ export default function AdminBatchEditPanelClient({
<LoaderButton <LoaderButton
onClick={() => setTags('')} onClick={() => setTags('')}
disabled={isPerformingSelectEdit} disabled={isPerformingSelectEdit}
icon={<IconTag size={15} className="translate-y-[1.5px]" />}
> >
<ResponsiveText shortText="Tag"> Tag ...
Tag ...
</ResponsiveText>
</LoaderButton> </LoaderButton>
</>} </>}
<LoaderButton <LoaderButton

View File

@ -21,8 +21,8 @@ import PhotoTagFieldset from './PhotoTagFieldset';
import DeleteUploadButton from './DeleteUploadButton'; import DeleteUploadButton from './DeleteUploadButton';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
import { pluralize } from '@/utility/string'; import { pluralize } from '@/utility/string';
import { FaRegStar } from 'react-icons/fa6'; import IconFavs from '@/components/icons/IconFavs';
import { AiOutlineEyeInvisible } from 'react-icons/ai'; import IconHidden from '@/components/icons/IconHidden';
const UPLOAD_BATCH_SIZE = 4; const UPLOAD_BATCH_SIZE = 4;
@ -147,7 +147,7 @@ export default function AdminBatchUploadActions({
<div className="flex gap-8"> <div className="flex gap-8">
<FieldSetWithStatus <FieldSetWithStatus
label="Favorite" label="Favorite"
icon={<FaRegStar size={14} />} icon={<IconFavs size={14} />}
type="checkbox" type="checkbox"
value={favorite} value={favorite}
onChange={setFavorite} onChange={setFavorite}
@ -155,7 +155,7 @@ export default function AdminBatchUploadActions({
/> />
<FieldSetWithStatus <FieldSetWithStatus
label="Hidden" label="Hidden"
icon={<AiOutlineEyeInvisible size={16} />} icon={<IconHidden size={16} />}
type="checkbox" type="checkbox"
value={hidden} value={hidden}
onChange={setHidden} onChange={setHidden}

View File

@ -3,7 +3,7 @@
import { Photo } from '@/photo'; import { Photo } from '@/photo';
import AdminPhotosTable from '@/admin/AdminPhotosTable'; import AdminPhotosTable from '@/admin/AdminPhotosTable';
import LoaderButton from '@/components/primitives/LoaderButton'; import LoaderButton from '@/components/primitives/LoaderButton';
import IconGrSync from '@/app/IconGrSync'; import IconGrSync from '@/components/icons/IconGrSync';
import Note from '@/components/Note'; import Note from '@/components/Note';
import AdminChildPage from '@/components/AdminChildPage'; import AdminChildPage from '@/components/AdminChildPage';
import { PATH_ADMIN_PHOTOS } from '@/app/paths'; import { PATH_ADMIN_PHOTOS } from '@/app/paths';

View File

@ -7,7 +7,6 @@ import {
syncPhotoAction, syncPhotoAction,
toggleFavoritePhotoAction, toggleFavoritePhotoAction,
} from '@/photo/actions'; } from '@/photo/actions';
import { FaRegEdit, FaRegStar, FaStar } from 'react-icons/fa';
import { import {
Photo, Photo,
deleteConfirmationTextForPhoto, deleteConfirmationTextForPhoto,
@ -21,9 +20,11 @@ import { useAppState } from '@/state/AppState';
import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll'; import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll';
import { MdOutlineFileDownload } from 'react-icons/md'; import { MdOutlineFileDownload } from 'react-icons/md';
import MoreMenuItem from '@/components/more/MoreMenuItem'; import MoreMenuItem from '@/components/more/MoreMenuItem';
import IconGrSync from '@/app/IconGrSync'; import IconGrSync from '@/components/icons/IconGrSync';
import { isPhotoOutdated } from '@/photo/outdated'; import { isPhotoOutdated } from '@/photo/outdated';
import InsightsIndicatorDot from './insights/InsightsIndicatorDot'; import InsightsIndicatorDot from './insights/InsightsIndicatorDot';
import IconFavs from '@/components/icons/IconFavs';
import IconEdit from '@/components/icons/IconEdit';
export default function AdminPhotoMenuClient({ export default function AdminPhotoMenuClient({
photo, photo,
@ -42,12 +43,10 @@ export default function AdminPhotoMenuClient({
const shouldRedirectFav = isPathFavs(path) && isFav; const shouldRedirectFav = isPathFavs(path) && isFav;
const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path; const shouldRedirectDelete = pathForPhoto({ photo: photo.id }) === path;
const favIconClass = 'translate-x-[-1px] translate-y-[0.5px]';
const items = useMemo(() => { const items = useMemo(() => {
const items: ComponentProps<typeof MoreMenuItem>[] = [{ const items: ComponentProps<typeof MoreMenuItem>[] = [{
label: 'Edit', label: 'Edit',
icon: <FaRegEdit icon: <IconEdit
size={15} size={15}
className="translate-x-[0.5px] translate-y-[-0.5px]" className="translate-x-[0.5px] translate-y-[-0.5px]"
/>, />,
@ -56,15 +55,11 @@ export default function AdminPhotoMenuClient({
if (includeFavorite) { if (includeFavorite) {
items.push({ items.push({
label: isFav ? 'Unfavorite' : 'Favorite', label: isFav ? 'Unfavorite' : 'Favorite',
icon: isFav icon: <IconFavs
? <FaStar size={14}
size={14} className="translate-x-[-1px]"
className={`text-amber-500 ${favIconClass}`} highlight={isFav}
/> />,
: <FaRegStar
size={14}
className={favIconClass}
/>,
action: () => toggleFavoritePhotoAction( action: () => toggleFavoritePhotoAction(
photo.id, photo.id,
shouldRedirectFav, shouldRedirectFav,

View File

@ -1,5 +1,5 @@
import IconEdit from '@/components/icons/IconEdit';
import PathLoaderButton from '@/components/primitives/PathLoaderButton'; import PathLoaderButton from '@/components/primitives/PathLoaderButton';
import { FaRegEdit } from 'react-icons/fa';
export default function EditButton ({ export default function EditButton ({
path, path,
@ -9,7 +9,7 @@ export default function EditButton ({
return ( return (
<PathLoaderButton <PathLoaderButton
path={path} path={path}
icon={<FaRegEdit size={15} className="translate-y-[0.5px]" />} icon={<IconEdit size={15} className="translate-y-[0.5px]" />}
> >
Edit Edit
</PathLoaderButton> </PathLoaderButton>

View File

@ -1,6 +1,6 @@
import LoaderButton from '@/components/primitives/LoaderButton'; import LoaderButton from '@/components/primitives/LoaderButton';
import { syncPhotoAction } from '@/photo/actions'; import { syncPhotoAction } from '@/photo/actions';
import IconGrSync from '@/app/IconGrSync'; import IconGrSync from '@/components/icons/IconGrSync';
import { toastSuccess } from '@/toast'; import { toastSuccess } from '@/toast';
import { ComponentProps, useState } from 'react'; import { ComponentProps, useState } from 'react';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';

View File

@ -1,23 +1,26 @@
import Switcher from '@/components/Switcher'; import Switcher from '@/components/Switcher';
import SwitcherItem from '@/components/SwitcherItem'; import SwitcherItem from '@/components/SwitcherItem';
import IconFeed from '@/app/IconFeed'; import IconFeed from '@/components/icons/IconFeed';
import IconGrid from '@/app/IconGrid'; import IconGrid from '@/components/icons/IconGrid';
import { import {
PATH_FEED_INFERRED, PATH_FEED_INFERRED,
PATH_GRID_INFERRED, PATH_GRID_INFERRED,
} from '@/app/paths'; } from '@/app/paths';
import IconSearch from './IconSearch'; import IconSearch from '../components/icons/IconSearch';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
import { GRID_HOMEPAGE_ENABLED } from './config'; import { GRID_HOMEPAGE_ENABLED } from './config';
import AdminAppMenu from '@/admin/AdminAppMenu'; import AdminAppMenu from '@/admin/AdminAppMenu';
import Spinner from '@/components/Spinner'; import Spinner from '@/components/Spinner';
import clsx from 'clsx/lite';
export type SwitcherSelection = 'feed' | 'grid' | 'admin'; export type SwitcherSelection = 'feed' | 'grid' | 'admin';
export default function ViewSwitcher({ export default function ViewSwitcher({
currentSelection, currentSelection,
className,
}: { }: {
currentSelection?: SwitcherSelection currentSelection?: SwitcherSelection
className?: string
}) { }) {
const { const {
isUserSignedIn, isUserSignedIn,
@ -42,7 +45,10 @@ export default function ViewSwitcher({
/>; />;
return ( return (
<div className="flex gap-1 sm:gap-2"> <div className={clsx(
'flex gap-1 sm:gap-2',
className,
)}>
<Switcher> <Switcher>
{GRID_HOMEPAGE_ENABLED ? renderItemGrid : renderItemFeed} {GRID_HOMEPAGE_ENABLED ? renderItemGrid : renderItemFeed}
{GRID_HOMEPAGE_ENABLED ? renderItemFeed : renderItemGrid} {GRID_HOMEPAGE_ENABLED ? renderItemFeed : renderItemGrid}

View File

@ -19,8 +19,8 @@ import {
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { FiLock } from 'react-icons/fi';
import { PATH_ADMIN_PHOTOS } from '@/app/paths'; import { PATH_ADMIN_PHOTOS } from '@/app/paths';
import IconLock from '@/components/icons/IconLock';
export default function SignInForm({ export default function SignInForm({
includeTitle = true, includeTitle = true,
@ -71,7 +71,7 @@ export default function SignInForm({
'flex gap-3 items-center justify-center', 'flex gap-3 items-center justify-center',
'self-start text-2xl mb-3.5', 'self-start text-2xl mb-3.5',
)}> )}>
<FiLock className="text-main translate-y-[0.5px]" /> <IconLock className="text-main translate-y-[0.5px]" />
<span className="text-main"> <span className="text-main">
Sign in Sign in
</span> </span>

View File

@ -4,7 +4,7 @@ import { Camera, formatCameraText, isCameraApple } from '.';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import { TbCamera } from 'react-icons/tb'; import IconCamera from '@/components/icons/IconCamera';
export default function PhotoCamera({ export default function PhotoCamera({
camera, camera,
@ -33,7 +33,7 @@ export default function PhotoCamera({
className="translate-x-[-1px] translate-y-[-0.5px]" className="translate-x-[-1px] translate-y-[-0.5px]"
size={15} size={15}
/> />
: <TbCamera : <IconCamera
size={15} size={15}
className="translate-x-[-0.5px]" className="translate-x-[-0.5px]"
/>} />}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { TbCamera } from 'react-icons/tb';
export default function IconCamera(props: IconBaseProps) {
return <TbCamera {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { FaRegEdit } from 'react-icons/fa';
export default function IconEdit(props: IconBaseProps) {
return <FaRegEdit {...props} />;
}

View File

@ -0,0 +1,16 @@
import { clsx } from 'clsx/lite';
import { IconBaseProps } from 'react-icons';
import { FaRegStar, FaStar } from 'react-icons/fa6';
export default function IconFavs({
highlight,
className,
...props
}: IconBaseProps & { highlight?: boolean}) {
return highlight
? <FaStar
{...props}
className={clsx('text-amber-500', className)}
/>
: <FaRegStar {...{ ...props, className }} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { IoFilmOutline } from 'react-icons/io5';
export default function IconFilmSimulation(props: IconBaseProps) {
return <IoFilmOutline {...props} />;
}

View File

@ -0,0 +1,17 @@
import { IconBaseProps } from 'react-icons';
import { TbCone } from 'react-icons/tb';
export default function IconFocalLength({
style,
...props
}: IconBaseProps) {
return <TbCone {...{
...props,
style: {
...style,
transform: `rotate(270deg)${style?.transform
? ` ${style.transform}`
: ''}`,
},
}} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { FaRegFolderOpen } from 'react-icons/fa';
export default function IconFolder(props: IconBaseProps) {
return <FaRegFolderOpen {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { AiOutlineEyeInvisible } from 'react-icons/ai';
export default function IconHidden(props: IconBaseProps) {
return <AiOutlineEyeInvisible {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { RiCameraLensLine } from 'react-icons/ri';
export default function IconLens(props: IconBaseProps) {
return <RiCameraLensLine {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { FiLock } from 'react-icons/fi';
export default function IconLock(props: IconBaseProps) {
return <FiLock {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { TbPhoto } from 'react-icons/tb';
export default function IconPhoto(props: IconBaseProps) {
return <TbPhoto {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { TbChecklist } from 'react-icons/tb';
export default function IconRecipe(props: IconBaseProps) {
return <TbChecklist {...props} />;
}

View File

@ -0,0 +1,28 @@
import clsx from 'clsx/lite';
import { IconBaseProps } from 'react-icons';
import { ImCheckboxUnchecked } from 'react-icons/im';
import { IoCloseSharp } from 'react-icons/io5';
export default function IconSelectMultiple({
isSelecting,
className,
...props
}: IconBaseProps & { isSelecting: boolean }) {
return isSelecting
? <IoCloseSharp {...{
...props,
className: clsx(
'text-[18px] translate-x-[-1px] translate-y-[0.5px]',
className,
),
}} />
: <ImCheckboxUnchecked
{...{
...props,
className: clsx(
'translate-x-[-0.5px] translate-y-[0.5px] text-[0.75rem]',
className,
),
}}
/>;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { PiSignOutBold } from 'react-icons/pi';
export default function IconSignOut(props: IconBaseProps) {
return <PiSignOutBold {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { FiTag } from 'react-icons/fi';
export default function IconTag(props: IconBaseProps) {
return <FiTag {...props} />;
}

View File

@ -0,0 +1,6 @@
import { IconBaseProps } from 'react-icons';
import { FiUploadCloud } from 'react-icons/fi';
export default function IconUpload(props: IconBaseProps) {
return <FiUploadCloud {...props} />;
}

View File

@ -2,8 +2,8 @@ import { pathForFocalLength } from '@/app/paths';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import { TbCone } from 'react-icons/tb';
import { formatFocalLength } from '.'; import { formatFocalLength } from '.';
import IconFocalLength from '@/components/icons/IconFocalLength';
export default function PhotoFocalLength({ export default function PhotoFocalLength({
focal, focal,
@ -21,7 +21,7 @@ export default function PhotoFocalLength({
<EntityLink <EntityLink
label={formatFocalLength(focal)} label={formatFocalLength(focal)}
href={pathForFocalLength(focal)} href={pathForFocalLength(focal)}
icon={<TbCone className="rotate-[270deg]" />} icon={<IconFocalLength />}
type={type} type={type}
className={className} className={className}
badged={badged} badged={badged}

View File

@ -8,9 +8,9 @@ import {
formatCameraText, formatCameraText,
isCameraApple, isCameraApple,
} from '@/camera'; } from '@/camera';
import { IoMdCamera } from 'react-icons/io';
import { NextImageSize } from '@/platforms/next-image'; import { NextImageSize } from '@/platforms/next-image';
import { AiFillApple } from 'react-icons/ai'; import { AiFillApple } from 'react-icons/ai';
import IconCamera from '@/components/icons/IconCamera';
export default function CameraImageResponse({ export default function CameraImageResponse({
camera: cameraProp, camera: cameraProp,
@ -47,10 +47,11 @@ export default function CameraImageResponse({
transform: `translateY(${-height * .002}px)`, transform: `translateY(${-height * .002}px)`,
}} }}
/> />
: <IoMdCamera : <IconCamera
size={height * .079} size={height * .09}
style={{ style={{
marginRight: height * .015, marginRight: height * .015,
transform: `translateY(${height * .001}px)`,
}} }}
/>, />,
title: formatCameraText(camera).toLocaleUpperCase(), title: formatCameraText(camera).toLocaleUpperCase(),

View File

@ -3,8 +3,8 @@ import ImageCaption from './components/ImageCaption';
import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer'; import ImageContainer from './components/ImageContainer';
import type { NextImageSize } from '@/platforms/next-image'; import type { NextImageSize } from '@/platforms/next-image';
import { TbCone } from 'react-icons/tb';
import { formatFocalLength } from '@/focal'; import { formatFocalLength } from '@/focal';
import IconFocalLength from '@/components/icons/IconFocalLength';
export default function FocalLengthImageResponse({ export default function FocalLengthImageResponse({
focal, focal,
@ -32,13 +32,13 @@ export default function FocalLengthImageResponse({
width, width,
height, height,
fontFamily, fontFamily,
icon: <TbCone icon: <span style={{
size={height * .075} display: 'flex',
style={{ transform: `translateY(${height * .002}px)`,
transform: `translateY(${height * .002}px) rotate(270deg)`, marginRight: height * .01,
marginRight: height * .01, }}>
}} <IconFocalLength size={height * .075} />
/>, </span>,
title: formatFocalLength(focal), title: formatFocalLength(focal),
}} /> }} />
</ImageContainer> </ImageContainer>

View File

@ -4,7 +4,7 @@ import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer'; import ImageContainer from './components/ImageContainer';
import { NextImageSize } from '@/platforms/next-image'; import { NextImageSize } from '@/platforms/next-image';
import { formatLensText, Lens, lensFromPhoto } from '@/lens'; import { formatLensText, Lens, lensFromPhoto } from '@/lens';
import { RiCameraLensLine } from 'react-icons/ri'; import IconLens from '@/components/icons/IconLens';
export default function LensImageResponse({ export default function LensImageResponse({
lens: lensProp, lens: lensProp,
@ -33,7 +33,7 @@ export default function LensImageResponse({
width, width,
height, height,
fontFamily, fontFamily,
icon: <RiCameraLensLine icon: <IconLens
size={height * .079} size={height * .079}
style={{ style={{
marginRight: height * .015, marginRight: height * .015,

View File

@ -4,10 +4,10 @@ import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer'; import ImageContainer from './components/ImageContainer';
import type { NextImageSize } from '@/platforms/next-image'; import type { NextImageSize } from '@/platforms/next-image';
import { formatTag } from '@/tag'; import { formatTag } from '@/tag';
import { TbChecklist } from 'react-icons/tb';
import { generateRecipeText, getPhotoWithRecipeFromPhotos } from '@/recipe'; import { generateRecipeText, getPhotoWithRecipeFromPhotos } from '@/recipe';
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
import { isStringFilmSimulation } from '@/platforms/fujifilm/simulation'; import { isStringFilmSimulation } from '@/platforms/fujifilm/simulation';
import IconRecipe from '@/components/icons/IconRecipe';
const MAX_RECIPE_LINES = 8; const MAX_RECIPE_LINES = 8;
export default function RecipeImageResponse({ export default function RecipeImageResponse({
@ -49,7 +49,6 @@ export default function RecipeImageResponse({
photos, photos,
width, width,
height, height,
gap: 0,
}} }}
/> />
<div <div
@ -65,10 +64,10 @@ export default function RecipeImageResponse({
fontFamily, fontFamily,
legacyBottomAlignment: false, legacyBottomAlignment: false,
gap: '0', gap: '0',
icon: <TbChecklist icon: <IconRecipe
size={height * .087} size={height * .087}
style={{ style={{
transform: `translateY(${height * .003}px)`, transform: `translateY(${height * .002}px)`,
marginRight: height * .02, marginRight: height * .02,
}} }}
/>, />,

View File

@ -1,10 +1,11 @@
import type { Photo } from '../photo'; import type { Photo } from '../photo';
import { FaStar, FaTag } from 'react-icons/fa';
import ImageCaption from './components/ImageCaption'; import ImageCaption from './components/ImageCaption';
import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer'; import ImageContainer from './components/ImageContainer';
import type { NextImageSize } from '@/platforms/next-image'; import type { NextImageSize } from '@/platforms/next-image';
import { formatTag, isTagFavs } from '@/tag'; import { formatTag, isTagFavs } from '@/tag';
import IconTag from '@/components/icons/IconTag';
import IconFavs from '@/components/icons/IconFavs';
export default function TagImageResponse({ export default function TagImageResponse({
tag, tag,
@ -33,7 +34,7 @@ export default function TagImageResponse({
height, height,
fontFamily, fontFamily,
icon: isTagFavs(tag) icon: isTagFavs(tag)
? <FaStar ? <IconFavs
size={height * .066} size={height * .066}
style={{ style={{
// Fix horizontal distortion in icon size // Fix horizontal distortion in icon size
@ -41,11 +42,11 @@ export default function TagImageResponse({
marginRight: height * .015, marginRight: height * .015,
}} }}
/> />
: <FaTag : <IconTag
size={height * .06} size={height * .0725}
style={{ style={{
transform: `translateY(${height * .006}px)`, transform: `translateY(${height * .007}px)`,
marginRight: height * .02, marginRight: height * .01,
}} }}
/>, />,
title: formatTag(tag).toLocaleUpperCase(), title: formatTag(tag).toLocaleUpperCase(),

View File

@ -1,6 +1,6 @@
import { Photo } from '../photo'; import { Photo } from '../photo';
import IconFeed from '@/app/IconFeed'; import IconFeed from '@/components/icons/IconFeed';
import IconGrid from '@/app/IconGrid'; import IconGrid from '@/components/icons/IconGrid';
import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImagePhotoGrid from './components/ImagePhotoGrid';
import { NextImageSize } from '@/platforms/next-image'; import { NextImageSize } from '@/platforms/next-image';

View File

@ -13,7 +13,7 @@ export default function ImagePhotoGrid({
widthArbitrary, widthArbitrary,
height, height,
imagePosition = 'center', imagePosition = 'center',
gap = 4, gap = 0,
imageStyle, imageStyle,
}: ({ }: ({
photos: Photo[] photos: Photo[]

View File

@ -3,7 +3,7 @@ import { Lens, formatLensText } from '.';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import { RiCameraLensLine } from 'react-icons/ri'; import IconLens from '@/components/icons/IconLens';
export default function PhotoLens({ export default function PhotoLens({
lens, lens,
@ -21,7 +21,7 @@ export default function PhotoLens({
<EntityLink <EntityLink
label={formatLensText(lens, 'short')} label={formatLensText(lens, 'short')}
href={pathForLens(lens)} href={pathForLens(lens)}
icon={<RiCameraLensLine icon={<IconLens
size={14} size={14}
className="translate-x-[-0.5px] translate-y-[-0.5px]" className="translate-x-[-0.5px] translate-y-[-0.5px]"
/>} />}

View File

@ -4,12 +4,9 @@ import { Cameras, sortCamerasWithCount } from '@/camera';
import PhotoCamera from '@/camera/PhotoCamera'; import PhotoCamera from '@/camera/PhotoCamera';
import HeaderList from '@/components/HeaderList'; import HeaderList from '@/components/HeaderList';
import PhotoTag from '@/tag/PhotoTag'; import PhotoTag from '@/tag/PhotoTag';
import { FaTag } from 'react-icons/fa';
import { IoMdCamera } from 'react-icons/io';
import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.'; import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.';
import { TAG_FAVS, TAG_HIDDEN, Tags, addHiddenToTags } from '@/tag'; import { TAG_FAVS, TAG_HIDDEN, Tags, addHiddenToTags } from '@/tag';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation'; import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation';
import FavsTag from '../tag/FavsTag'; import FavsTag from '../tag/FavsTag';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
@ -23,7 +20,10 @@ import {
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { Recipes, sortRecipesWithCount } from '@/recipe'; import { Recipes, sortRecipesWithCount } from '@/recipe';
import PhotoRecipe from '@/recipe/PhotoRecipe'; import PhotoRecipe from '@/recipe/PhotoRecipe';
import { TbChecklist } from 'react-icons/tb'; import IconCamera from '@/components/icons/IconCamera';
import IconRecipe from '@/components/icons/IconRecipe';
import IconTag from '@/components/icons/IconTag';
import IconFilmSimulation from '@/components/icons/IconFilmSimulation';
export default function PhotoGridSidebar({ export default function PhotoGridSidebar({
tags, tags,
@ -52,8 +52,8 @@ export default function PhotoGridSidebar({
? <HeaderList ? <HeaderList
key="tags" key="tags"
title='Tags' title='Tags'
icon={<FaTag icon={<IconTag
size={12} size={14}
className="translate-y-[1px]" className="translate-y-[1px]"
/>} />}
items={tagsIncludingHidden.map(({ tag, count }) => { items={tagsIncludingHidden.map(({ tag, count }) => {
@ -95,10 +95,7 @@ export default function PhotoGridSidebar({
? <HeaderList ? <HeaderList
key="cameras" key="cameras"
title="Cameras" title="Cameras"
icon={<IoMdCamera icon={<IconCamera size={15} />}
size={13}
className="translate-y-[-0.25px]"
/>}
items={cameras items={cameras
.sort(sortCamerasWithCount) .sort(sortCamerasWithCount)
.map(({ cameraKey, camera, count }) => .map(({ cameraKey, camera, count }) =>
@ -119,7 +116,7 @@ export default function PhotoGridSidebar({
? <HeaderList ? <HeaderList
key="recipes" key="recipes"
title="Recipes" title="Recipes"
icon={<TbChecklist icon={<IconRecipe
size={16} size={16}
className="translate-x-[-1px]" className="translate-x-[-1px]"
/>} />}
@ -141,9 +138,7 @@ export default function PhotoGridSidebar({
? <HeaderList ? <HeaderList
key="films" key="films"
title="Films" title="Films"
icon={<PhotoFilmSimulationIcon icon={<IconFilmSimulation size={15} />}
className="translate-y-[0.5px]"
/>}
items={simulations items={simulations
.sort(sortFilmSimulationsWithCount) .sort(sortFilmSimulationsWithCount)
.map(({ simulation, count }) => .map(({ simulation, count }) =>

View File

@ -284,11 +284,12 @@ export default function PhotoLarge({
<div> <div>
{(showCameraContent || showLensContent) && {(showCameraContent || showLensContent) &&
<div className="flex flex-col"> <div className="flex flex-col">
<PhotoCamera {showCameraContent &&
camera={camera} <PhotoCamera
contrast="medium" camera={camera}
prefetch={prefetchRelatedLinks} contrast="medium"
/> prefetch={prefetchRelatedLinks}
/>}
{showLensContent && {showLensContent &&
<PhotoLens <PhotoLens
lens={lens} lens={lens}

View File

@ -42,17 +42,17 @@ import { convertRecipesForForm, Recipes } from '@/recipe';
import deepEqual from 'fast-deep-equal/es6/react'; import deepEqual from 'fast-deep-equal/es6/react';
import ApplyRecipeTitleGloballyCheckbox from './ApplyRecipesGloballyCheckbox'; import ApplyRecipeTitleGloballyCheckbox from './ApplyRecipesGloballyCheckbox';
import { FilmSimulation } from '@/simulation'; import { FilmSimulation } from '@/simulation';
import { FaRegStar } from 'react-icons/fa6'; import IconFavs from '@/components/icons/IconFavs';
import { AiOutlineEyeInvisible } from 'react-icons/ai'; import IconHidden from '@/components/icons/IconHidden';
const THUMBNAIL_SIZE = 300; const THUMBNAIL_SIZE = 300;
const iconForKey = (key: keyof PhotoFormData) => { const iconForKey = (key: keyof PhotoFormData) => {
switch (key) { switch (key) {
case 'favorite': case 'favorite':
return <FaRegStar size={14} />; return <IconFavs size={14} />;
case 'hidden': case 'hidden':
return <AiOutlineEyeInvisible size={16} />; return <IconHidden size={16} />;
} }
}; };

View File

@ -2,10 +2,10 @@ import { pathForRecipe } from '@/app/paths';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import { TbChecklist } from 'react-icons/tb';
import { formatRecipe } from '.'; import { formatRecipe } from '.';
import clsx from 'clsx/lite'; import clsx from 'clsx/lite';
import { RefObject } from 'react'; import { RefObject } from 'react';
import IconRecipe from '@/components/icons/IconRecipe';
export default function PhotoRecipe({ export default function PhotoRecipe({
recipe, recipe,
@ -31,7 +31,7 @@ export default function PhotoRecipe({
title="Recipe" title="Recipe"
label={formatRecipe(recipe)} label={formatRecipe(recipe)}
href={pathForRecipe(recipe)} href={pathForRecipe(recipe)}
icon={<TbChecklist icon={<IconRecipe
size={16} size={16}
className={clsx( className={clsx(
badged badged

View File

@ -1,10 +1,9 @@
import { FaStar } from 'react-icons/fa';
import { TAG_FAVS } from '.'; import { TAG_FAVS } from '.';
import { pathForTag } from '@/app/paths'; import { pathForTag } from '@/app/paths';
import { clsx } from 'clsx/lite';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import IconFavs from '@/components/icons/IconFavs';
export default function FavsTag({ export default function FavsTag({
type, type,
@ -21,20 +20,19 @@ export default function FavsTag({
label={badged label={badged
? <span className="inline-flex gap-1 items-center"> ? <span className="inline-flex gap-1 items-center">
{TAG_FAVS} {TAG_FAVS}
<FaStar <IconFavs
size={10} size={10}
className="text-amber-500 translate-y-[-0.5px]" className="translate-y-[-0.5px]"
highlight
/> />
</span> </span>
: TAG_FAVS} : TAG_FAVS}
href={pathForTag(TAG_FAVS)} href={pathForTag(TAG_FAVS)}
icon={!badged && icon={!badged &&
<FaStar <IconFavs
size={12} size={13}
className={clsx( className="translate-x-[-0.5px] translate-y-[-0.5px]"
'text-amber-500', highlight
'translate-x-[-1px]',
)}
/>} />}
type={type} type={type}
className={className} className={className}

View File

@ -3,7 +3,7 @@ import { formatTag } from '.';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import { FiTag } from 'react-icons/fi'; import IconTag from '@/components/icons/IconTag';
export default function PhotoTag({ export default function PhotoTag({
tag, tag,
@ -21,7 +21,7 @@ export default function PhotoTag({
<EntityLink <EntityLink
label={formatTag(tag)} label={formatTag(tag)}
href={pathForTag(tag)} href={pathForTag(tag)}
icon={<FiTag size={14} className="translate-x-[0.5px]" />} icon={<IconTag size={14} className="translate-x-[0.5px]" />}
type={type} type={type}
className={className} className={className}
badged={badged} badged={badged}