Skip to content

Commit

Permalink
feat(FileOrganizer): thumbnailSize prop, fixed thumbnail auto size check
Browse files Browse the repository at this point in the history
  • Loading branch information
liamross committed Nov 19, 2020
1 parent e45689c commit 9c3d65b
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 35 deletions.
74 changes: 53 additions & 21 deletions src/components/FileOrganizer/FileOrganizer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { boolean, number } from '@storybook/addon-knobs';
import React, { FC, useCallback, useEffect, useState, useRef } from 'react';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import { FixedSizeGrid } from 'react-window';
import { useManagedFiles } from '../../hooks';
import { action } from '../../storybook-helpers/action/action';
import { createFile, FakeFile } from '../../storybook-helpers/data/files';
import { forwardAction } from '../../storybook-helpers/knobs/forwardAction';
import { integer } from '../../storybook-helpers/knobs/integer';
import { Button } from '../Button';
import { FileOrganizer, FileOrganizerProps } from '../FileOrganizer';
import { Icon } from '../Icon';
import { Thumbnail } from '../Thumbnail';
import { ThumbnailDragLayer } from '../ThumbnailDragLayer';
import readme from './README.md';
import { FixedSizeGrid } from 'react-window';
import { Button } from '../Button';

export default {
title: 'Components/FileOrganizer',
Expand All @@ -24,9 +24,16 @@ interface TemplateProps {
numFiles?: number;
editable?: boolean;
scrollToTop?: boolean;
customSizedThumbnail?: boolean;
}

const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable, scrollToTop }) => {
const Template: FC<TemplateProps> = ({
onRenderDragLayer,
numFiles = 2,
editable,
scrollToTop,
customSizedThumbnail,
}) => {
// This is the index organizing function.
const [files, setFiles] = useState<FakeFile[]>([]);
const handleOnMove = useCallback<NonNullable<FileOrganizerProps<FakeFile>['onMove']>>((fromIndex, toIndex) => {
Expand All @@ -45,6 +52,10 @@ const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable
gridRef.current?.scrollTo({ scrollTop: 0, scrollLeft: 0 });
};

const [largerSize, setLargerSize] = useState(false);
const changeSize = () => setLargerSize((prev) => !prev);
const size = largerSize ? { width: 200, height: 250 } : { width: 150, height: 200 };

// This is just a helper for adding or removing files.
useEffect(() => {
setFiles((prev) => {
Expand Down Expand Up @@ -75,26 +86,43 @@ const Template: FC<TemplateProps> = ({ onRenderDragLayer, numFiles = 2, editable
onDragChange={action('onDragChange')}
onDeselectAll={action('onDeselectAll')}
onSelectAll={action('onSelectAll')}
thumbnailSize={customSizedThumbnail ? size : undefined}
onRenderDragLayer={onRenderDragLayer ? () => <ThumbnailDragLayer /> : undefined}
onRenderThumbnail={({ onRenderThumbnailProps }) => (
<Thumbnail
{...onRenderThumbnailProps}
onRename={editable ? () => {} : undefined}
buttonProps={
editable
? [
{
children: <Icon icon="Close" />,
onClick: () => {},
key: 0,
},
]
: undefined
}
/>
)}
onRenderThumbnail={({ onRenderThumbnailProps, index }) =>
customSizedThumbnail ? (
<div
style={{
...size,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: 'lightblue',
border: '1px solid orange',
}}
>
Thumbnail {index + 1}
</div>
) : (
<Thumbnail
{...onRenderThumbnailProps}
onRename={editable ? () => {} : undefined}
buttonProps={
editable
? [
{
children: <Icon icon="Close" />,
onClick: () => {},
key: 0,
},
]
: undefined
}
/>
)
}
/>
{scrollToTop && <Button onClick={onScrollToTop}>Scroll to top</Button>}
{customSizedThumbnail && <Button onClick={changeSize}>Change thumbnail size</Button>}
</>
);
};
Expand All @@ -111,6 +139,10 @@ export const WithCustomDragLayer = () => {
const numFiles = number('number of files', 2, { min: 0, max: 16, step: 1, range: true });
return <Template numFiles={numFiles} onRenderDragLayer />;
};
export const DifferentThumbnailSize = () => {
const numFiles = number('number of files', 2, { min: 0, max: 16, step: 1, range: true });
return <Template numFiles={numFiles} customSizedThumbnail />;
};
export const WithGridRef = () => {
return <Template numFiles={100} scrollToTop />;
};
Expand Down
43 changes: 33 additions & 10 deletions src/components/FileOrganizer/FileOrganizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,31 @@ import React, {
KeyboardEventHandler,
MouseEventHandler,
ReactNode,
Ref,
RefObject,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
Ref,
useImperativeHandle,
} from 'react';
import { FixedSizeGrid } from 'react-window';
import {
focusableElementDomString,
getRowAndColumnIndex,
getSibling,
isScrolledIntoView,
ObjectWithId,
THUMBNAIL_WIDTH,
focusableElementDomString,
} from '../../utils';
import { DndMultiProvider } from '../DndMultiProvider';
import { Draggable } from '../Draggable';
import { DragLayer, DragLayerProps } from '../DragLayer';
import { MemoAutoSizer } from './MemoAutoSizer';

type Size = { width: number; height: number };
const defaultSize: Size = { width: THUMBNAIL_WIDTH, height: THUMBNAIL_WIDTH };

export interface FileOrganizerProps<F> extends HTMLAttributes<HTMLDivElement> {
/**
* A list of files to render out within the page organizer.
Expand Down Expand Up @@ -70,6 +73,12 @@ export interface FileOrganizerProps<F> extends HTMLAttributes<HTMLDivElement> {
* If provided, the ref is attached to the `react-window` FixedSizeGrid.
*/
gridRef?: Ref<FixedSizeGrid>;
/**
* If you know exactly what size your thumbnail is going to be, you can input
* the value here. Use this if the thumbnail is going to change sizes, since
* `FileOrganizer` will only detect changes when files change.
*/
thumbnailSize?: { width: number; height: number };
/**
* On render function for generating the thumbnails for the page organizer.
* If you do not want to build your own, try using the `Thumbnail` component.
Expand Down Expand Up @@ -140,6 +149,7 @@ export function FileOrganizer<F extends ObjectWithId>({
draggingIds,
padding,
gridRef: _gridRef,
thumbnailSize,
className,
onClick,
onKeyDown,
Expand All @@ -156,19 +166,32 @@ export function FileOrganizer<F extends ObjectWithId>({

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

// Detect size of first item and use as size throughout.
const [size, setSize] = useState({ width: THUMBNAIL_WIDTH, height: THUMBNAIL_WIDTH });
// Get the width of the first item, or default if no first item found.
const hasFiles = files.length > 0;
const getSize = useCallback<() => Size>(() => {
if (!hasFiles) return defaultSize;
if (!fileOrganizerRef.current) return defaultSize;
const draggableWrapper = fileOrganizerRef.current.querySelector('div[draggable="true"]');
const draggableElement = draggableWrapper?.firstChild as Element | undefined;
const firstItem = draggableElement?.firstChild as Element | undefined;
if (!firstItem) return defaultSize;
return firstItem.getBoundingClientRect();
}, [hasFiles]);

// Detect size of first item and use as size throughout.
const [size, setSize] = useState<Size>(() => thumbnailSize || getSize());

// Update size when getWidth ref changes (when hasFiles changes).
useEffect(() => {
if (!fileOrganizerRef.current) return;
const firstItem = fileOrganizerRef.current.querySelector('div[draggable="true"]');
if (!firstItem) return;
const { width, height } = firstItem.getBoundingClientRect();
if (thumbnailSize) setSize(thumbnailSize);
if (files.length === 0) setSize(defaultSize);
setSize((prev) => {
const { width, height } = getSize();
if (prev.width === width && prev.height === height) return prev;
return { width, height };
});
}, [hasFiles]);
// Watches all files to continuously check width and height.
}, [files, getSize, thumbnailSize]);

const handleOnDragChange = useCallback(
(id?: string) => {
Expand Down
5 changes: 1 addition & 4 deletions src/components/Thumbnail/Thumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classnames from 'classnames';
import React, { MouseEvent, ReactNode, ReactText, useRef } from 'react';
import React, { MouseEvent, ReactNode, ReactText } from 'react';
import { FileLike } from '../../data';
import { useAccessibleFocus, useFileSubscribe, useFocus } from '../../hooks';
import { ClickableDiv, ClickableDivProps } from '../ClickableDiv';
Expand Down Expand Up @@ -85,8 +85,6 @@ export function Thumbnail<F extends FileLike>({
}: ThumbnailProps<F>) {
const isUserTabbing = useAccessibleFocus(thumbnailFocusObservable);

const thumbnailRef = useRef<HTMLDivElement>(null);

const { focused, handleOnFocus, handleOnBlur } = useFocus(onFocus, onBlur);

const [thumbnail, thumbnailErr] = useFileSubscribe(file, (f) => f.thumbnail, 'onthumbnailchange');
Expand Down Expand Up @@ -122,7 +120,6 @@ export function Thumbnail<F extends FileLike>({
<ClickableDiv
{...divProps}
className={thumbnailClass}
ref={thumbnailRef}
noFocusStyle
disabled={disabled}
onFocus={handleOnFocus}
Expand Down

0 comments on commit 9c3d65b

Please sign in to comment.