feat: enhance Masonry component with reposition functionality

- Added a `reposition` method to the Masonry component, allowing for dynamic layout adjustments.
- Updated MasonryRoot to utilize the new ref for repositioning when photos change.
- Introduced a new script command "web" in package.json for easier development with Vite.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-07-12 00:22:41 +08:00
parent 440e051351
commit 6c702ef9f1
3 changed files with 25 additions and 3 deletions

View File

@@ -14,7 +14,8 @@
"format": "prettier --write \"src/**/*.ts\" ",
"lint": "eslint --fix",
"serve": "vite preview",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"web": "vite"
},
"dependencies": {
"@afilmory/data": "workspace:*",

View File

@@ -22,6 +22,9 @@ import * as React from 'react'
import { useScrollViewElement } from '~/components/ui/scroll-areas/hooks'
export interface MasonryRef {
reposition: () => void
}
/**
* A "batteries included" masonry grid which includes all of the implementation details below. This component is the
* easiest way to get off and running in your app, before switching to more advanced implementations, if necessary.
@@ -30,7 +33,9 @@ import { useScrollViewElement } from '~/components/ui/scroll-areas/hooks'
*
* @param props
*/
export const Masonry = <Item,>(props: MasonryProps<Item>) => {
export const Masonry = <Item,>(
props: MasonryProps<Item> & { ref?: React.Ref<MasonryRef> },
) => {
const [scrollTop, setScrollTop] = React.useState(0)
const [isScrolling, setIsScrolling] = React.useState(false)
const scrollElement = useScrollViewElement()
@@ -91,6 +96,14 @@ export const Masonry = <Item,>(props: MasonryProps<Item>) => {
props,
) as any
const [positionIndex, setPositionIndex] = React.useState(0)
React.useImperativeHandle(props.ref, () => ({
reposition: () => {
setPositionIndex((i) => i + 1)
},
}))
// Workaround for https://github.com/jaredLunde/masonic/issues/12
const itemCounter = React.useRef<number>(props.items.length)
@@ -102,7 +115,9 @@ export const Masonry = <Item,>(props: MasonryProps<Item>) => {
itemCounter.current = props.items.length
}
nextProps.positioner = usePositioner(nextProps, [shrunk && Math.random()])
nextProps.positioner = usePositioner(nextProps, [
shrunk ? Math.random() + positionIndex : positionIndex,
])
nextProps.resizeObserver = useResizeObserver(nextProps.positioner)
nextProps.scrollTop = scrollTop

View File

@@ -16,6 +16,7 @@ import type { PhotoManifest } from '~/types/photo'
import type { ActionType } from './ActionGroup'
import { ActionGroup, ActionPanel } from './ActionGroup'
import { FloatingActionButton } from './FloatingActionButton'
import type { MasonryRef } from './Masonic'
import { Masonry } from './Masonic'
import { MasonryHeaderMasonryItem } from './MasonryHeaderMasonryItem'
import { MasonryPhotoItem } from './MasonryPhotoItem'
@@ -51,6 +52,10 @@ export const MasonryRoot = () => {
const [containerWidth, setContainerWidth] = useState(0)
const photos = usePhotos()
const masonryRef = useRef<MasonryRef>(null)
// useEffect(() => {
// nextFrame(() => masonryRef.current?.reposition())
// }, [photos])
const { dateRange, handleRender } = useVisiblePhotosDateRange(photos)
const scrollElement = useScrollViewElement()
@@ -163,6 +168,7 @@ export const MasonryRoot = () => {
<div className="p-1 lg:px-0 lg:pb-0 [&_*]:!select-none">
{isMobile && <MasonryHeaderMasonryItem className="mb-1" />}
<Masonry<MasonryItemType>
ref={masonryRef}
items={useMemo(
() => (isMobile ? photos : [MasonryHeaderItem.default, ...photos]),
[photos, isMobile],