Repository: gradio-app/gradio
Pull request: Allows setting a height to `gr.File` and improves the UI of the component
This PR:

* Adds a `height` parameter to `gr.File()` which sets both the height of upload box as well as the maximum height of the displayed files. If the number of files exceeds what can be displayed, then a scrollbar appears. This is useful when displaying a large number of files (and closes: #5131)
* Fixes some incorrect types in `gr.Gallery` which was the inspiration for this PR
* Improves the UI of `gr.File` a bit --> no more "Download" text. Now just clicking on the filename downloads the file. This looks better on smaller screens and also makes it easier for users to download the file that they intend to download since they no longer have to trace across the screen. 

<img width="770" alt="image" src="https://github.com/gradio-app/gradio/assets/1778297/7b237f81-96b0-46d7-a384-aa3176ca60cf">

* Adds stories for `gr.File`

As a side note, it seemed like I had to add the `height` prop to a lot of different places in order to get this to work. Maybe I'm doing something wrong? 

Fixes: #5131

Problem statement:
Limit height for Files Component and adding vertical scrollbar
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
Since currently the limit for File upload was removed it is especially cumbersome to have a large filelist display, which is completely trashing an otherwise nice layout. 

**Describe the solution you'd like**  
A max scale or height would be great to limit the height and allow for vertical scrolling.
Perhaps this can already be achieved with tricky css but so far my efforts were unlucky.

Hints from issue discussion:

Commit message: add changeset

Patch to review:
diff --git a/.changeset/lovely-bikes-thank.md b/.changeset/lovely-bikes-thank.md
new file mode 100644
index 0000000000..76f3244425
--- /dev/null
+++ b/.changeset/lovely-bikes-thank.md
@@ -0,0 +1,7 @@
+---
+"@gradio/file": minor
+"@gradio/upload": minor
+"gradio": minor
+---
+
+feat:Allows setting a height to `gr.File` and improves the UI of the component
diff --git a/gradio/components/file.py b/gradio/components/file.py
index 79426473d6..e6bfb5ff32 100644
--- a/gradio/components/file.py
+++ b/gradio/components/file.py
@@ -55,6 +55,7 @@ def __init__(
         container: bool = True,
         scale: int | None = None,
         min_width: int = 160,
+        height: int | float | None = None,
         interactive: bool | None = None,
         visible: bool = True,
         elem_id: str | None = None,
@@ -73,6 +74,7 @@ def __init__(
             container: If True, will place the component in a container - providing some extra padding around the border.
             scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
             min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+            height: The maximum height of the file component, in pixels. If more files are uploaded than can fit in the height, a scrollbar will appear.
             interactive: if True, will allow users to upload a file; if False, can only be used to display files. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
@@ -102,6 +104,7 @@ def __init__(
                 "The `file_types` parameter is ignored when `file_count` is 'directory'."
             )
         self.type = type
+        self.height = height
         self.select: EventListenerMethod
         """
         Event listener for when the user selects file from list.
@@ -130,6 +133,7 @@ def get_config(self):
             "file_types": self.file_types,
             "value": self.value,
             "selectable": self.selectable,
+            "height": self.height,
             **IOComponent.get_config(self),
         }
 
@@ -141,6 +145,7 @@ def update(
         container: bool | None = None,
         scale: int | None = None,
         min_width: int | None = None,
+        height: int | float | None = None,
         interactive: bool | None = None,
         visible: bool | None = None,
     ):
@@ -150,6 +155,7 @@ def update(
             "container": container,
             "scale": scale,
             "min_width": min_width,
+            "height": height,
             "interactive": interactive,
             "visible": visible,
             "value": value,
diff --git a/gradio/components/gallery.py b/gradio/components/gallery.py
index 8dccacf77f..f32b0aa700 100644
--- a/gradio/components/gallery.py
+++ b/gradio/components/gallery.py
@@ -48,7 +48,7 @@ def __init__(
         elem_classes: list[str] | str | None = None,
         columns: int | tuple | None = 2,
         rows: int | tuple | None = None,
-        height: str | None = None,
+        height: int | float | None = None,
         preview: bool | None = None,
         object_fit: Literal["contain", "cover", "fill", "none", "scale-down"]
         | None = None,
@@ -71,7 +71,7 @@ def __init__(
             elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
             columns: Represents the number of images that should be shown in one row, for each of the six standard screen sizes (<576px, <768px, <992px, <1200px, <1400px, >1400px). if fewer that 6 are given then the last will be used for all subsequent breakpoints
             rows: Represents the number of rows in the image grid, for each of the six standard screen sizes (<576px, <768px, <992px, <1200px, <1400px, >1400px). if fewer that 6 are given then the last will be used for all subsequent breakpoints
-            height: Height of the gallery.
+            height: Height of the gallery, in pixels.
             preview: If True, will display the Gallery in preview mode, which shows all of the images as thumbnails and allows the user to click on them to view them in full size.
             object_fit: CSS object-fit property for the thumbnail images in the gallery. Can be "contain", "cover", "fill", "none", or "scale-down".
             allow_preview: If True, images in the gallery will be enlarged when they are clicked. Default is True.
@@ -127,7 +127,7 @@ def update(
         visible: bool | None = None,
         columns: int | tuple | None = None,
         rows: int | tuple | None = None,
-        height: str | None = None,
+        height: int | float | None = None,
         preview: bool | None = None,
         object_fit: Literal["contain", "cover", "fill", "none", "scale-down"]
         | None = None,
diff --git a/js/file/File.stories.svelte b/js/file/File.stories.svelte
new file mode 100644
index 0000000000..6f6590624f
--- /dev/null
+++ b/js/file/File.stories.svelte
@@ -0,0 +1,45 @@
+<script>
+	import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
+	import FilePreview from "./shared/FilePreview.svelte";
+</script>
+
+<Meta
+	title="Components/File"
+	component={FilePreview}
+	argTypes={{
+		value: {
+			control: "text",
+			description: "The URL or filepath (or list of URLs or filepaths)",
+			name: "value",
+			value: []
+		}
+	}}
+/>
+
+<Template let:args>
+	<FilePreview {...args} />
+</Template>
+
+<Story
+	name="Default"
+	args={{
+		value: [
+			{
+				name: "groot.jpg",
+				data: "https://i.ibb.co/6BgKdSj/groot.jpg",
+				size: 10000
+			}
+		]
+	}}
+/>
+<Story
+	name="With height set to 80px"
+	args={{
+		value: Array(10).fill({
+			name: "groot.jpg",
+			data: "https://i.ibb.co/6BgKdSj/groot.jpg",
+			size: 10000
+		}),
+		height: 80
+	}}
+/>
diff --git a/js/file/index.svelte b/js/file/index.svelte
index 1685ad486c..878753d887 100644
--- a/js/file/index.svelte
+++ b/js/file/index.svelte
@@ -31,6 +31,7 @@
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
+	export let height: number | "auto" = "auto";
 </script>
 
 {#if mode === "static"}
@@ -49,6 +50,7 @@
 		{container}
 		{scale}
 		{min_width}
+		{height}
 		on:clear
 		on:select
 		on:change
@@ -73,6 +75,7 @@
 		{container}
 		{scale}
 		{min_width}
+		{height}
 		on:clear
 		on:select
 		on:change
diff --git a/js/file/interactive/FileUpload.svelte b/js/file/interactive/FileUpload.svelte
index d068801710..56c8ff08a8 100644
--- a/js/file/interactive/FileUpload.svelte
+++ b/js/file/interactive/FileUpload.svelte
@@ -14,6 +14,7 @@
 	export let file_count = "single";
 	export let file_types: string[] | null = null;
 	export let selectable = false;
+	export let height: number | "auto" = "auto";
 
 	async function handle_upload({
 		detail
@@ -64,13 +65,14 @@
 
 {#if value}
 	<ModifyUpload on:clear={handle_clear} absolute />
-	<FilePreview on:select {selectable} {value} />
+	<FilePreview on:select {selectable} {value} {height} />
 {:else}
 	<Upload
 		on:load={handle_upload}
 		filetype={accept_file_types}
 		parse_to_data_url={false}
 		{file_count}
+		{height}
 		bind:dragging
 	>
 		<slot />
diff --git a/js/file/interactive/InteractiveFile.svelte b/js/file/interactive/InteractiveFile.svelte
index 2df2edc07e..5482948958 100644
--- a/js/file/interactive/InteractiveFile.svelte
+++ b/js/file/interactive/InteractiveFile.svelte
@@ -33,6 +33,7 @@
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
+	export let height: number | "auto" = "auto";
 
 	const upload_files =
 		getContext<typeof default_upload_files>("upload_files") ??
@@ -128,6 +129,7 @@
 		{file_count}
 		{file_types}
 		{selectable}
+		{height}
 		on:change={({ detail }) => (value = detail)}
 		on:drag={({ detail }) => (dragging = detail)}
 		on:clear
diff --git a/js/file/shared/FilePreview.svelte b/js/file/shared/FilePreview.svelte
index 13310fb089..9b1a1f1650 100644
--- a/js/file/shared/FilePreview.svelte
+++ b/js/file/shared/FilePreview.svelte
@@ -9,9 +9,14 @@
 	}>();
 	export let value: FileData | FileData[];
 	export let selectable = false;
+	export let height: number | "auto" = "auto";
+	let max_height: string = height === "auto" ? "auto" : height + "px";
 </script>
 
-<div class="file-preview-holder">
+<div
+	class="file-preview-holder"
+	style="max-height: {max_height}; overflow-y:scroll;"
+>
 	<table class="file-preview">
 		<tbody>
 			{#each Array.isArray(value) ? value : [value] as file, i}
@@ -24,14 +29,6 @@
 							index: i
 						})}
 				>
-					<td>
-						{display_file_name(file)}
-					</td>
-
-					<td>
-						{display_file_size(file)}
-					</td>
-
 					<td class="download">
 						{#if file.data}
 							<a
@@ -41,12 +38,16 @@
 									? null
 									: file.orig_name || file.name}
 							>
-								Download
+								{display_file_name(file)}
 							</a>
 						{:else}
 							Uploading...
 						{/if}
 					</td>
+
+					<td>
+						{display_file_size(file)}
+					</td>
 				</tr>
 			{/each}
 		</tbody>
diff --git a/js/file/static/File.svelte b/js/file/static/File.svelte
index f1382ac5c2..f8936c4441 100644
--- a/js/file/static/File.svelte
+++ b/js/file/static/File.svelte
@@ -8,6 +8,7 @@
 	export let label: string;
 	export let show_label = true;
 	export let selectable = false;
+	export let height: number | "auto" = "auto";
 </script>
 
 <BlockLabel
@@ -18,7 +19,7 @@
 />
 
 {#if value}
-	<FilePreview {selectable} on:select {value} />
+	<FilePreview {selectable} on:select {value} {height} />
 {:else}
 	<Empty unpadded_box={true} size="large"><File /></Empty>
 {/if}
diff --git a/js/file/static/StaticFile.svelte b/js/file/static/StaticFile.svelte
index 75527be1e0..4e4b4a5039 100644
--- a/js/file/static/StaticFile.svelte
+++ b/js/file/static/StaticFile.svelte
@@ -25,6 +25,7 @@
 	export let root: string;
 	export let label: string;
 	export let show_label: boolean;
+	export let height: number | "auto" = "auto";
 
 	export let root_url: null | string;
 	export let selectable = false;
@@ -120,5 +121,5 @@
 			: loading_status?.status || "complete"}
 	/>
 
-	<File on:select {selectable} value={_value} {label} {show_label} />
+	<File on:select {selectable} value={_value} {label} {show_label} {height} />
 </Block>
diff --git a/js/upload/src/Upload.svelte b/js/upload/src/Upload.svelte
index d95ef7d5eb..43f006a92a 100644
--- a/js/upload/src/Upload.svelte
+++ b/js/upload/src/Upload.svelte
@@ -12,6 +12,7 @@
 	export let file_count = "single";
 	export let disable_click = false;
 	export let parse_to_data_url = true;
+	export let height: number | "auto" = "auto";
 
 	let hidden_upload: HTMLInputElement;
 
@@ -103,6 +104,7 @@
 	on:drop={loadFilesFromDrop}
 	on:dragenter={updateDragging}
 	on:dragleave={updateDragging}
+	style="height: {height === 'auto' ? 'auto' : height + 'px'};"
 >
 	<slot />
 	<input
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 41af0682e6..e4f6cbf813 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
 
 settings:
   autoInstallPeers: true
diff --git a/test/test_components.py b/test/test_components.py
index f9795c1a4a..94dcdf6779 100644
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -1015,6 +1015,7 @@ def test_component_functions(self):
             "interactive": None,
             "root_url": None,
             "selectable": False,
+            "height": None,
         }
         assert file_input.preprocess(None) is None
         x_file["is_example"] = True


Code review comments:
diff_hunk: @@ -41,12 +38,16 @@
 									? null
 									: file.orig_name || file.name}
 							>
-								Download
+								{display_file_name(file)}
path: js/file/shared/FilePreview.svelte
text: @user1:
It's best practice to add the file type and, if possible, the size of the file in parentheses at the end of a file name to denote that the link is a download ([ref](https://www.a11y-collective.com/the-perfect-link/#:~:text=WebAIM%20Quick%20Reference%3A%20Web%20Accessibility%20Principles%20(PDF%2C%20327%20Kb).)). As we've removed the download button, it's a little bit more implicit now. Since we've already got the file size in the next column, just the file type would be fine!

We could change the `display_file_name` func in `file/shared/utils.ts` to the following to achieve this:

```
export const display_file_name = (value: FileData): string => {
    var str: string;
    str = value.orig_name || value.name;
    
    if (str.length > 30) {
        str = `${str.substr(0, 30)}...`;
    }
    
    if (value.type) {
        const fileExtension = value.type.split('/').pop();
        if (fileExtension) {
            str += ` (${fileExtension.toUpperCase()})`;
        }
    }
    
    return str;
};
```

This func would output `important_document984023984903... (PDF)`. LMK your thoughts on implementing that :) 



@author:
Thanks @user1 appreciate the suggestion. Putting the extension in all caps feels very visually crowded to me, particularly if there's a list of many files. What do you think about instead always including the extension at the end of the filename even if we trim? So it would look like:

* `important_document984023984903...pdf`

Shorter filenames would remain in their original form:

* `requirements.txt`

@user1:
Yeah I think that'd work nicely!

@user1:
something like this
```
 if (str.length > 30) {
        const fileExtension = value.type ? value.type.split('/').pop() : null;
        if (fileExtension) {
            str = `${str.substr(0, 30 - fileExtension.length - 1)}...${fileExtension}`;
        } else {
            str = `${str.substr(0, 30)}...`;
        }
    }
```

@author:
Fixed:
<img width="630" alt="image" src="https://github.com/gradio-app/gradio/assets/1778297/cb8a5e6f-8fed-4866-8529-1a23ef9b2d83">