diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a9b39cb3d61..a2d6f884b899e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## New Features: +### Dropdown Component Updates + +The standard dropdown component now supports searching for choices. Also when `multiselect` is `True`, you can specify `max_choices` to set the maximum number of choices you want the user to be able to select from the dropdown component. + +```python +gr.Dropdown(label="Choose your favorite colors", choices=["red", "blue", "green", "yellow", "orange"], multiselect=True, max_choices=2) +``` +by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3211](https://github.com/gradio-app/gradio/pull/3211) + ### Download button for images 🖼️ Output images will now automatically have a download button displayed to make it easier to save and share diff --git a/demo/kitchen_sink/run.ipynb b/demo/kitchen_sink/run.ipynb index 7131e5e337db1..c61c3aa5dc8a1 100644 --- a/demo/kitchen_sink/run.ipynb +++ b/demo/kitchen_sink/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: kitchen_sink"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/cantina.wav https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cantina.wav\n", "!wget -q -O files/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cheetah1.jpg\n", "!wget -q -O files/time.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/time.csv\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/titanic.csv\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "import json\n", "\n", "import numpy as np\n", "\n", "import gradio as gr\n", "\n", "CHOICES = [\"foo\", \"bar\", \"baz\"]\n", "JSONOBJ = \"\"\"{\"items\":{\"item\":[{\"id\": \"0001\",\"type\": null,\"is_good\": false,\"ppu\": 0.55,\"batters\":{\"batter\":[{ \"id\": \"1001\", \"type\": \"Regular\" },{ \"id\": \"1002\", \"type\": \"Chocolate\" },{ \"id\": \"1003\", \"type\": \"Blueberry\" },{ \"id\": \"1004\", \"type\": \"Devil's Food\" }]},\"topping\":[{ \"id\": \"5001\", \"type\": \"None\" },{ \"id\": \"5002\", \"type\": \"Glazed\" },{ \"id\": \"5005\", \"type\": \"Sugar\" },{ \"id\": \"5007\", \"type\": \"Powdered Sugar\" },{ \"id\": \"5006\", \"type\": \"Chocolate with Sprinkles\" },{ \"id\": \"5003\", \"type\": \"Chocolate\" },{ \"id\": \"5004\", \"type\": \"Maple\" }]}]}}\"\"\"\n", "\n", "\n", "def fn(\n", " text1,\n", " text2,\n", " num,\n", " slider1,\n", " slider2,\n", " single_checkbox,\n", " checkboxes,\n", " radio,\n", " dropdown,\n", " im1,\n", " im2,\n", " im3,\n", " im4,\n", " video,\n", " audio1,\n", " audio2,\n", " file,\n", " df1,\n", " df2,\n", "):\n", " return (\n", " (text1 if single_checkbox else text2)\n", " + \", selected:\"\n", " + \", \".join(checkboxes), # Text\n", " {\n", " \"positive\": num / (num + slider1 + slider2),\n", " \"negative\": slider1 / (num + slider1 + slider2),\n", " \"neutral\": slider2 / (num + slider1 + slider2),\n", " }, # Label\n", " (audio1[0], np.flipud(audio1[1]))\n", " if audio1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cantina.wav\"), # Audio\n", " np.flipud(im1)\n", " if im1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"), # Image\n", " video\n", " if video is not None\n", " else os.path.join(os.path.abspath(''), \"files/world.mp4\"), # Video\n", " [\n", " (\"The\", \"art\"),\n", " (\"quick brown\", \"adj\"),\n", " (\"fox\", \"nn\"),\n", " (\"jumped\", \"vrb\"),\n", " (\"testing testing testing\", None),\n", " (\"over\", \"prp\"),\n", " (\"the\", \"art\"),\n", " (\"testing\", None),\n", " (\"lazy\", \"adj\"),\n", " (\"dogs\", \"nn\"),\n", " (\".\", \"punc\"),\n", " ]\n", " + [(f\"test {x}\", f\"test {x}\") for x in range(10)], # HighlightedText\n", " # [(\"The testing testing testing\", None), (\"quick brown\", 0.2), (\"fox\", 1), (\"jumped\", -1), (\"testing testing testing\", 0), (\"over\", 0), (\"the\", 0), (\"testing\", 0), (\"lazy\", 1), (\"dogs\", 0), (\".\", 1)] + [(f\"test {x}\", x/10) for x in range(-10, 10)], # HighlightedText\n", " [\n", " (\"The testing testing testing\", None),\n", " (\"over\", 0.6),\n", " (\"the\", 0.2),\n", " (\"testing\", None),\n", " (\"lazy\", -0.1),\n", " (\"dogs\", 0.4),\n", " (\".\", 0),\n", " ]\n", " + [(f\"test\", x / 10) for x in range(-10, 10)], # HighlightedText\n", " json.loads(JSONOBJ), # JSON\n", " \"\", # HTML\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " df1, # Dataframe\n", " np.random.randint(0, 10, (4, 4)), # Dataframe\n", " df2, # Timeseries\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fn,\n", " inputs=[\n", " gr.Textbox(value=\"Lorem ipsum\", label=\"Textbox\"),\n", " gr.Textbox(lines=3, placeholder=\"Type here..\", label=\"Textbox 2\"),\n", " gr.Number(label=\"Number\", value=42),\n", " gr.Slider(10, 20, value=15, label=\"Slider: 10 - 20\"),\n", " gr.Slider(maximum=20, step=0.04, label=\"Slider: step @ 0.04\"),\n", " gr.Checkbox(label=\"Checkbox\"),\n", " gr.CheckboxGroup(label=\"CheckboxGroup\", choices=CHOICES, value=CHOICES[0:2]),\n", " gr.Radio(label=\"Radio\", choices=CHOICES, value=CHOICES[2]),\n", " gr.Dropdown(label=\"Dropdown\", choices=CHOICES),\n", " gr.Image(label=\"Image\"),\n", " gr.Image(label=\"Image w/ Cropper\", tool=\"select\"),\n", " gr.Image(label=\"Sketchpad\", source=\"canvas\"),\n", " gr.Image(label=\"Webcam\", source=\"webcam\"),\n", " gr.Video(label=\"Video\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Audio(label=\"Microphone\", source=\"microphone\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\", headers=[\"Name\", \"Age\", \"Gender\"]),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], colors=[\"pink\", \"purple\"]),\n", " ],\n", " outputs=[\n", " gr.Textbox(label=\"Textbox\"),\n", " gr.Label(label=\"Label\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Image(label=\"Image\"),\n", " gr.Video(label=\"Video\"),\n", " gr.HighlightedText(label=\"HighlightedText\").style(\n", " color_map={\"punc\": \"pink\", \"test 0\": \"blue\"}\n", " ),\n", " gr.HighlightedText(label=\"HighlightedText\", show_legend=True),\n", " gr.JSON(label=\"JSON\"),\n", " gr.HTML(label=\"HTML\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\"),\n", " gr.Dataframe(label=\"Numpy\"),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], label=\"Timeseries\"),\n", " ],\n", " examples=[\n", " [\n", " \"the quick brown fox\",\n", " \"jumps over the lazy dog\",\n", " 10,\n", " 12,\n", " 4,\n", " True,\n", " [\"foo\", \"baz\"],\n", " \"baz\",\n", " \"bar\",\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/world.mp4\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]],\n", " os.path.join(os.path.abspath(''), \"files/time.csv\"),\n", " ]\n", " ]\n", " * 3,\n", " theme=\"default\",\n", " title=\"Kitchen Sink\",\n", " description=\"Try out all the components!\",\n", " article=\"Learn more about [Gradio](http://gradio.app)\",\n", " cache_examples=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: kitchen_sink"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/cantina.wav https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cantina.wav\n", "!wget -q -O files/cheetah1.jpg https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/cheetah1.jpg\n", "!wget -q -O files/time.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/time.csv\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/titanic.csv\n", "!wget -q -O files/world.mp4 https://github.com/gradio-app/gradio/raw/main/demo/kitchen_sink/files/world.mp4"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "import json\n", "\n", "import numpy as np\n", "\n", "import gradio as gr\n", "\n", "CHOICES = [\"foo\", \"bar\", \"baz\"]\n", "JSONOBJ = \"\"\"{\"items\":{\"item\":[{\"id\": \"0001\",\"type\": null,\"is_good\": false,\"ppu\": 0.55,\"batters\":{\"batter\":[{ \"id\": \"1001\", \"type\": \"Regular\" },{ \"id\": \"1002\", \"type\": \"Chocolate\" },{ \"id\": \"1003\", \"type\": \"Blueberry\" },{ \"id\": \"1004\", \"type\": \"Devil's Food\" }]},\"topping\":[{ \"id\": \"5001\", \"type\": \"None\" },{ \"id\": \"5002\", \"type\": \"Glazed\" },{ \"id\": \"5005\", \"type\": \"Sugar\" },{ \"id\": \"5007\", \"type\": \"Powdered Sugar\" },{ \"id\": \"5006\", \"type\": \"Chocolate with Sprinkles\" },{ \"id\": \"5003\", \"type\": \"Chocolate\" },{ \"id\": \"5004\", \"type\": \"Maple\" }]}]}}\"\"\"\n", "\n", "\n", "def fn(\n", " text1,\n", " text2,\n", " num,\n", " slider1,\n", " slider2,\n", " single_checkbox,\n", " checkboxes,\n", " radio,\n", " dropdown,\n", " multi_dropdown,\n", " im1,\n", " im2,\n", " im3,\n", " im4,\n", " video,\n", " audio1,\n", " audio2,\n", " file,\n", " df1,\n", " df2,\n", "):\n", " return (\n", " (text1 if single_checkbox else text2)\n", " + \", selected:\"\n", " + \", \".join(checkboxes), # Text\n", " {\n", " \"positive\": num / (num + slider1 + slider2),\n", " \"negative\": slider1 / (num + slider1 + slider2),\n", " \"neutral\": slider2 / (num + slider1 + slider2),\n", " }, # Label\n", " (audio1[0], np.flipud(audio1[1]))\n", " if audio1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cantina.wav\"), # Audio\n", " np.flipud(im1)\n", " if im1 is not None\n", " else os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"), # Image\n", " video\n", " if video is not None\n", " else os.path.join(os.path.abspath(''), \"files/world.mp4\"), # Video\n", " [\n", " (\"The\", \"art\"),\n", " (\"quick brown\", \"adj\"),\n", " (\"fox\", \"nn\"),\n", " (\"jumped\", \"vrb\"),\n", " (\"testing testing testing\", None),\n", " (\"over\", \"prp\"),\n", " (\"the\", \"art\"),\n", " (\"testing\", None),\n", " (\"lazy\", \"adj\"),\n", " (\"dogs\", \"nn\"),\n", " (\".\", \"punc\"),\n", " ]\n", " + [(f\"test {x}\", f\"test {x}\") for x in range(10)], # HighlightedText\n", " # [(\"The testing testing testing\", None), (\"quick brown\", 0.2), (\"fox\", 1), (\"jumped\", -1), (\"testing testing testing\", 0), (\"over\", 0), (\"the\", 0), (\"testing\", 0), (\"lazy\", 1), (\"dogs\", 0), (\".\", 1)] + [(f\"test {x}\", x/10) for x in range(-10, 10)], # HighlightedText\n", " [\n", " (\"The testing testing testing\", None),\n", " (\"over\", 0.6),\n", " (\"the\", 0.2),\n", " (\"testing\", None),\n", " (\"lazy\", -0.1),\n", " (\"dogs\", 0.4),\n", " (\".\", 0),\n", " ]\n", " + [(f\"test\", x / 10) for x in range(-10, 10)], # HighlightedText\n", " json.loads(JSONOBJ), # JSON\n", " \"\", # HTML\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " df1, # Dataframe\n", " np.random.randint(0, 10, (4, 4)), # Dataframe\n", " df2, # Timeseries\n", " )\n", "\n", "\n", "demo = gr.Interface(\n", " fn,\n", " inputs=[\n", " gr.Textbox(value=\"Lorem ipsum\", label=\"Textbox\"),\n", " gr.Textbox(lines=3, placeholder=\"Type here..\", label=\"Textbox 2\"),\n", " gr.Number(label=\"Number\", value=42),\n", " gr.Slider(10, 20, value=15, label=\"Slider: 10 - 20\"),\n", " gr.Slider(maximum=20, step=0.04, label=\"Slider: step @ 0.04\"),\n", " gr.Checkbox(label=\"Checkbox\"),\n", " gr.CheckboxGroup(label=\"CheckboxGroup\", choices=CHOICES, value=CHOICES[0:2]),\n", " gr.Radio(label=\"Radio\", choices=CHOICES, value=CHOICES[2]),\n", " gr.Dropdown(label=\"Dropdown\", choices=CHOICES),\n", " gr.Dropdown(label=\"Multiselect Dropdown (Max choice: 2)\", choices=CHOICES, multiselect=True, max_choices=2),\n", " gr.Image(label=\"Image\"),\n", " gr.Image(label=\"Image w/ Cropper\", tool=\"select\"),\n", " gr.Image(label=\"Sketchpad\", source=\"canvas\"),\n", " gr.Image(label=\"Webcam\", source=\"webcam\"),\n", " gr.Video(label=\"Video\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Audio(label=\"Microphone\", source=\"microphone\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\", headers=[\"Name\", \"Age\", \"Gender\"]),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], colors=[\"pink\", \"purple\"]),\n", " ],\n", " outputs=[\n", " gr.Textbox(label=\"Textbox\"),\n", " gr.Label(label=\"Label\"),\n", " gr.Audio(label=\"Audio\"),\n", " gr.Image(label=\"Image\"),\n", " gr.Video(label=\"Video\"),\n", " gr.HighlightedText(label=\"HighlightedText\").style(\n", " color_map={\"punc\": \"pink\", \"test 0\": \"blue\"}\n", " ),\n", " gr.HighlightedText(label=\"HighlightedText\", show_legend=True),\n", " gr.JSON(label=\"JSON\"),\n", " gr.HTML(label=\"HTML\"),\n", " gr.File(label=\"File\"),\n", " gr.Dataframe(label=\"Dataframe\"),\n", " gr.Dataframe(label=\"Numpy\"),\n", " gr.Timeseries(x=\"time\", y=[\"price\", \"value\"], label=\"Timeseries\"),\n", " ],\n", " examples=[\n", " [\n", " \"the quick brown fox\",\n", " \"jumps over the lazy dog\",\n", " 10,\n", " 12,\n", " 4,\n", " True,\n", " [\"foo\", \"baz\"],\n", " \"baz\",\n", " \"bar\",\n", " [\"foo\", \"bar\"],\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/cheetah1.jpg\"),\n", " os.path.join(os.path.abspath(''), \"files/world.mp4\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/cantina.wav\"),\n", " os.path.join(os.path.abspath(''), \"files/titanic.csv\"),\n", " [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]],\n", " os.path.join(os.path.abspath(''), \"files/time.csv\"),\n", " ]\n", " ]\n", " * 3,\n", " theme=\"default\",\n", " title=\"Kitchen Sink\",\n", " description=\"Try out all the components!\",\n", " article=\"Learn more about [Gradio](http://gradio.app)\",\n", " cache_examples=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/kitchen_sink/run.py b/demo/kitchen_sink/run.py index ebefc517374c0..53e5a94634b11 100755 --- a/demo/kitchen_sink/run.py +++ b/demo/kitchen_sink/run.py @@ -19,6 +19,7 @@ def fn( checkboxes, radio, dropdown, + multi_dropdown, im1, im2, im3, @@ -96,6 +97,7 @@ def fn( gr.CheckboxGroup(label="CheckboxGroup", choices=CHOICES, value=CHOICES[0:2]), gr.Radio(label="Radio", choices=CHOICES, value=CHOICES[2]), gr.Dropdown(label="Dropdown", choices=CHOICES), + gr.Dropdown(label="Multiselect Dropdown (Max choice: 2)", choices=CHOICES, multiselect=True, max_choices=2), gr.Image(label="Image"), gr.Image(label="Image w/ Cropper", tool="select"), gr.Image(label="Sketchpad", source="canvas"), @@ -135,6 +137,7 @@ def fn( ["foo", "baz"], "baz", "bar", + ["foo", "bar"], os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"), os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"), os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg"), diff --git a/gradio/components.py b/gradio/components.py index fb7cb9dc5e8be..d59916f890bf9 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1204,6 +1204,7 @@ def __init__( value: str | List[str] | Callable | None = None, type: str = "value", multiselect: bool | None = None, + max_choices: int | None = None, label: str | None = None, info: str | None = None, every: float | None = None, @@ -1219,6 +1220,7 @@ def __init__( value: default value(s) selected in dropdown. If None, no value is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component. type: Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected. multiselect: if True, multiple choices can be selected. + max_choices: maximum number of choices that can be selected. If None, no limit is enforced. label: component name in interface. info: additional component description. every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. @@ -1238,6 +1240,9 @@ def __init__( if multiselect: if isinstance(value, str): value = [value] + if not multiselect and max_choices is not None: + warnings.warn("The `max_choices` parameter is ignored when `multiselect` is False.") + self.max_choices = max_choices self.test_input = self.choices[0] if len(self.choices) else None self.interpret_by_tokens = False IOComponent.__init__( @@ -1259,6 +1264,7 @@ def get_config(self): "choices": self.choices, "value": self.value, "multiselect": self.multiselect, + "max_choices": self.max_choices, **IOComponent.get_config(self), } diff --git a/test/test_components.py b/test/test_components.py index 20a80d4602986..1291ad5ded712 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -563,7 +563,7 @@ def test_component_functions(self): assert dropdown_input.preprocess("a") == "a" assert dropdown_input.postprocess("a") == "a" - dropdown_input_multiselect = gr.Dropdown(["a", "b", "c"], multiselect=True) + dropdown_input_multiselect = gr.Dropdown(["a", "b", "c"]) assert dropdown_input_multiselect.preprocess(["a", "c"]) == ["a", "c"] assert dropdown_input_multiselect.postprocess(["a", "c"]) == ["a", "c"] assert dropdown_input_multiselect.serialize(["a", "c"], True) == ["a", "c"] @@ -572,6 +572,8 @@ def test_component_functions(self): value=["a", "c"], choices=["a", "b", "c"], label="Select Your Inputs", + multiselect=True, + max_choices=2, ) assert dropdown_input_multiselect.get_config() == { "choices": ["a", "b", "c"], @@ -584,7 +586,8 @@ def test_component_functions(self): "visible": True, "interactive": None, "root_url": None, - "multiselect": None, + "multiselect": True, + "max_choices": 2, } with pytest.raises(ValueError): gr.Dropdown(["a"], type="unknown") diff --git a/ui/packages/app/src/components/Dropdown/Dropdown.svelte b/ui/packages/app/src/components/Dropdown/Dropdown.svelte index 3032c4a155ae5..e2082dcef377a 100644 --- a/ui/packages/app/src/components/Dropdown/Dropdown.svelte +++ b/ui/packages/app/src/components/Dropdown/Dropdown.svelte @@ -11,6 +11,7 @@ export let visible: boolean = true; export let value: string | Array = []; export let multiselect: boolean = false; + export let max_choices: number; export let choices: Array; export let show_label: boolean; export let style: Styles = {}; @@ -30,6 +31,7 @@ bind:value {choices} {multiselect} + {max_choices} {label} {info} {show_label} diff --git a/ui/packages/app/test/outbreak_forecast.spec.ts b/ui/packages/app/test/outbreak_forecast.spec.ts index 3184d83bec37e..4d6020cba971e 100644 --- a/ui/packages/app/test/outbreak_forecast.spec.ts +++ b/ui/packages/app/test/outbreak_forecast.spec.ts @@ -31,8 +31,10 @@ test("matplotlib", async ({ page }) => { await mock_api(page, [[{ type: "matplotlib", plot: BASE64_PLOT_IMG }]]); await page.goto("http://localhost:9876"); - await page.getByLabel("Plot Type").selectOption("Matplotlib"); - await page.getByLabel("Month").selectOption("January"); + await page.getByLabel("Plot Type").click(); + await page.getByRole("button", { name: "Matplotlib" }).click(); + await page.getByLabel("Month").click(); + await page.getByRole("button", { name: "January" }).click(); await page.getByLabel("Social Distancing?").check(); await Promise.all([ @@ -57,8 +59,10 @@ test("plotly", async ({ page }) => { ]); await page.goto("http://localhost:9876"); - await page.getByLabel("Plot Type").selectOption("Plotly"); - await page.getByLabel("Month").selectOption("January"); + await page.getByLabel("Plot Type").click(); + await page.getByRole("button", { name: "Matplotlib" }).click(); + await page.getByLabel("Month").click(); + await page.getByRole("button", { name: "January" }).click(); await page.getByLabel("Social Distancing?").check(); await Promise.all([ diff --git a/ui/packages/form/package.json b/ui/packages/form/package.json index e108ae2721976..1df7dc4bfd346 100644 --- a/ui/packages/form/package.json +++ b/ui/packages/form/package.json @@ -9,6 +9,7 @@ "private": true, "dependencies": { "@gradio/atoms": "workspace:^0.0.1", - "@gradio/utils": "workspace:^0.0.1" + "@gradio/utils": "workspace:^0.0.1", + "@gradio/icons": "workspace:^0.0.1" } } diff --git a/ui/packages/form/src/Dropdown.svelte b/ui/packages/form/src/Dropdown.svelte index 7a93ee34fd107..cb1ccf719a13a 100644 --- a/ui/packages/form/src/Dropdown.svelte +++ b/ui/packages/form/src/Dropdown.svelte @@ -1,70 +1,270 @@ diff --git a/ui/packages/form/src/DropdownOptions.svelte b/ui/packages/form/src/DropdownOptions.svelte new file mode 100644 index 0000000000000..d7b6a755b1645 --- /dev/null +++ b/ui/packages/form/src/DropdownOptions.svelte @@ -0,0 +1,73 @@ + + +{#if showOptions && !disabled} + +{/if} + + diff --git a/ui/packages/icons/src/DropdownArrow.svelte b/ui/packages/icons/src/DropdownArrow.svelte new file mode 100644 index 0000000000000..af9565c677c3b --- /dev/null +++ b/ui/packages/icons/src/DropdownArrow.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/ui/packages/icons/src/Remove.svelte b/ui/packages/icons/src/Remove.svelte new file mode 100644 index 0000000000000..5c9374a0099a6 --- /dev/null +++ b/ui/packages/icons/src/Remove.svelte @@ -0,0 +1,10 @@ + + + diff --git a/ui/packages/icons/src/index.ts b/ui/packages/icons/src/index.ts index 5fa3eee757502..a604ab7a149fd 100644 --- a/ui/packages/icons/src/index.ts +++ b/ui/packages/icons/src/index.ts @@ -5,6 +5,7 @@ export { default as Chat } from "./Chat.svelte"; export { default as Circle } from "./Circle.svelte"; export { default as Clear } from "./Clear.svelte"; export { default as Color } from "./Color.svelte"; +export { default as DropdownArrow } from "./DropdownArrow.svelte"; export { default as Edit } from "./Edit.svelte"; export { default as File } from "./File.svelte"; export { default as Image } from "./Image.svelte"; @@ -15,6 +16,7 @@ export { default as Music } from "./Music.svelte"; export { default as Pause } from "./Pause.svelte"; export { default as Play } from "./Play.svelte"; export { default as Plot } from "./Plot.svelte"; +export { default as Remove } from "./Remove.svelte"; export { default as Sketch } from "./Sketch.svelte"; export { default as Square } from "./Square.svelte"; export { default as Table } from "./Table.svelte"; diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 00a99ccec7d53..48478ed95474c 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -230,9 +230,11 @@ importers: packages/form: specifiers: '@gradio/atoms': workspace:^0.0.1 + '@gradio/icons': workspace:^0.0.1 '@gradio/utils': workspace:^0.0.1 dependencies: '@gradio/atoms': link:../atoms + '@gradio/icons': link:../icons '@gradio/utils': link:../utils packages/gallery: