Date: Thu, 26 Sep 2024 21:52:39 +0100
Subject: [PATCH 16/39] fix: disabled accessibility for primitive button
components
---
packages/react/src/components/primitive/button.tsx | 1 +
packages/react/src/components/primitive/clear-button.tsx | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/react/src/components/primitive/button.tsx b/packages/react/src/components/primitive/button.tsx
index 5a80f65246..e39b2ce2c5 100644
--- a/packages/react/src/components/primitive/button.tsx
+++ b/packages/react/src/components/primitive/button.tsx
@@ -35,6 +35,7 @@ function ButtonFn
(
{
onClick?.(e);
if (state === "uploading") {
diff --git a/packages/react/src/components/primitive/clear-button.tsx b/packages/react/src/components/primitive/clear-button.tsx
index 0bbeb78c3c..215119dead 100644
--- a/packages/react/src/components/primitive/clear-button.tsx
+++ b/packages/react/src/components/primitive/clear-button.tsx
@@ -18,13 +18,14 @@ function ClearButtonFn<
{ children, onClick, as, ...props }: PrimitiveClearButtonProps,
ref: Ref,
) {
- const { setFiles, state } = usePrimitiveValues("ClearButton");
+ const { setFiles, state, disabled } = usePrimitiveValues("ClearButton");
const Comp = as ?? DEFAULT_CLEAR_BUTTON_TAG;
return (
{
onClick?.(e);
setFiles([]);
From 29469b3a144a69e82957a8e79c25f945e07a0681 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Thu, 26 Sep 2024 21:54:39 +0100
Subject: [PATCH 17/39] fix: useUncontrolledState potential issue with falsy
value
---
packages/react/src/utils/useControllableState.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react/src/utils/useControllableState.ts b/packages/react/src/utils/useControllableState.ts
index e18100a330..2894bbcda8 100644
--- a/packages/react/src/utils/useControllableState.ts
+++ b/packages/react/src/utils/useControllableState.ts
@@ -39,7 +39,7 @@ const useUncontrolledState = ({
const handleChange = useCallbackRef(onChange);
React.useEffect(() => {
- if (!value) return;
+ if (value === undefined) return;
if (prevValueRef.current !== value) {
handleChange(value);
prevValueRef.current = value;
From d1dd53ca10bc0ecff98a0a6c0cea7ddfa453efe7 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Thu, 26 Sep 2024 21:56:35 +0100
Subject: [PATCH 18/39] fix: dropzone and button components not using custom cn
---
packages/react/src/components/button.tsx | 2 +-
packages/react/src/components/dropzone.tsx | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx
index 941410b0ab..097c4ec7bf 100644
--- a/packages/react/src/components/button.tsx
+++ b/packages/react/src/components/button.tsx
@@ -78,7 +78,7 @@ export function UploadButton<
const { className, content, appearance, ...$props } =
props as unknown as UploadButtonProps;
- const cn = defaultClassListMerger ?? $props.config ?? {};
+ const cn = $props.config?.cn ?? defaultClassListMerger;
return (
{...($props as any)}>
diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx
index 2f0b871363..c7f951ea00 100644
--- a/packages/react/src/components/dropzone.tsx
+++ b/packages/react/src/components/dropzone.tsx
@@ -95,7 +95,7 @@ export function UploadDropzone<
props as unknown as UploadDropzoneProps &
UploadThingInternalProps;
- const cn = defaultClassListMerger ?? rootProps.config ?? {};
+ const cn = rootProps.config?.cn ?? defaultClassListMerger;
return (
{...(rootProps as any)}>
@@ -253,4 +253,3 @@ export function UploadDropzone<
);
}
-
From 1330c72eb9e38fce5437dec6aa92975f3132aca3 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Thu, 26 Sep 2024 21:57:10 +0100
Subject: [PATCH 19/39] refactor: button component to use `rootProps` variable
name for consistency
---
packages/react/src/components/button.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx
index 097c4ec7bf..79b82e04bb 100644
--- a/packages/react/src/components/button.tsx
+++ b/packages/react/src/components/button.tsx
@@ -75,13 +75,13 @@ export function UploadButton<
) {
// Cast back to UploadthingComponentProps to get the correct type
// since the ErrorMessage messes it up otherwise
- const { className, content, appearance, ...$props } =
+ const { className, content, appearance, ...rootProps } =
props as unknown as UploadButtonProps;
- const cn = $props.config?.cn ?? defaultClassListMerger;
+ const cn = rootProps.config?.cn ?? defaultClassListMerger;
return (
- {...($props as any)}>
+ {...(rootProps as any)}>
{({ state, uploadProgress, fileTypes, files, options }) => {
const styleFieldArg = {
ready: state !== "readying",
From 753c95d9c833f9e05e6bd8549647c5efdc380c47 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Thu, 26 Sep 2024 21:58:31 +0100
Subject: [PATCH 20/39] refactor: use more precise function type for `RefProp`
---
packages/react/src/components/primitive/root.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index bad152f7d7..94c8021534 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -124,7 +124,7 @@ export type HasDisplayName = {
displayName: string;
};
-export type RefProp = T extends (
+export type RefProp any> = T extends (
props: any,
ref: Ref,
) => any
From fc729786f043eda6924b967d26a04d7a70aff00c Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Thu, 26 Sep 2024 22:00:28 +0100
Subject: [PATCH 21/39] perf: optimise useEffect deps in the root primitive
component
---
packages/react/src/components/primitive/root.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index 94c8021534..05ebb1bf3c 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -223,17 +223,18 @@ export function Root<
},
);
+ const { onUploadAborted } = $props;
const uploadFiles = useCallback(
(files: File[]) => {
startUpload(files, fileRouteInput).catch((e) => {
if (e instanceof UploadAbortedError) {
- void $props.onUploadAborted?.();
+ void onUploadAborted?.();
} else {
throw e;
}
});
},
- [$props, startUpload, fileRouteInput],
+ [onUploadAborted, startUpload, fileRouteInput],
);
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);
From 3c1d7dc242f907e6eaef1245095e3dfd6c3b1cae Mon Sep 17 00:00:00 2001
From: Aria <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:17:08 +0100
Subject: [PATCH 22/39] remove redundant type guards in the root primitive
component
Co-authored-by: Julius Marminge
---
packages/react/src/components/primitive/root.tsx | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index 05ebb1bf3c..85e386cb91 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -167,14 +167,8 @@ export type RootPrimitiveComponentProps<
export function Root<
TRouter extends FileRouter,
TEndpoint extends keyof TRouter,
->(
- props: FileRouter extends TRouter
- ? ErrorMessage<"You forgot to pass the generic">
- : RootPrimitiveComponentProps,
-) {
- // Cast back to UploadthingComponentProps to get the correct type
- // since the ErrorMessage messes it up otherwise
- const $props = props as unknown as RootPrimitiveComponentProps<
+>(props: RootPrimitiveComponentProps) {
+ props;
TRouter,
TEndpoint
> &
From f9bb2a8ff13f768c68e1a097c37a7d40007c4b7c Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:23:21 +0100
Subject: [PATCH 23/39] fix: root primitive syntax
---
.../react/src/components/primitive/root.tsx | 51 +++++++++----------
1 file changed, 24 insertions(+), 27 deletions(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index 85e386cb91..2ea7286084 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -167,57 +167,54 @@ export type RootPrimitiveComponentProps<
export function Root<
TRouter extends FileRouter,
TEndpoint extends keyof TRouter,
->(props: RootPrimitiveComponentProps) {
- props;
- TRouter,
- TEndpoint
- > &
- UploadThingInternalProps;
+>(
+ props: RootPrimitiveComponentProps &
+ UploadThingInternalProps,
+) {
+ const fileRouteInput = "input" in props ? props.input : undefined;
- const fileRouteInput = "input" in $props ? $props.input : undefined;
-
- const { mode = "auto", appendOnPaste = false } = $props.config ?? {};
+ const { mode = "auto", appendOnPaste = false } = props.config ?? {};
const acRef = useRef(new AbortController());
const useUploadThing = INTERNAL_uploadthingHookGen({
- url: resolveMaybeUrlArg($props.url),
+ url: resolveMaybeUrlArg(props.url),
});
const focusElementRef = useRef(null);
const fileInputRef = useRef(null);
const [uploadProgress, setUploadProgress] = useState(
- $props.__internal_upload_progress ?? 0,
+ props.__internal_upload_progress ?? 0,
);
const [files, setFiles] = useControllableState({
- prop: $props.files,
- onChange: $props.onFilesChange,
+ prop: props.files,
+ onChange: props.onFilesChange,
defaultProp: [],
});
const { startUpload, isUploading, routeConfig } = useUploadThing(
- $props.endpoint,
+ props.endpoint,
{
signal: acRef.current.signal,
- headers: $props.headers,
+ headers: props.headers,
onClientUploadComplete: (res) => {
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
setFiles([]);
- void $props.onClientUploadComplete?.(res);
+ void props.onClientUploadComplete?.(res);
setUploadProgress(0);
},
onUploadProgress: (p) => {
setUploadProgress(p);
- $props.onUploadProgress?.(p);
+ props.onUploadProgress?.(p);
},
- onUploadError: $props.onUploadError,
- onUploadBegin: $props.onUploadBegin,
- onBeforeUploadBegin: $props.onBeforeUploadBegin,
+ onUploadError: props.onUploadError,
+ onUploadBegin: props.onUploadBegin,
+ onBeforeUploadBegin: props.onBeforeUploadBegin,
},
);
- const { onUploadAborted } = $props;
+ const { onUploadAborted } = props;
const uploadFiles = useCallback(
(files: File[]) => {
startUpload(files, fileRouteInput).catch((e) => {
@@ -234,13 +231,13 @@ export function Root<
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);
let disabled = fileTypes.length === 0;
- if ($props.disabled) disabled = true;
- if ($props.__internal_button_disabled) disabled = true;
+ if (props.disabled) disabled = true;
+ if (props.__internal_button_disabled) disabled = true;
const accept = generateMimeTypes(fileTypes).join(", ");
const state = (() => {
- if ($props.__internal_state) return $props.__internal_state;
+ if (props.__internal_state) return props.__internal_state;
if (disabled) return "disabled";
if (!disabled && !isUploading) return "ready";
return "uploading";
@@ -259,7 +256,7 @@ export function Root<
setFiles((prev) => {
filesToUpload = [...prev, ...pastedFiles];
- $props.onChange?.(filesToUpload);
+ props.onChange?.(filesToUpload);
return filesToUpload;
});
@@ -271,7 +268,7 @@ export function Root<
files,
setFiles: (files) => {
setFiles(files);
- $props.onChange?.(files);
+ props.onChange?.(files);
if (files.length <= 0) {
if (fileInputRef.current) fileInputRef.current.value = "";
@@ -303,7 +300,7 @@ export function Root<
return (
- {$props.children}
+ {props.children}
);
}
From 3b51eaa5222a5fd54990ab10185d649a789c3808 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:27:02 +0100
Subject: [PATCH 24/39] feat: disabled prop on dropzone primitive
---
.../src/components/primitive/dropzone.tsx | 21 ++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/packages/react/src/components/primitive/dropzone.tsx b/packages/react/src/components/primitive/dropzone.tsx
index 0f82d8a9e0..eea602aef5 100644
--- a/packages/react/src/components/primitive/dropzone.tsx
+++ b/packages/react/src/components/primitive/dropzone.tsx
@@ -40,20 +40,31 @@ const DEFAULT_DROPZONE_TAG = "div" as const;
export type PrimitiveDropzoneProps<
TTag extends ElementType = typeof DEFAULT_DROPZONE_TAG,
-> = PrimitiveComponentProps;
+> = PrimitiveComponentProps & { disabled?: boolean };
function DropzoneFn(
- { children, as, ...props }: PrimitiveDropzoneProps,
+ {
+ children,
+ as,
+ disabled: componentDisabled,
+ ...props
+ }: PrimitiveDropzoneProps,
ref: Ref,
) {
- const { setFiles, options, fileTypes, disabled, state, refs } =
- usePrimitiveValues("Dropzone");
+ const {
+ setFiles,
+ options,
+ fileTypes,
+ disabled: rootDisabled,
+ state,
+ refs,
+ } = usePrimitiveValues("Dropzone");
const { getRootProps, getInputProps, isDragActive, rootRef } = useDropzone({
onDrop: setFiles,
multiple: options.multiple,
accept: fileTypes ? generateClientDropzoneAccept(fileTypes) : undefined,
- disabled,
+ disabled: rootDisabled || componentDisabled,
});
const Comp = as ?? DEFAULT_DROPZONE_TAG;
From 46678ecf2a54bd1e4599d1ea773eb74549d51505 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:34:07 +0100
Subject: [PATCH 25/39] fix: allow undefined on disabled prop
---
packages/react/src/components/primitive/dropzone.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react/src/components/primitive/dropzone.tsx b/packages/react/src/components/primitive/dropzone.tsx
index eea602aef5..87fa28d0d5 100644
--- a/packages/react/src/components/primitive/dropzone.tsx
+++ b/packages/react/src/components/primitive/dropzone.tsx
@@ -40,7 +40,7 @@ const DEFAULT_DROPZONE_TAG = "div" as const;
export type PrimitiveDropzoneProps<
TTag extends ElementType = typeof DEFAULT_DROPZONE_TAG,
-> = PrimitiveComponentProps & { disabled?: boolean };
+> = PrimitiveComponentProps & { disabled?: boolean | undefined };
function DropzoneFn(
{
From a7b146a296db56a4cac2cde80d63f5d429f9d248 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:40:26 +0100
Subject: [PATCH 26/39] fix: internal component props
---
packages/react/src/components/button.tsx | 22 ++++++++++++++++---
packages/react/src/components/dropzone.tsx | 18 +++++++++------
.../react/src/components/primitive/root.tsx | 14 +++++++-----
3 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx
index 79b82e04bb..36f6ef8ef8 100644
--- a/packages/react/src/components/button.tsx
+++ b/packages/react/src/components/button.tsx
@@ -56,6 +56,13 @@ export type UploadButtonProps<
content?: ButtonContent;
};
+/** These are some internal stuff we use to test the component and for forcing a state in docs */
+type UploadThingInternalProps = {
+ __internal_state?: "readying" | "ready" | "uploading";
+ __internal_upload_progress?: number;
+ __internal_button_disabled?: boolean;
+};
+
/**
* @remarks It is not recommended using this directly as it requires manually binding generics. Instead, use `createUploadButton`.
* @example
@@ -75,13 +82,22 @@ export function UploadButton<
) {
// Cast back to UploadthingComponentProps to get the correct type
// since the ErrorMessage messes it up otherwise
- const { className, content, appearance, ...rootProps } =
- props as unknown as UploadButtonProps;
+ const {
+ className,
+ content,
+ appearance,
+ __internal_button_disabled,
+ ...rootProps
+ } = props as unknown as UploadButtonProps &
+ UploadThingInternalProps;
const cn = rootProps.config?.cn ?? defaultClassListMerger;
return (
- {...(rootProps as any)}>
+
+ {...(rootProps as any)}
+ disabled={__internal_button_disabled}
+ >
{({ state, uploadProgress, fileTypes, files, options }) => {
const styleFieldArg = {
ready: state !== "readying",
diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx
index c7f951ea00..3802357285 100644
--- a/packages/react/src/components/dropzone.tsx
+++ b/packages/react/src/components/dropzone.tsx
@@ -73,8 +73,6 @@ type UploadThingInternalProps = {
__internal_upload_progress?: number;
// Allow to set ready explicitly and independently of internal state
__internal_ready?: boolean;
- // Allow to show the button even if no files were added
- __internal_show_button?: boolean;
// Allow to disable the button
__internal_button_disabled?: boolean;
// Allow to disable the dropzone
@@ -91,15 +89,21 @@ export function UploadDropzone<
) {
// Cast back to UploadthingComponentProps to get the correct type
// since the ErrorMessage messes it up otherwise
- const { className, content, appearance, ...rootProps } =
- props as unknown as UploadDropzoneProps &
- UploadThingInternalProps;
+ const {
+ className,
+ content,
+ appearance,
+ __internal_dropzone_disabled,
+ __internal_button_disabled,
+ ...rootProps
+ } = props as unknown as UploadDropzoneProps &
+ UploadThingInternalProps;
const cn = rootProps.config?.cn ?? defaultClassListMerger;
return (
{...(rootProps as any)}>
-
+
{({
files,
fileTypes,
@@ -242,7 +246,7 @@ export function UploadDropzone<
)}
style={styleFieldToCssObject(appearance?.button, styleFieldArg)}
data-ut-element="button"
- disabled={rootProps.__internal_button_disabled ?? !files.length}
+ disabled={__internal_button_disabled ?? !files.length}
>
{getUploadButtonContents()}
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index 2ea7286084..eb5b8b642c 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -150,8 +150,10 @@ export type PrimitiveComponentChildren =
/** These are some internal stuff we use to test the component and for forcing a state in docs */
type UploadThingInternalProps = {
__internal_state?: "readying" | "ready" | "uploading";
+ // Allow to set upload progress for testing
__internal_upload_progress?: number;
- __internal_button_disabled?: boolean;
+ // Allow to set ready explicitly and independently of internal state
+ __internal_ready?: boolean;
};
export type RootPrimitiveComponentProps<
@@ -230,9 +232,7 @@ export function Root<
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);
- let disabled = fileTypes.length === 0;
- if (props.disabled) disabled = true;
- if (props.__internal_button_disabled) disabled = true;
+ const disabled = fileTypes.length === 0 || !!props.disabled;
const accept = generateMimeTypes(fileTypes).join(", ");
@@ -264,6 +264,10 @@ export function Root<
if (mode === "auto") void uploadFiles(files);
});
+ const ready =
+ props.__internal_ready ??
+ (props.__internal_state === "ready" || fileTypes.length > 0);
+
const primitiveValues: PrimitiveContextValues = {
files,
setFiles: (files) => {
@@ -295,7 +299,7 @@ export function Root<
},
routeConfig,
isUploading: state === "uploading",
- ready: state === "ready",
+ ready,
};
return (
From 1fea66201708c9b1fcc1f724c50930c549198e74 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:43:30 +0100
Subject: [PATCH 27/39] fix: add disabled check on primitive button on click
---
packages/react/src/components/primitive/button.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/react/src/components/primitive/button.tsx b/packages/react/src/components/primitive/button.tsx
index e39b2ce2c5..6b1eacc435 100644
--- a/packages/react/src/components/primitive/button.tsx
+++ b/packages/react/src/components/primitive/button.tsx
@@ -37,6 +37,8 @@ function ButtonFn(
data-state={state}
aria-disabled={disabled}
onClick={(e) => {
+ if (disabled) return;
+
onClick?.(e);
if (state === "uploading") {
e.preventDefault();
From 07424f32fb4f181d41f7014c8c767e7b90547f02 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:45:54 +0100
Subject: [PATCH 28/39] fix: usePaste hook in primtive root not auto uploading
the pasted files
---
packages/react/src/components/primitive/root.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index eb5b8b642c..b2ab33c973 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -261,7 +261,7 @@ export function Root<
return filesToUpload;
});
- if (mode === "auto") void uploadFiles(files);
+ if (mode === "auto") void uploadFiles(filesToUpload);
});
const ready =
From 3b8a3664a228176c3c828463c8ebe982d470acbf Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 18:46:45 +0100
Subject: [PATCH 29/39] chore: remove redundant import in root primitve
---
packages/react/src/components/primitive/root.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index b2ab33c973..95281d1d81 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -17,7 +17,6 @@ import {
UploadAbortedError,
} from "@uploadthing/shared";
import type {
- ErrorMessage,
ExpandedRouteConfig,
FileRouterInputKey,
} from "@uploadthing/shared";
From 8faf68f27a26f22f3a1f4c765aff32c8744c1c97 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 2 Oct 2024 19:01:45 +0100
Subject: [PATCH 30/39] fix: backwards compat with onDrop prop
---
packages/react/src/components/dropzone.tsx | 6 ++++-
.../src/components/primitive/dropzone.tsx | 22 +++++++++++++++++--
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx
index 3802357285..43ad5629a0 100644
--- a/packages/react/src/components/dropzone.tsx
+++ b/packages/react/src/components/dropzone.tsx
@@ -93,6 +93,7 @@ export function UploadDropzone<
className,
content,
appearance,
+ onDrop,
__internal_dropzone_disabled,
__internal_button_disabled,
...rootProps
@@ -103,7 +104,10 @@ export function UploadDropzone<
return (
{...(rootProps as any)}>
-
+
{({
files,
fileTypes,
diff --git a/packages/react/src/components/primitive/dropzone.tsx b/packages/react/src/components/primitive/dropzone.tsx
index 87fa28d0d5..353f7900c8 100644
--- a/packages/react/src/components/primitive/dropzone.tsx
+++ b/packages/react/src/components/primitive/dropzone.tsx
@@ -40,12 +40,22 @@ const DEFAULT_DROPZONE_TAG = "div" as const;
export type PrimitiveDropzoneProps<
TTag extends ElementType = typeof DEFAULT_DROPZONE_TAG,
-> = PrimitiveComponentProps & { disabled?: boolean | undefined };
+> = PrimitiveComponentProps & {
+ disabled?: boolean | undefined;
+ /**
+ * Callback called when files are dropped.
+ *
+ * @param acceptedFiles - The files that were accepted.
+ * @deprecated Use `onFilesChange` in ``
+ */
+ onFilesDropped?: ((acceptedFiles: File[]) => void) | undefined;
+};
function DropzoneFn(
{
children,
as,
+ onDrop,
disabled: componentDisabled,
...props
}: PrimitiveDropzoneProps,
@@ -60,8 +70,16 @@ function DropzoneFn(
refs,
} = usePrimitiveValues("Dropzone");
+ const onDropCallback = useCallback(
+ (acceptedFiles: File[]) => {
+ onDrop?.(acceptedFiles);
+ setFiles(acceptedFiles);
+ },
+ [setFiles, onDrop],
+ );
+
const { getRootProps, getInputProps, isDragActive, rootRef } = useDropzone({
- onDrop: setFiles,
+ onDrop: onDropCallback,
multiple: options.multiple,
accept: fileTypes ? generateClientDropzoneAccept(fileTypes) : undefined,
disabled: rootDisabled || componentDisabled,
From f49d5c69e3fc9b292ba71ee7bb5cd720c67e1615 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 23 Oct 2024 18:28:16 +0100
Subject: [PATCH 31/39] fix: merge conflicts
---
packages/react/src/components/button.tsx | 6 ++--
packages/react/src/components/dropzone.tsx | 25 ++++++---------
packages/react/src/components/index.tsx | 6 ++++
.../react/src/components/primitive/button.tsx | 9 +++---
.../src/components/primitive/clear-button.tsx | 4 +--
.../src/components/primitive/dropzone.tsx | 26 +++++++--------
.../react/src/components/primitive/root.tsx | 32 +++++++------------
7 files changed, 49 insertions(+), 59 deletions(-)
diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx
index 3bb0025d1c..37665937dc 100644
--- a/packages/react/src/components/button.tsx
+++ b/packages/react/src/components/button.tsx
@@ -23,6 +23,7 @@ type ButtonStyleFieldCallbackArgs = {
isUploading: boolean;
uploadProgress: number;
fileTypes: string[];
+ files: File[];
};
type ButtonAppearance = {
@@ -104,6 +105,7 @@ export function UploadButton<
isUploading: state === "uploading",
uploadProgress: uploadProgress,
fileTypes: fileTypes,
+ files,
} as ButtonStyleFieldCallbackArgs;
const renderAllowedContent = () => (
@@ -151,7 +153,7 @@ export function UploadButton<
return "Loading...";
}
case "uploading": {
- if (uploadProgress === 100) return ;
+ if (uploadProgress >= 100) return ;
return (
@@ -206,4 +208,4 @@ export function UploadButton<
}}
);
-}
\ No newline at end of file
+}
diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx
index b8e8925c6f..7e075c5c72 100644
--- a/packages/react/src/components/dropzone.tsx
+++ b/packages/react/src/components/dropzone.tsx
@@ -24,6 +24,7 @@ type DropzoneStyleFieldCallbackArgs = {
uploadProgress: number;
fileTypes: string[];
isDragActive: boolean;
+ files: File[];
};
type DropzoneAppearance = {
@@ -108,22 +109,14 @@ export function UploadDropzone<
onFilesDropped={onDrop}
disabled={__internal_dropzone_disabled}
>
- {({
- files,
- fileTypes,
- dropzone,
- isUploading,
- ready,
- uploadProgress,
- state,
- options,
- }) => {
+ {({ files, fileTypes, dropzone, uploadProgress, state, options }) => {
const styleFieldArg = {
fileTypes,
isDragActive: !!dropzone?.isDragActive,
- isUploading,
- ready,
+ isUploading: state === "uploading",
+ ready: state !== "readying",
uploadProgress,
+ files,
} as DropzoneStyleFieldCallbackArgs;
const getUploadButtonContents = () => {
@@ -138,7 +131,7 @@ export function UploadDropzone<
return "Loading...";
}
case "uploading": {
- if (uploadProgress === 100) return ;
+ if (uploadProgress >= 100) return ;
return (
@@ -206,7 +199,7 @@ export function UploadDropzone<
{contentFieldToContent(content?.label, styleFieldArg) ??
- (ready
+ (state === "ready"
? `Choose ${options.multiple ? "file(s)" : "a file"} or drag and drop`
: `Loading...`)}
@@ -260,4 +253,4 @@ export function UploadDropzone<
);
-}
\ No newline at end of file
+}
diff --git a/packages/react/src/components/index.tsx b/packages/react/src/components/index.tsx
index 64ab258d43..b33c66b31c 100644
--- a/packages/react/src/components/index.tsx
+++ b/packages/react/src/components/index.tsx
@@ -63,6 +63,12 @@ export const generateUploadDropzone = (
export const generateUploadPrimitives = (
opts?: GenerateTypedHelpersOptions,
) => {
+ warnIfInvalidPeerDependency(
+ "@uploadthing/react",
+ peerDependencies.uploadthing,
+ uploadthingClientVersion,
+ );
+
const url = resolveMaybeUrlArg(opts?.url);
const TypedUploadRoot = (
diff --git a/packages/react/src/components/primitive/button.tsx b/packages/react/src/components/primitive/button.tsx
index 6b1eacc435..fa812fa579 100644
--- a/packages/react/src/components/primitive/button.tsx
+++ b/packages/react/src/components/primitive/button.tsx
@@ -18,7 +18,6 @@ function ButtonFn(
) {
const {
refs,
- disabled,
setFiles,
dropzone,
accept,
@@ -35,9 +34,9 @@ function ButtonFn(
{
- if (disabled) return;
+ if (state === "disabled") return;
onClick?.(e);
if (state === "uploading") {
@@ -66,8 +65,8 @@ function ButtonFn(
if (!e.target.files) return;
setFiles(Array.from(e.target.files));
}}
- disabled={disabled}
- tabIndex={disabled ? -1 : 0}
+ disabled={state === "disabled"}
+ tabIndex={state === "disabled" ? -1 : 0}
className="sr-only"
/>
)}
diff --git a/packages/react/src/components/primitive/clear-button.tsx b/packages/react/src/components/primitive/clear-button.tsx
index 215119dead..03361158aa 100644
--- a/packages/react/src/components/primitive/clear-button.tsx
+++ b/packages/react/src/components/primitive/clear-button.tsx
@@ -18,14 +18,14 @@ function ClearButtonFn<
{ children, onClick, as, ...props }: PrimitiveClearButtonProps,
ref: Ref,
) {
- const { setFiles, state, disabled } = usePrimitiveValues("ClearButton");
+ const { setFiles, state } = usePrimitiveValues("ClearButton");
const Comp = as ?? DEFAULT_CLEAR_BUTTON_TAG;
return (
{
onClick?.(e);
setFiles([]);
diff --git a/packages/react/src/components/primitive/dropzone.tsx b/packages/react/src/components/primitive/dropzone.tsx
index 353f7900c8..c8395cb75d 100644
--- a/packages/react/src/components/primitive/dropzone.tsx
+++ b/packages/react/src/components/primitive/dropzone.tsx
@@ -61,14 +61,8 @@ function DropzoneFn(
}: PrimitiveDropzoneProps,
ref: Ref,
) {
- const {
- setFiles,
- options,
- fileTypes,
- disabled: rootDisabled,
- state,
- refs,
- } = usePrimitiveValues("Dropzone");
+ const { setFiles, options, fileTypes, state, refs } =
+ usePrimitiveValues("Dropzone");
const onDropCallback = useCallback(
(acceptedFiles: File[]) => {
@@ -82,7 +76,7 @@ function DropzoneFn(
onDrop: onDropCallback,
multiple: options.multiple,
accept: fileTypes ? generateClientDropzoneAccept(fileTypes) : undefined,
- disabled: rootDisabled || componentDisabled,
+ disabled: state === "disabled" || componentDisabled,
});
const Comp = as ?? DEFAULT_DROPZONE_TAG;
@@ -99,7 +93,7 @@ function DropzoneFn(
ref={ref}
>
{children}
-
+
);
@@ -355,9 +349,15 @@ export function useDropzone({
[openFileDialog],
);
- const onInputElementClick = useCallback((e: MouseEvent) => {
- e.stopPropagation();
- }, []);
+ const onInputElementClick = useCallback(
+ (e: MouseEvent) => {
+ e.stopPropagation();
+ if (state.isFileDialogActive) {
+ e.preventDefault();
+ }
+ },
+ [state.isFileDialogActive],
+ );
// Update focus state for the dropzone
const onFocus = useCallback(() => dispatch({ type: "focus" }), []);
diff --git a/packages/react/src/components/primitive/root.tsx b/packages/react/src/components/primitive/root.tsx
index 95281d1d81..e85af25394 100644
--- a/packages/react/src/components/primitive/root.tsx
+++ b/packages/react/src/components/primitive/root.tsx
@@ -23,17 +23,13 @@ import type {
import type { FileRouter } from "uploadthing/types";
import type { UploadthingComponentProps } from "../../types";
-import { INTERNAL_uploadthingHookGen } from "../../useUploadThing";
+import { __useUploadThingInternal } from "../../useUploadThing";
import { useControllableState } from "../../utils/useControllableState";
import { usePaste } from "../../utils/usePaste";
type PrimitiveContextValues = {
state: "readying" | "ready" | "uploading" | "disabled";
- disabled: boolean;
- isUploading: boolean;
- ready: boolean;
-
files: File[];
fileTypes: FileRouterInputKey[];
accept: string;
@@ -177,10 +173,6 @@ export function Root<
const { mode = "auto", appendOnPaste = false } = props.config ?? {};
const acRef = useRef(new AbortController());
- const useUploadThing = INTERNAL_uploadthingHookGen({
- url: resolveMaybeUrlArg(props.url),
- });
-
const focusElementRef = useRef(null);
const fileInputRef = useRef(null);
const [uploadProgress, setUploadProgress] = useState(
@@ -192,7 +184,8 @@ export function Root<
defaultProp: [],
});
- const { startUpload, isUploading, routeConfig } = useUploadThing(
+ const { startUpload, isUploading, routeConfig } = __useUploadThingInternal(
+ resolveMaybeUrlArg(props.url),
props.endpoint,
{
signal: acRef.current.signal,
@@ -231,14 +224,18 @@ export function Root<
const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig);
- const disabled = fileTypes.length === 0 || !!props.disabled;
-
const accept = generateMimeTypes(fileTypes).join(", ");
const state = (() => {
if (props.__internal_state) return props.__internal_state;
- if (disabled) return "disabled";
- if (!disabled && !isUploading) return "ready";
+
+ const ready =
+ props.__internal_ready ??
+ (props.__internal_state === "ready" || fileTypes.length > 0);
+
+ if (fileTypes.length === 0 || !!props.disabled) return "disabled";
+ if (!ready) return "readying";
+ if (ready && !isUploading) return "ready";
return "uploading";
})();
@@ -263,10 +260,6 @@ export function Root<
if (mode === "auto") void uploadFiles(filesToUpload);
});
- const ready =
- props.__internal_ready ??
- (props.__internal_state === "ready" || fileTypes.length > 0);
-
const primitiveValues: PrimitiveContextValues = {
files,
setFiles: (files) => {
@@ -288,7 +281,6 @@ export function Root<
},
uploadProgress,
state,
- disabled,
accept,
fileTypes,
options: { mode, multiple },
@@ -297,8 +289,6 @@ export function Root<
fileInputRef,
},
routeConfig,
- isUploading: state === "uploading",
- ready,
};
return (
From 24082c741860b5d5891f6ffa7426a918af5135d6 Mon Sep 17 00:00:00 2001
From: veloii <85405932+veloii@users.noreply.github.com>
Date: Wed, 23 Oct 2024 18:34:49 +0100
Subject: [PATCH 32/39] fix: examples to use state instead of isUploading
---
examples/minimal-appdir/src/app/page.tsx | 4 ++--
examples/minimal-pagedir/src/pages/index.tsx | 4 ++--
examples/with-clerk-appdir/src/app/page.tsx | 4 ++--
examples/with-clerk-pagesdir/src/pages/index.tsx | 4 ++--
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/examples/minimal-appdir/src/app/page.tsx b/examples/minimal-appdir/src/app/page.tsx
index 1f5220dc3a..4b2a02b0ba 100644
--- a/examples/minimal-appdir/src/app/page.tsx
+++ b/examples/minimal-appdir/src/app/page.tsx
@@ -82,7 +82,7 @@ export default function Home() {
}}
>
- {({ dropzone, isUploading }) => (
+ {({ dropzone, state }) => (
- {isUploading ? "Uploading" : "Upload file"}
+ {state === "uploading" ? "Uploading" : "Upload file"}
- {({ dropzone, isUploading }) => (
+ {({ dropzone, state }) => (
- {isUploading ? "Uploading" : "Upload file"}
+ {state === "uploading" ? "Uploading" : "Upload file"}
- {({ dropzone, isUploading }) => (
+ {({ dropzone, state }) => (
- {isUploading ? "Uploading" : "Upload file"}
+ {state === "uploading" ? "Uploading" : "Upload file"}
- {({ dropzone, isUploading }) => (
+ {({ dropzone, state }) => (
- {isUploading ? "Uploading" : "Upload file"}
+ {state === "uploading" ? "Uploading" : "Upload file"}
Date: Wed, 23 Oct 2024 18:47:44 +0100
Subject: [PATCH 33/39] docs: unstyled primitive components
---
docs/src/app/(docs)/concepts/theming/page.mdx | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/docs/src/app/(docs)/concepts/theming/page.mdx b/docs/src/app/(docs)/concepts/theming/page.mdx
index d28e561f35..8484c1eab5 100644
--- a/docs/src/app/(docs)/concepts/theming/page.mdx
+++ b/docs/src/app/(docs)/concepts/theming/page.mdx
@@ -509,3 +509,51 @@ type UploadDropzoneProps = {
+
+
+## Unstyled Primitive Components
+
+These components allow you to bring your own styling solution while not having to implement any of the internals. They accept any normal HTML props and can be assigned specific HTML tags through the `as` prop.
+
+### Setup
+
+```ts {{ title: 'src/utils/uploadthing.ts' }}
+import { generateUploadPrimitives } from "@uploadthing/react";
+
+import type { OurFileRouter } from "~/server/uploadthing";
+
+export const UT = generateUploadPrimitives();
+```
+
+### Example of Dropzone
+
+The `dropzone` parameter will only be defined from within children of the `Dropzone` component.
+
+```jsx
+
+