Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: getInputProps #746

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d04eec6
first lets move some stuff
juliusmarminge Apr 5, 2024
3481530
move input props to hook
juliusmarminge Apr 5, 2024
ca176fe
ncieeee
juliusmarminge Apr 6, 2024
db16244
nicee
juliusmarminge Apr 6, 2024
655e27b
[wip] custom component example
juliusmarminge Apr 7, 2024
00d2d84
Merge branch 'main' into input-props
juliusmarminge Apr 13, 2024
2435570
merge
juliusmarminge Apr 13, 2024
abd83c5
init demo
juliusmarminge Apr 13, 2024
ec486df
Merge branch 'main' into input-props
juliusmarminge Apr 13, 2024
c1d8139
gettin somewher
juliusmarminge Apr 13, 2024
7f307cf
nit
juliusmarminge Apr 13, 2024
4dd50a1
refactor useDropzone a bit
juliusmarminge Apr 13, 2024
1649f8c
nice
juliusmarminge Apr 13, 2024
467cc66
null
juliusmarminge Apr 13, 2024
cc8f222
fix solid
juliusmarminge Apr 13, 2024
f4584df
fix svelte
juliusmarminge Apr 13, 2024
160ff6c
minify type output
juliusmarminge Apr 13, 2024
e506f61
minify type output for solid and svelte too
juliusmarminge Apr 13, 2024
c111b41
undefined is more natural here
juliusmarminge Apr 13, 2024
fce1a94
fmt
juliusmarminge Apr 13, 2024
564436d
fix svelte linting
juliusmarminge Apr 13, 2024
366a776
re-export useful helper from shared
juliusmarminge Apr 14, 2024
7d115b8
eh
juliusmarminge Apr 15, 2024
c0c15c4
rhf tests
juliusmarminge Apr 15, 2024
fa3d69b
break out
juliusmarminge Apr 15, 2024
6839f2f
check dirty
juliusmarminge Apr 15, 2024
2176789
skippolling
juliusmarminge Apr 15, 2024
84eab1f
auto mode
juliusmarminge Apr 15, 2024
9567b3a
nice
juliusmarminge Apr 15, 2024
3df628c
simple nav
juliusmarminge Apr 15, 2024
66e0cbf
nice
juliusmarminge Apr 15, 2024
f6c6d3b
rm unused
juliusmarminge Apr 15, 2024
8f9e823
rm log
juliusmarminge Apr 15, 2024
bff3a88
simplify
juliusmarminge Apr 15, 2024
5573faa
nice
juliusmarminge Apr 15, 2024
338c61f
with-novel example
juliusmarminge Apr 16, 2024
f9d13c0
readme
juliusmarminge Apr 16, 2024
b559318
nice
juliusmarminge Apr 16, 2024
fd53c56
rename
juliusmarminge Apr 16, 2024
0f08bc0
fix
juliusmarminge Apr 16, 2024
2f03be4
Merge branch 'main' into input-props
juliusmarminge May 12, 2024
32800eb
fix dupe
juliusmarminge May 12, 2024
7ab2c32
Merge branch 'main' into input-props
juliusmarminge May 12, 2024
6c906dc
fix some types
juliusmarminge May 12, 2024
a9431a7
fix more
juliusmarminge May 12, 2024
88576c8
rm ts-expect-error's
juliusmarminge May 12, 2024
7d18883
fix more types
juliusmarminge May 12, 2024
2aacc64
Merge branch 'main' into input-props
markflorkowski May 24, 2024
bc43e09
Merge branch 'main' into input-props
markflorkowski May 25, 2024
5bf7ea3
Merge branch 'main' into input-props
markflorkowski May 29, 2024
53312d7
chore: migrate vue off of `permittedFileInfo` (#837)
markflorkowski Jun 3, 2024
1caf977
chore: Add redirects to docs nextconfig (#843)
markflorkowski May 31, 2024
e0e4740
Merge branch 'main' into input-props
juliusmarminge Jun 3, 2024
a214d80
rm shared dep from pg
juliusmarminge Jun 3, 2024
0c94c52
Merge branch 'main' into input-props
markflorkowski Jun 24, 2024
35d4782
pnpm i
markflorkowski Jun 24, 2024
7182285
remove duplicate imports
markflorkowski Jun 24, 2024
8c592a6
format
markflorkowski Jun 24, 2024
cdb4c0c
Merge branch 'main' into input-props
juliusmarminge Jun 25, 2024
6d0abe7
Merge branch 'main' into input-props
juliusmarminge Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/with-tailwindcss/src/app/my-upload-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useUploadThing } from "~/utils/uploadthing";

export const MyUploadButton = (props: {}) => {
const { getInputProps, files, isUploading, progresses } =
useUploadThing("videoAndImage");

return (
<label className="cursor-pointer rounded bg-blue-500 px-8 py-2 text-white hover:bg-blue-600">
{files.length ? "Upload files" : "Select files to upload"}
<input {...getInputProps({ mode: "manual" })} className="sr-only" />
</label>
);
};
6 changes: 6 additions & 0 deletions examples/with-tailwindcss/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
UploadDropzone,
useUploadThing,
} from "~/utils/uploadthing";
import { MyUploadButton } from "./my-upload-button";

export default function Home() {
const { startUpload } = useUploadThing("videoAndImage", {
Expand All @@ -15,6 +16,7 @@ export default function Home() {

return (
<main className="mx-auto flex min-h-screen max-w-sm flex-col items-center justify-center gap-4 py-24">
<MyUploadButton />
<UploadButton
endpoint="videoAndImage"
onClientUploadComplete={(res) => {
Expand All @@ -24,6 +26,10 @@ export default function Home() {
onUploadBegin={() => {
console.log("upload begin");
}}
config={{
appendOnPaste: true,
mode: "manual",
}}
/>
<UploadDropzone
className="ut-label:text-lg ut-allowed-content:ut-uploading:text-red-300 ut-allowed-content:text-white w-full bg-slate-800"
Expand Down
2 changes: 2 additions & 0 deletions packages/dropzone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@uploadthing/shared": "workspace:*",
"effect": "3.4.2",
"file-selector": "^0.6.0"
},
"devDependencies": {
Expand Down
44 changes: 40 additions & 4 deletions packages/dropzone/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
* See original source here: https://github.com/react-dropzone/react-dropzone
* The original package is licensed under the MIT license.
*/
import * as Micro from "effect/Micro";

import type { ExpandedRouteConfig, FileWithState } from "@uploadthing/shared";
import {
fileSizeToBytes,
generateClientDropzoneAccept,
generatePermittedFileTypes,
} from "@uploadthing/shared";

import type { AcceptProp, DropzoneState } from "./types";

Expand Down Expand Up @@ -48,7 +56,8 @@ export const isPropagationStopped = (

// Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
// that MIME type will always be accepted
export function isFileAccepted(file: File, accept: string | string[]) {
export function isFileAccepted(file: File, accept: string | undefined) {
if (!accept) return true;
return file.type === "application/x-moz-file" || accepts(file, accept);
}

Expand Down Expand Up @@ -89,7 +98,7 @@ export function allFilesAccepted({
maxFiles,
}: {
files: File[];
accept: string | string[];
accept: string | undefined;
minSize: number;
maxSize: number;
multiple: boolean;
Expand Down Expand Up @@ -141,7 +150,7 @@ function isExt(v: string) {
/**
* Convert the `{accept}` dropzone prop to an array of MIME types/extensions.
*/
export function acceptPropAsAcceptAttr(accept?: AcceptProp) {
function acceptPropAsAcceptAttr(accept?: AcceptProp) {
if (isDefined(accept)) {
return (
Object.entries(accept)
Expand All @@ -159,6 +168,33 @@ export function noop() {
// noop
}

export const routeConfigToDropzoneProps = (
routeConfig: ExpandedRouteConfig | undefined,
) => {
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);

const { maxFiles, maxSize } = Object.values(routeConfig ?? {}).reduce(
(acc, curr) => {
// Don't think it makes sense to have a minFileCount since they can select many times
// acc.minFiles = Math.min(acc.minFiles, curr.minFileCount);
acc.maxFiles = Math.max(acc.maxFiles, curr.maxFileCount);
acc.maxSize = Math.max(
acc.maxSize,
Micro.runSync(fileSizeToBytes(curr.maxFileSize)),
);
return acc;
},
{ maxFiles: 0, maxSize: 0 },
);

return {
multiple,
accept: acceptPropAsAcceptAttr(generateClientDropzoneAccept(fileTypes)),
maxFiles,
maxSize: maxSize,
};
};

/**
* ================================================
* Reducer
Expand Down Expand Up @@ -191,7 +227,7 @@ export const initialState = {
isDragActive: false,
isDragAccept: false,
isDragReject: false,
acceptedFiles: [] as File[],
acceptedFiles: [] as FileWithState[],
};

export function reducer(
Expand Down
45 changes: 30 additions & 15 deletions packages/dropzone/src/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import type {
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
import { fromEvent } from "file-selector";

import type { FileWithState } from "@uploadthing/shared";

import {
acceptPropAsAcceptAttr,
allFilesAccepted,
initialState,
isEnterOrSpace,
Expand All @@ -27,6 +28,7 @@ import {
isValidSize,
noop,
reducer,
routeConfigToDropzoneProps,
} from "./core";
import type { DropzoneOptions } from "./types";

Expand Down Expand Up @@ -60,15 +62,15 @@ export type DropEvent =
* ```
*/
export function useDropzone({
accept,
routeConfig,
disabled = false,
maxSize = Number.POSITIVE_INFINITY,
minSize = 0,
multiple = true,
maxFiles = 0,
onDrop,
}: DropzoneOptions) {
const acceptAttr = useMemo(() => acceptPropAsAcceptAttr(accept), [accept]);
const { accept, multiple, maxFiles, maxSize } = useMemo(
() => routeConfigToDropzoneProps(routeConfig),
[routeConfig],
);

const rootRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -136,7 +138,7 @@ export function useDropzone({
fileCount > 0 &&
allFilesAccepted({
files: files as File[],
accept: acceptAttr!,
accept,
minSize,
maxSize,
multiple,
Expand All @@ -156,7 +158,7 @@ export function useDropzone({
.catch(noop);
}
},
[acceptAttr, maxFiles, maxSize, minSize, multiple],
[accept, maxFiles, maxSize, minSize, multiple],
);

const onDragOver = useCallback((event: DragEvent<HTMLElement>) => {
Expand Down Expand Up @@ -203,14 +205,18 @@ export function useDropzone({

const setFiles = useCallback(
(files: File[]) => {
const acceptedFiles: File[] = [];
const acceptedFiles: FileWithState[] = [];

files.forEach((file) => {
const accepted = isFileAccepted(file, acceptAttr!);
const accepted = isFileAccepted(file, accept);
const sizeMatch = isValidSize(file, minSize, maxSize);

if (accepted && sizeMatch) {
acceptedFiles.push(file);
const fileWithState: FileWithState = Object.assign(file, {
status: "pending" as const,
key: null,
});
acceptedFiles.push(fileWithState);
}
});

Expand All @@ -227,7 +233,7 @@ export function useDropzone({

onDrop(acceptedFiles);
},
[acceptAttr, maxFiles, maxSize, minSize, multiple, onDrop],
[accept, maxFiles, maxSize, minSize, multiple, onDrop],
);

const onDropCb = useCallback(
Expand All @@ -241,7 +247,16 @@ export function useDropzone({
Promise.resolve(fromEvent(event))
.then((files) => {
if (event.isPropagationStopped()) return;
setFiles(files as File[]);

console.log("files in onDrop", files);

const filesWithState = (files as File[]).map((file) =>
Object.assign(file, {
status: "pending" as const,
key: null,
}),
);
setFiles(filesWithState);
})
.catch(noop);
}
Expand Down Expand Up @@ -323,7 +338,7 @@ export function useDropzone({
ref: inputRef,
type: "file",
style: { display: "none" },
accept: acceptAttr,
accept: accept,
multiple,
tabIndex: -1,
...(!disabled
Expand All @@ -334,7 +349,7 @@ export function useDropzone({
}
: {}),
}),
[acceptAttr, multiple, onDropCb, onInputElementClick, disabled],
[accept, multiple, onDropCb, onInputElementClick, disabled],
);

return {
Expand Down
41 changes: 24 additions & 17 deletions packages/dropzone/src/solid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
} from "solid-js";
import { createStore } from "solid-js/store";

import type { FileWithState } from "@uploadthing/shared";

import {
acceptPropAsAcceptAttr,
allFilesAccepted,
initialState,
isEnterOrSpace,
Expand All @@ -27,6 +28,7 @@ import {
isValidQuantity,
isValidSize,
noop,
routeConfigToDropzoneProps,
} from "./core";
import type { DropzoneOptions } from "./types";

Expand All @@ -38,15 +40,14 @@ export function createDropzone(_props: DropzoneOptions) {
const props = mergeProps(
{
disabled: false,
maxSize: Number.POSITIVE_INFINITY,
minSize: 0,
multiple: true,
maxFiles: 0,
},
_props,
);

const acceptAttr = createMemo(() => acceptPropAsAcceptAttr(props.accept));
const routeProps = createMemo(() =>
routeConfigToDropzoneProps(props.routeConfig),
);

const [rootRef, setRootRef] = createSignal<HTMLElement | null>();
const [inputRef, setInputRef] = createSignal<HTMLInputElement | null>();
Expand Down Expand Up @@ -114,11 +115,8 @@ export function createDropzone(_props: DropzoneOptions) {
fileCount > 0 &&
allFilesAccepted({
files: files as File[],
accept: acceptAttr()!,
minSize: props.minSize,
maxSize: props.maxSize,
multiple: props.multiple,
maxFiles: props.maxFiles,
...routeProps(),
});
const isDragReject = fileCount > 0 && !isDragAccept;

Expand Down Expand Up @@ -174,25 +172,34 @@ export function createDropzone(_props: DropzoneOptions) {
};

const setFiles = (files: File[]) => {
const acceptedFiles: File[] = [];
const acceptedFiles: FileWithState[] = [];

files.forEach((file) => {
const accepted = isFileAccepted(file, acceptAttr()!);
const sizeMatch = isValidSize(file, props.minSize, props.maxSize);
const accepted = isFileAccepted(file, routeProps().accept);
const sizeMatch = isValidSize(file, props.minSize, routeProps().maxSize);

if (accepted && sizeMatch) {
acceptedFiles.push(file);
const fileWithState: FileWithState = Object.assign(file, {
status: "pending" as const,
key: null,
});
acceptedFiles.push(fileWithState);
}
});

if (!isValidQuantity(acceptedFiles, props.multiple, props.maxFiles)) {
if (
!isValidQuantity(
acceptedFiles,
routeProps().multiple,
routeProps().maxFiles,
)
) {
acceptedFiles.splice(0);
}

setState({
acceptedFiles,
});

props.onDrop?.(acceptedFiles);
};

Expand Down Expand Up @@ -271,8 +278,8 @@ export function createDropzone(_props: DropzoneOptions) {
ref: setInputRef,
type: "file",
style: { display: "none" },
accept: acceptAttr(),
multiple: props.multiple,
accept: routeProps().accept,
multiple: routeProps().multiple,
tabIndex: -1,
...(!props.disabled
? {
Expand Down
Loading
Loading