diff --git a/src/components/primitives/EntityLink.tsx b/src/components/primitives/EntityLink.tsx index aa494f43..c77ac81b 100644 --- a/src/components/primitives/EntityLink.tsx +++ b/src/components/primitives/EntityLink.tsx @@ -107,9 +107,14 @@ export default function EntityLink({ {hoverEntity} } - {isLoading && } + {isLoading && + } } diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index c5c0bf21..36b51122 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -40,7 +40,7 @@ import ZoomControls, { ZoomControlsRef } from '@/components/image/ZoomControls'; import PhotoRecipe from './PhotoRecipe'; import { TbChecklist } from 'react-icons/tb'; import { IoCloseSharp } from 'react-icons/io5'; -import { AnimatePresence, motion } from 'framer-motion'; +import { AnimatePresence } from 'framer-motion'; import useRecipeState from './useRecipeState'; export default function PhotoLarge({ @@ -98,11 +98,12 @@ export default function PhotoLarge({ const showZoomControls = showZoomControlsProp && areZoomControlsShown; + const recipeRef = useRef(null); const { shouldShowRecipe, toggleRecipe, recipeButtonRef, - } = useRecipeState(); + } = useRecipeState(recipeRef); const tags = sortTags(photo.tags, primaryTag); @@ -173,22 +174,21 @@ export default function PhotoLarge({ /> - {shouldShowRecipe && photo.fujifilmRecipe && photo.filmSimulation && - +
+ {shouldShowRecipe && photo.fujifilmRecipe && photo.filmSimulation && - } + />} +
; diff --git a/src/photo/PhotoRecipe.tsx b/src/photo/PhotoRecipe.tsx index 18d25255..b881fe57 100644 --- a/src/photo/PhotoRecipe.tsx +++ b/src/photo/PhotoRecipe.tsx @@ -12,10 +12,12 @@ import useClickInsideOutside from '@/utility/useClickInsideOutside'; import clsx from 'clsx/lite'; import { ReactNode, useRef, RefObject } from 'react'; import { IoCloseCircle } from 'react-icons/io5'; +import { motion } from 'framer-motion'; const addSign = (value = 0) => value < 0 ? value : `+${value}`; export default function PhotoRecipe({ + ref: refExternal, recipe: { dynamicRange, whiteBalance = DEFAULT_WHITE_BALANCE, @@ -38,6 +40,7 @@ export default function PhotoRecipe({ onClose, externalTriggerRef, }: { + ref?: RefObject recipe: FujifilmRecipe simulation: FilmSimulation iso?: string @@ -48,7 +51,7 @@ export default function PhotoRecipe({ const ref = useRef(null); useClickInsideOutside({ - htmlElements: [ref, externalTriggerRef], + htmlElements: [ref, refExternal, externalTriggerRef], onClickOutside: onClose, }); @@ -67,11 +70,12 @@ export default function PhotoRecipe({
-
+
{typeof value === 'number' ? addSign(value) : value}
{label &&
@@ -149,26 +156,17 @@ export default function PhotoRecipe({ )} {renderRow(<> {renderDataSquare( - grainEffect.roughness === 'off' - ? 'NONE' - : <> - {grainEffect.roughness === 'strong' - ? 'STR' - : grainEffect.roughness === 'weak' - ? 'WK' - : 'OFF'} - {'/'} - {grainEffect.size === 'large' - ? 'LG' - : grainEffect.size === 'small' - ? 'SM' : 'OFF'} - , - 'Grain', + grainEffect.roughness.toLocaleUpperCase(), + grainEffect.size === 'large' + ? 'Large Grain' + : grainEffect.size === 'small' + ? 'Small Grain' + : 'Grain', )} {renderDataSquare(bwAdjustment ?? 0, 'BW ADJ')} {renderDataSquare(bwMagentaGreen ?? 0, 'BW M/G')} )}
-
+ ); } diff --git a/src/photo/useRecipeState.ts b/src/photo/useRecipeState.ts index f6af4935..678163b0 100644 --- a/src/photo/useRecipeState.ts +++ b/src/photo/useRecipeState.ts @@ -6,9 +6,12 @@ import { import { usePathname } from 'next/navigation'; import { SEARCH_PARAM_SHOW } from '@/app/paths'; import { useSearchParams } from 'next/navigation'; -import { useCallback, useRef, useState } from 'react'; +import { RefObject, useCallback, useEffect, useRef, useState } from 'react'; +import { isElementEntirelyInViewport } from '@/utility/dom'; -export default function useRecipeState() { +export default function useRecipeState( + ref?: RefObject, +) { const pathname = usePathname(); const params = useSearchParams(); @@ -54,6 +57,12 @@ export default function useRecipeState() { } }, [pathComponents, photoId, shouldShowRecipe]); + useEffect(() => { + if (shouldShowRecipe && !isElementEntirelyInViewport(ref?.current)) { + ref?.current?.scrollIntoView({ behavior: 'smooth' }); + } + }, [ref, shouldShowRecipe]); + return { toggleRecipe, recipeButtonRef, diff --git a/src/utility/dom.ts b/src/utility/dom.ts new file mode 100644 index 00000000..a8e92ecd --- /dev/null +++ b/src/utility/dom.ts @@ -0,0 +1,20 @@ +export const isElementEntirelyInViewport = ( + element?: HTMLElement | null, +) => { + if (element) { + const rect = element.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= ( + window.innerHeight || + document.documentElement.clientHeight + ) && + rect.right <= ( + window.innerWidth || document.documentElement.clientWidth + ) + ); + } else { + return false; + } +};