Skip to content

Commit

Permalink
Add support for grid/card layout of items in FileSelect and `FinalF…
Browse files Browse the repository at this point in the history
…ormFileSelect` (#2344)
  • Loading branch information
jamesricky authored Jul 29, 2024
1 parent 7f88a96 commit 2c57136
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 11 deletions.
47 changes: 40 additions & 7 deletions packages/admin/admin/src/form/file/FileSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { getFilesInfoText } from "./getFilesInfoText";

export type FileSelectClassKey =
| "root"
| "listLayout"
| "gridLayout"
| "maxFilesReachedInfo"
| "dropzone"
| "fileList"
Expand All @@ -23,6 +25,8 @@ export type FileSelectClassKey =
| "errorMessage"
| "filesInfoText";

type Layout = "list" | "grid";

type ThemeProps = ThemedComponentBaseProps<{
root: "div";
maxFilesReachedInfo: typeof Alert;
Expand All @@ -34,6 +38,10 @@ type ThemeProps = ThemedComponentBaseProps<{
filesInfoText: typeof FormHelperText;
}>;

type OwnerState = {
layout: Layout;
};

export type FileSelectProps<AdditionalValidFileValues = Record<string, unknown>> = {
files: FileSelectItem<AdditionalValidFileValues>[];
onDrop?: DropzoneOptions["onDrop"];
Expand All @@ -47,6 +55,7 @@ export type FileSelectProps<AdditionalValidFileValues = Record<string, unknown>>
maxFiles?: number;
multiple?: boolean;
error?: React.ReactNode;
layout?: Layout;
iconMapping?: {
error?: React.ReactNode;
};
Expand All @@ -68,6 +77,7 @@ export const FileSelect = <AdditionalValidFileValues = Record<string, unknown>,>
getDownloadUrl,
files,
error,
layout = "list",
...restProps
} = useThemeProps({
props: inProps,
Expand All @@ -86,8 +96,12 @@ export const FileSelect = <AdditionalValidFileValues = Record<string, unknown>,>
const filesInfoText = getFilesInfoText(maxFiles, maxFileSize);
const showFileList = files.length > 0 || readOnly;

const ownerState: OwnerState = {
layout,
};

return (
<Root {...slotProps?.root} {...restProps}>
<Root ownerState={ownerState} {...slotProps?.root} {...restProps}>
{!readOnly && (
<>
{maxAmountOfFilesSelected ? (
Expand Down Expand Up @@ -116,7 +130,7 @@ export const FileSelect = <AdditionalValidFileValues = Record<string, unknown>,>
</>
)}
{showFileList && (
<FileList {...slotProps?.fileList}>
<FileList ownerState={ownerState} {...slotProps?.fileList}>
{files.length > 0 ? (
<>
{files.map((file, index) => {
Expand All @@ -135,6 +149,7 @@ export const FileSelect = <AdditionalValidFileValues = Record<string, unknown>,>
}
downloadUrl={isValidFile && getDownloadUrl ? getDownloadUrl(file) : undefined}
onClickDelete={readOnly || !onRemove ? undefined : () => onRemove(file)}
filePreview={layout === "grid"}
{...slotProps?.fileListItem}
/>
);
Expand Down Expand Up @@ -166,9 +181,10 @@ export const FileSelect = <AdditionalValidFileValues = Record<string, unknown>,>
);
};

const Root = createComponentSlot("div")<FileSelectClassKey>({
const Root = createComponentSlot("div")<FileSelectClassKey, OwnerState>({
componentName: "FileSelect",
slotName: "root",
classesResolver: ({ layout }) => [layout === "list" && "listLayout", layout === "grid" && "gridLayout"],
})(css`
display: flex;
flex-direction: column;
Expand All @@ -181,12 +197,29 @@ const Dropzone = createComponentSlot(FileDropzone)<FileSelectClassKey>({
slotName: "dropzone",
})();

const FileList = createComponentSlot("div")<FileSelectClassKey>({
const FileList = createComponentSlot("div")<FileSelectClassKey, OwnerState>({
componentName: "FileSelect",
slotName: "fileList",
})(css`
width: 100%;
`);
})(
({ theme, ownerState }) => css`
width: 100%;
${ownerState.layout === "grid" &&
css`
display: grid;
gap: ${theme.spacing(2)};
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
${theme.breakpoints.up("md")} {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
.CometAdminFormFieldContainer-horizontal & {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
`}
`,
);

const FileListItem = createComponentSlot(FileSelectListItem)<FileSelectClassKey>({
componentName: "FileSelect",
Expand Down
60 changes: 57 additions & 3 deletions storybook/src/admin/form/FinalFormFileSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Field, FinalFormFileSelect } from "@comet/admin";
import { Card, CardContent, Grid } from "@mui/material";
import { Box, Card, CardContent, Grid, Typography } from "@mui/material";
import { storiesOf } from "@storybook/react";
import * as React from "react";
import { Form } from "react-final-form";
Expand All @@ -13,7 +13,7 @@ type FormValues = {

function Story() {
return (
<div style={{ width: 800 }}>
<Box maxWidth={1600}>
<Form<FormValues>
onSubmit={(values) => {
//
Expand Down Expand Up @@ -63,12 +63,66 @@ function Story() {
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card variant="outlined">
<CardContent>
<Typography variant="h4" sx={{ mb: 4 }}>
Vertical form layout
</Typography>
<Field
name="vertical"
label="File Select"
component={FinalFormFileSelect}
multiple
fullWidth
helperText="Selected files will also be shown in the read-only field below."
/>
<Field
name="vertical"
label="File Select - ReadOnly Grid"
component={FinalFormFileSelect}
layout="grid"
readOnly
multiple
fullWidth
/>
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card variant="outlined">
<CardContent>
<Typography variant="h4" sx={{ mb: 4 }}>
Horizontal form layout
</Typography>
<Field
name="horizontal"
label="File Select"
component={FinalFormFileSelect}
multiple
fullWidth
variant="horizontal"
helperText="Selected files will also be shown in the read-only field below."
/>
<Field
variant="horizontal"
name="horizontal"
label="File Select - ReadOnly Grid"
component={FinalFormFileSelect}
layout="grid"
readOnly
multiple
fullWidth
/>
</CardContent>
</Card>
</Grid>
</Grid>
<pre>{JSON.stringify(values, null, 2)}</pre>
</form>
)}
/>
</div>
</Box>
);
}

Expand Down
5 changes: 4 additions & 1 deletion storybook/src/admin/form/file/FileSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { boolean, select } from "@storybook/addon-knobs";
import { storiesOf } from "@storybook/react";
import React from "react";

const getLayout = () => select("Layout", { List: "list", Grid: "grid" }, "list");

storiesOf("@comet/admin/form/File", module)
.addDecorator((story) => (
<Card sx={{ maxWidth: 440 }}>
<Card sx={{ maxWidth: getLayout() === "grid" ? 960 : 440 }}>
<CardContent>
<Stack spacing={4}>{story()}</Stack>
</CardContent>
Expand Down Expand Up @@ -79,6 +81,7 @@ storiesOf("@comet/admin/form/File", module)
onDownload={() => {
console.log("Downloading file");
}}
layout={getLayout()}
files={filesMapping[filesSelection]}
disabled={disabled}
readOnly={readOnly}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Used to combine `FileDropzone` and `FileSelectListItem` to handle the user's sel
- Use props from [react-dropzone](https://www.npmjs.com/package/react-dropzone), such as `onDrop`, to handle what happens when selecting files.
- Use the `files` prop to pass in the list of files that should be shown.
- Use the `getDownloadUrl`, `onDownload` and `onRemove` props to handle what happens when clicking the download and remove buttons.
- Set the `layout` prop to `grid` to display the files as a grid of cards with a file preview.
- Limit the amount of files and the size of the individual files with the `maxFiles` and `maxFileSize` props.

<Canvas>
Expand All @@ -23,3 +24,9 @@ Used to combine `FileDropzone` and `FileSelectListItem` to handle the user's sel
<Canvas>
<Story id="stories-components-fileselect--readonly-fileselect" />
</Canvas>

### ReadOnly FileSelect (Grid with preview)

<Canvas>
<Story id="stories-components-fileselect--grid-with-preview" />
</Canvas>
39 changes: 39 additions & 0 deletions storybook/src/docs/components/FileSelect/FileSelect.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,43 @@ storiesOf("stories/components/FileSelect", module)
readOnly
/>
);
})
.add("Grid with preview", () => {
const value: FileSelectItem[] = [
{
name: "Filename.xyz",
size: 4.3 * 1024 * 1024, // 4.3 MB
},
{
name: "Another file.png",
size: 568 * 1024, // 568 KB
},
{
name: "And another file.png",
size: 5.6 * 1024 * 1024, // 5.6 MB
},
{
name: "Yet another file.pdf",
size: 8.2 * 1024 * 1024, // 8.2 MB
},
{
name: "And again another file.jpg",
size: 3.4 * 1024 * 1024, // 3.4 MB
},
{
name: "One last file.png",
size: 1.2 * 1024 * 1024, // 1.2 MB
},
];

return (
<FileSelect
onDownload={(file) => {
// Handle download
}}
files={value}
readOnly
layout="grid"
/>
);
});

0 comments on commit 2c57136

Please sign in to comment.