Skip to content

Commit 9c3d65b

Browse files
committed
feat(FileOrganizer): thumbnailSize prop, fixed thumbnail auto size check
1 parent e45689c commit 9c3d65b

File tree

3 files changed

+87
-35
lines changed

3 files changed

+87
-35
lines changed

src/components/FileOrganizer/FileOrganizer.stories.tsx

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { boolean, number } from '@storybook/addon-knobs';
2-
import React, { FC, useCallback, useEffect, useState, useRef } from 'react';
2+
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
3+
import { FixedSizeGrid } from 'react-window';
34
import { useManagedFiles } from '../../hooks';
45
import { action } from '../../storybook-helpers/action/action';
56
import { createFile, FakeFile } from '../../storybook-helpers/data/files';
67
import { forwardAction } from '../../storybook-helpers/knobs/forwardAction';
78
import { integer } from '../../storybook-helpers/knobs/integer';
9+
import { Button } from '../Button';
810
import { FileOrganizer, FileOrganizerProps } from '../FileOrganizer';
911
import { Icon } from '../Icon';
1012
import { Thumbnail } from '../Thumbnail';
1113
import { ThumbnailDragLayer } from '../ThumbnailDragLayer';
1214
import readme from './README.md';
13-
import { FixedSizeGrid } from 'react-window';
14-
import { Button } from '../Button';
1515

1616
export default {
1717
title: 'Components/FileOrganizer',
@@ -24,9 +24,16 @@ interface TemplateProps {
2424
numFiles?: number;
2525
editable?: boolean;
2626
scrollToTop?: boolean;
27+
customSizedThumbnail?: boolean;
2728
}
2829

29-
const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable, scrollToTop }) => {
30+
const Template: FC<TemplateProps> = ({
31+
onRenderDragLayer,
32+
numFiles = 2,
33+
editable,
34+
scrollToTop,
35+
customSizedThumbnail,
36+
}) => {
3037
// This is the index organizing function.
3138
const [files, setFiles] = useState<FakeFile[]>([]);
3239
const handleOnMove = useCallback<NonNullable<FileOrganizerProps<FakeFile>['onMove']>>((fromIndex, toIndex) => {
@@ -45,6 +52,10 @@ const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable
4552
gridRef.current?.scrollTo({ scrollTop: 0, scrollLeft: 0 });
4653
};
4754

55+
const [largerSize, setLargerSize] = useState(false);
56+
const changeSize = () => setLargerSize((prev) => !prev);
57+
const size = largerSize ? { width: 200, height: 250 } : { width: 150, height: 200 };
58+
4859
// This is just a helper for adding or removing files.
4960
useEffect(() => {
5061
setFiles((prev) => {
@@ -75,26 +86,43 @@ const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable
7586
onDragChange={action('onDragChange')}
7687
onDeselectAll={action('onDeselectAll')}
7788
onSelectAll={action('onSelectAll')}
89+
thumbnailSize={customSizedThumbnail ? size : undefined}
7890
onRenderDragLayer={onRenderDragLayer ? () => <ThumbnailDragLayer /> : undefined}
79-
onRenderThumbnail={({ onRenderThumbnailProps }) => (
80-
<Thumbnail
81-
{...onRenderThumbnailProps}
82-
onRename={editable ? () => {} : undefined}
83-
buttonProps={
84-
editable
85-
? [
86-
{
87-
children: <Icon icon="Close" />,
88-
onClick: () => {},
89-
key: 0,
90-
},
91-
]
92-
: undefined
93-
}
94-
/>
95-
)}
91+
onRenderThumbnail={({ onRenderThumbnailProps, index }) =>
92+
customSizedThumbnail ? (
93+
<div
94+
style={{
95+
...size,
96+
display: 'flex',
97+
justifyContent: 'center',
98+
alignItems: 'center',
99+
background: 'lightblue',
100+
border: '1px solid orange',
101+
}}
102+
>
103+
Thumbnail {index + 1}
104+
</div>
105+
) : (
106+
<Thumbnail
107+
{...onRenderThumbnailProps}
108+
onRename={editable ? () => {} : undefined}
109+
buttonProps={
110+
editable
111+
? [
112+
{
113+
children: <Icon icon="Close" />,
114+
onClick: () => {},
115+
key: 0,
116+
},
117+
]
118+
: undefined
119+
}
120+
/>
121+
)
122+
}
96123
/>
97124
{scrollToTop && <Button onClick={onScrollToTop}>Scroll to top</Button>}
125+
{customSizedThumbnail && <Button onClick={changeSize}>Change thumbnail size</Button>}
98126
</>
99127
);
100128
};
@@ -111,6 +139,10 @@ export const WithCustomDragLayer = () => {
111139
const numFiles = number('number of files', 2, { min: 0, max: 16, step: 1, range: true });
112140
return <Template numFiles={numFiles} onRenderDragLayer />;
113141
};
142+
export const DifferentThumbnailSize = () => {
143+
const numFiles = number('number of files', 2, { min: 0, max: 16, step: 1, range: true });
144+
return <Template numFiles={numFiles} customSizedThumbnail />;
145+
};
114146
export const WithGridRef = () => {
115147
return <Template numFiles={100} scrollToTop />;
116148
};

src/components/FileOrganizer/FileOrganizer.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,31 @@ import React, {
88
KeyboardEventHandler,
99
MouseEventHandler,
1010
ReactNode,
11+
Ref,
1112
RefObject,
1213
useCallback,
1314
useEffect,
15+
useImperativeHandle,
1416
useRef,
1517
useState,
16-
Ref,
17-
useImperativeHandle,
1818
} from 'react';
1919
import { FixedSizeGrid } from 'react-window';
2020
import {
21+
focusableElementDomString,
2122
getRowAndColumnIndex,
2223
getSibling,
2324
isScrolledIntoView,
2425
ObjectWithId,
2526
THUMBNAIL_WIDTH,
26-
focusableElementDomString,
2727
} from '../../utils';
2828
import { DndMultiProvider } from '../DndMultiProvider';
2929
import { Draggable } from '../Draggable';
3030
import { DragLayer, DragLayerProps } from '../DragLayer';
3131
import { MemoAutoSizer } from './MemoAutoSizer';
3232

33+
type Size = { width: number; height: number };
34+
const defaultSize: Size = { width: THUMBNAIL_WIDTH, height: THUMBNAIL_WIDTH };
35+
3336
export interface FileOrganizerProps<F> extends HTMLAttributes<HTMLDivElement> {
3437
/**
3538
* A list of files to render out within the page organizer.
@@ -70,6 +73,12 @@ export interface FileOrganizerProps<F> extends HTMLAttributes<HTMLDivElement> {
7073
* If provided, the ref is attached to the `react-window` FixedSizeGrid.
7174
*/
7275
gridRef?: Ref<FixedSizeGrid>;
76+
/**
77+
* If you know exactly what size your thumbnail is going to be, you can input
78+
* the value here. Use this if the thumbnail is going to change sizes, since
79+
* `FileOrganizer` will only detect changes when files change.
80+
*/
81+
thumbnailSize?: { width: number; height: number };
7382
/**
7483
* On render function for generating the thumbnails for the page organizer.
7584
* If you do not want to build your own, try using the `Thumbnail` component.
@@ -140,6 +149,7 @@ export function FileOrganizer<F extends ObjectWithId>({
140149
draggingIds,
141150
padding,
142151
gridRef: _gridRef,
152+
thumbnailSize,
143153
className,
144154
onClick,
145155
onKeyDown,
@@ -156,19 +166,32 @@ export function FileOrganizer<F extends ObjectWithId>({
156166

157167
const [draggingId, setDraggingId] = useState<string>();
158168

159-
// Detect size of first item and use as size throughout.
160-
const [size, setSize] = useState({ width: THUMBNAIL_WIDTH, height: THUMBNAIL_WIDTH });
169+
// Get the width of the first item, or default if no first item found.
161170
const hasFiles = files.length > 0;
171+
const getSize = useCallback<() => Size>(() => {
172+
if (!hasFiles) return defaultSize;
173+
if (!fileOrganizerRef.current) return defaultSize;
174+
const draggableWrapper = fileOrganizerRef.current.querySelector('div[draggable="true"]');
175+
const draggableElement = draggableWrapper?.firstChild as Element | undefined;
176+
const firstItem = draggableElement?.firstChild as Element | undefined;
177+
if (!firstItem) return defaultSize;
178+
return firstItem.getBoundingClientRect();
179+
}, [hasFiles]);
180+
181+
// Detect size of first item and use as size throughout.
182+
const [size, setSize] = useState<Size>(() => thumbnailSize || getSize());
183+
184+
// Update size when getWidth ref changes (when hasFiles changes).
162185
useEffect(() => {
163-
if (!fileOrganizerRef.current) return;
164-
const firstItem = fileOrganizerRef.current.querySelector('div[draggable="true"]');
165-
if (!firstItem) return;
166-
const { width, height } = firstItem.getBoundingClientRect();
186+
if (thumbnailSize) setSize(thumbnailSize);
187+
if (files.length === 0) setSize(defaultSize);
167188
setSize((prev) => {
189+
const { width, height } = getSize();
168190
if (prev.width === width && prev.height === height) return prev;
169191
return { width, height };
170192
});
171-
}, [hasFiles]);
193+
// Watches all files to continuously check width and height.
194+
}, [files, getSize, thumbnailSize]);
172195

173196
const handleOnDragChange = useCallback(
174197
(id?: string) => {

src/components/Thumbnail/Thumbnail.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import classnames from 'classnames';
2-
import React, { MouseEvent, ReactNode, ReactText, useRef } from 'react';
2+
import React, { MouseEvent, ReactNode, ReactText } from 'react';
33
import { FileLike } from '../../data';
44
import { useAccessibleFocus, useFileSubscribe, useFocus } from '../../hooks';
55
import { ClickableDiv, ClickableDivProps } from '../ClickableDiv';
@@ -85,8 +85,6 @@ export function Thumbnail<F extends FileLike>({
8585
}: ThumbnailProps<F>) {
8686
const isUserTabbing = useAccessibleFocus(thumbnailFocusObservable);
8787

88-
const thumbnailRef = useRef<HTMLDivElement>(null);
89-
9088
const { focused, handleOnFocus, handleOnBlur } = useFocus(onFocus, onBlur);
9189

9290
const [thumbnail, thumbnailErr] = useFileSubscribe(file, (f) => f.thumbnail, 'onthumbnailchange');
@@ -122,7 +120,6 @@ export function Thumbnail<F extends FileLike>({
122120
<ClickableDiv
123121
{...divProps}
124122
className={thumbnailClass}
125-
ref={thumbnailRef}
126123
noFocusStyle
127124
disabled={disabled}
128125
onFocus={handleOnFocus}

0 commit comments

Comments
 (0)