浏览代码

revert pc select to original (#1123)

Elijah Ahianyo 2 年之前
父节点
当前提交
45ca318eb2

+ 2 - 0
pynecone/components/__init__.py

@@ -105,6 +105,8 @@ input = Input.create
 input_group = InputGroup.create
 input_left_addon = InputLeftAddon.create
 input_right_addon = InputRightAddon.create
+multi_select = MultiSelect
+multi_select_option = MultiSelectOption
 number_decrement_stepper = NumberDecrementStepper.create
 number_increment_stepper = NumberIncrementStepper.create
 number_input = NumberInput.create

+ 2 - 0
pynecone/components/forms/__init__.py

@@ -8,6 +8,8 @@ from .email import Email
 from .form import Form, FormControl, FormErrorMessage, FormHelperText, FormLabel
 from .iconbutton import IconButton
 from .input import Input, InputGroup, InputLeftAddon, InputRightAddon
+from .multiselect import Option as MultiSelectOption
+from .multiselect import Select as MultiSelect
 from .numberinput import (
     NumberDecrementStepper,
     NumberIncrementStepper,

+ 346 - 0
pynecone/components/forms/multiselect.py

@@ -0,0 +1,346 @@
+"""Provides a feature-rich Select and some (not all) related components."""
+
+from typing import Any, Dict, List, Optional, Set, Union
+
+from pynecone.base import Base
+from pynecone.components.component import Component
+from pynecone.event import EVENT_ARG
+from pynecone.vars import Var
+
+
+class Option(Base):
+    """An option component for the chakra-react-select Select."""
+
+    # What is displayed to the user
+    label: str
+
+    # The value of the option, must be serializable
+    value: Any
+
+    # the variant of the option tag
+    variant: Optional[str] = None
+
+    # [not working yet]
+    # Whether the option is disabled
+    # is_disabled: Optional[bool] = None
+
+    # [not working yet]
+    # The visual color appearance of the component
+    # options: "whiteAlpha" | "blackAlpha" | "gray" | "red" |
+    #  "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" |
+    #  "purple" | "pink" | "linkedin" | "facebook" | "messenger" |
+    #  "whatsapp" | "twitter" | "telegram"
+    # default: "gray"
+    # color_scheme: Optional[str] = None
+
+    # [not working yet]
+    # The icon of the option tag
+    # icon: Optional[str] = None
+
+
+class Select(Component):
+    """The default chakra-react-select Select component.
+    Not every available prop is listed here,
+    for a complete overview check the react-select/chakra-react-select docs.
+    Props added by chakra-react-select are marked with "[chakra]".
+    """
+
+    library = "chakra-react-select"
+    tag = "Select"
+
+    # Focus the control when it is mounted
+    auto_focus: Var[bool]
+
+    # Remove the currently focused option when the user presses backspace
+    #  when Select isClearable or isMulti
+    backspace_removes_value: Var[bool]
+
+    # Remove focus from the input when the user selects an option
+    # (handy for dismissing the keyboard on touch devices)
+    blur_input_on_select: Var[bool]
+
+    # When the user reaches the top/bottom of the menu,
+    # prevent scroll on the scroll-parent
+    capture_menu_scroll: Var[bool]
+
+    # [chakra]
+    # To use the chakraStyles prop, first,
+    # check the documentation for the original styles prop from the react-select docs.
+    # This package offers an identical API for the chakraStyles prop, however,
+    # the provided and output style objects use Chakra's sx prop
+    # instead of the default emotion styles the original package offers.
+    # This allows you to both use the shorthand styling props you'd normally use
+    # to style Chakra components, as well as tokens from your theme such as named colors.
+    # All of the style keys offered in the original package can be used in the chakraStyles prop
+    # except for menuPortal. Along with some other caveats, this is explained below.
+    # Most of the components rendered by this package use the basic Chakra <Box /> component with a few exceptions.
+    # Here are the style keys offered and the corresponding Chakra component that is rendered:
+    # - clearIndicator - Box (uses theme styles for Chakra's CloseButton)
+    # - container - Box
+    # - control - Box (uses theme styles for Chakra's Input)
+    # - dropdownIndicator - Box (uses theme styles for Chrakra's InputRightAddon)
+    # - downChevron - Icon
+    # - crossIcon - Icon
+    # - group - Box
+    # - groupHeading - Box (uses theme styles for Chakra's Menu group title)
+    # - indicatorsContainer - Box
+    # - indicatorSeparator - Divider
+    # - input - chakra.input (wrapped in a Box)
+    # - inputContainer - Box
+    # - loadingIndicator - Spinner
+    # - loadingMessage - Box
+    # - menu - Box
+    # - menuList - Box (uses theme styles for Chakra's Menu)
+    # - multiValue - chakra.span (uses theme styles for Chakra's Tag)
+    # - multiValueLabel - chakra.span (uses theme styles for Chakra's TagLabel)
+    # - multiValueRemove - Box (uses theme styles for Chakra's TagCloseButton)
+    # - noOptionsMessage - Box
+    # - option - Box (uses theme styles for Chakra's MenuItem)
+    # - placeholder - Box
+    # - singleValue - Box
+    # - valueContainer - Box
+    chakra_styles: Var[str]
+
+    # Close the select menu when the user selects an option
+    close_menu_on_select: Var[bool]
+
+    # If true, close the select menu when the user scrolls the document/body.
+    close_menu_on_scroll: Var[bool]
+
+    # [chakra]
+    # The visual color appearance of the component
+    # options: "whiteAlpha" | "blackAlpha" | "gray" | "red" |
+    #  "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" |
+    #  "purple" | "pink" | "linkedin" | "facebook" | "messenger" |
+    #  "whatsapp" | "twitter" | "telegram"
+    # default: "gray"
+    color_scheme: Var[str]
+
+    # This complex object includes all the compositional components
+    # that are used in react-select. If you wish to overwrite a component,
+    # pass in an object with the appropriate namespace.
+    # If you only wish to restyle a component,
+    # we recommend using the styles prop instead.
+    components: Var[Dict[str, Component]]
+
+    # Whether the value of the select, e.g. SingleValue,
+    # should be displayed in the control.
+    control_should_render_value: Var[bool]
+
+    # Delimiter used to join multiple values into a single HTML Input value
+    delimiter: Var[str]
+
+    # [chakra]
+    # Colors the component border with the given chakra color string on error state
+    # default: "red.500"
+    error_border_color: Var[str]
+
+    # Clear all values when the user presses escape AND the menu is closed
+    escape_clears_value: Var[bool]
+
+    # [chakra]
+    # Colors the component border with the given chakra color string on focus
+    # default: "blue.500"
+    focus_border_color: Var[str]
+
+    # Sets the form attribute on the input
+    form: Var[str]
+
+    # Hide the selected option from the menu
+    hide_selected_options: Var[bool]
+
+    # The id to set on the SelectContainer component.
+    # id: Var[str]
+
+    # The value of the search input
+    input_value: Var[str]
+
+    # The id of the search input
+    input_id: Var[str]
+
+    # Is the select value clearable
+    is_clearable: Var[bool]
+
+    # Is the select disabled
+    is_disabled: Var[bool]
+
+    # [chakra]
+    # Style component in the chakra invalid style
+    # default: False
+    is_invalid: Var[bool]
+
+    # Support multiple selected options
+    is_multi: Var[bool]
+
+    # [chakra]
+    # Style component as disabled (chakra style)
+    # default: False
+    is_read_only: Var[bool]
+
+    # Is the select direction right-to-left
+    is_rtl: Var[bool]
+
+    # Whether to enable search functionality
+    is_searchable: Var[bool]
+
+    # Minimum height of the menu before flipping
+    min_menu_height: Var[int]
+
+    # Maximum height of the menu before scrolling
+    max_menu_height: Var[int]
+
+    # Default placement of the menu in relation to the control.
+    # 'auto' will flip when there isn't enough space below the control.
+    # options: "bottom" | "auto" | "top"
+    menu_placement: Var[str]
+
+    # The CSS position value of the menu,
+    #  when "fixed" extra layout management is required
+    # options: "absolute" | "fixed"
+    menu_position: Var[str]
+
+    # Whether to block scroll events when the menu is open
+    menu_should_block_scroll: Var[bool]
+
+    # Whether the menu should be scrolled into view when it opens
+    menu_should_scroll_into_view: Var[bool]
+
+    # Name of the HTML Input (optional - without this, no input will be rendered)
+    name: Var[str]
+
+    # Allows control of whether the menu is opened when the Select is focused
+    open_menu_on_focus: Var[bool]
+
+    # Allows control of whether the menu is opened when the Select is clicked
+    open_menu_on_click: Var[bool]
+
+    # Array of options that populate the select menu
+    options: Var[List[Dict]]
+
+    # Number of options to jump in menu when page{up|down} keys are used
+    page_size: Var[int]
+
+    # Placeholder for the select value
+    placeholder: Var[Optional[str]]
+
+    # Marks the value-holding input as required for form validation
+    required: Var[bool]
+
+    # [chakra]
+    # If you choose to stick with the default selectedOptionStyle="color",
+    # you have one additional styling option.
+    # If you do not like the default of blue for the highlight color,
+    # you can pass the selectedOptionColorScheme prop to change it.
+    # This prop will accept any named color from your theme's color palette,
+    # and it will use the 500 value in light mode or the 300 value in dark mode.
+    # This prop can only be used for named colors from your theme, not arbitrary hex/rgb colors.
+    # If you would like to use a specific color for the background that's not a part of your theme,
+    # use the chakraStyles prop to customize it.
+    # default: "blue"
+    selected_option_color_scheme: Var[str]
+
+    # [chakra]
+    # The default option "color" will style a selected option
+    # similar to how react-select does it,
+    # by highlighting the selected option in the color blue.
+    # Alternatively, if you pass "check" for the value,
+    # the selected option will be styled like the Chakra UI Menu component
+    # and include a check icon next to the selected option(s).
+    # If is_multi and selected_option_style="check" are passed,
+    # space will only be added for the check marks
+    # if hide_selected_options=False is also passed.
+    # options: "color" | "check"
+    # default: "color"
+    selected_option_style: Var[str]
+
+    # [chakra]
+    # The size of the component.
+    # options: "sm" | "md" | "lg"
+    # default: "md"
+    size: Var[str]
+
+    # Sets the tabIndex attribute on the input
+    tab_index: Var[int]
+
+    # Select the currently focused option when the user presses tab
+    tab_selects_value: Var[bool]
+
+    # [chakra]
+    # Variant of multi-select tags
+    # options: "subtle" | "solid" | "outline"
+    # default: "subtle"
+    tag_variant: Var[str]
+
+    # Remove all non-essential styles
+    unstyled: Var[bool]
+
+    # [chakra]
+    # If this prop is passed,
+    # the dropdown indicator at the right of the component will be styled
+    # in the same way the original Chakra Select component is styled,
+    # instead of being styled as an InputRightAddon.
+    # The original purpose of styling it as an addon
+    # was to create a visual separation between the dropdown indicator
+    # and the button for clearing the selected options.
+    # However, as this button only appears when isMulti is passed,
+    # using this style could make more sense for a single select.
+    # default: False
+    use_basic_style: Var[bool]
+
+    # [chakra]
+    # The variant of the Select. If no variant is passed,
+    # it will default to defaultProps.variant from the theme for Chakra's Input component.
+    # If your component theme for Input is not modified, it will be outline.
+    # options: "outline" | "filled" | "flushed" | "unstyled"
+    # default: "outline"
+    variant: Var[str]
+
+    # How the options should be displayed in the menu.
+    menu_position: Var[str] = "fixed"  # type: ignore
+
+    def get_controlled_triggers(self) -> Dict[str, Var]:
+        """Get the event triggers that pass the component's value to the handler.
+
+        Returns:
+            A dict mapping the event trigger to the var that is passed to the handler.
+        """
+        # A normal select returns the value.
+        value = EVENT_ARG.value
+
+        # Multi-select returns a list of values.
+        if self.is_multi:
+            value = Var.create_safe(f"{EVENT_ARG}.map(e => e.value)", is_local=True)
+        return {"on_change": value}
+
+    @classmethod
+    def get_initial_props(cls) -> Set[str]:
+        """Get the initial props to set for the component.
+
+        Returns:
+            The initial props to set.
+        """
+        return super().get_initial_props() | {"is_multi"}
+
+    @classmethod
+    def create(
+        cls, options: List[Union[Option, str, int, float, bool]], **props
+    ) -> Component:
+        """Takes a list of options and additional properties, checks if each option is an
+        instance of Option, and returns a Select component with the given options and
+        properties. No children allowed.
+
+        Args:
+            options (List[Option | str | int | float | bool]): A list of values.
+            **props: Additional properties to be passed to the Select component.
+
+        Returns:
+            The `create` method is returning an instance of the `Select` class.
+        """
+        converted_options: List[Option] = []
+        for option in options:
+            if not isinstance(option, Option):
+                converted_options.append(Option(label=str(option), value=option))
+            else:
+                converted_options.append(option)
+        props["options"] = [o.dict() for o in converted_options]
+        return super().create(*[], **props)

+ 68 - 304
pynecone/components/forms/select.py

@@ -1,346 +1,110 @@
-"""Provides a feature-rich Select and some (not all) related components."""
+"""A select component."""
 
-from typing import Any, Dict, List, Optional, Set, Union
+from typing import Any, Dict, List
 
-from pynecone.base import Base
-from pynecone.components.component import Component
-from pynecone.event import EVENT_ARG
+from pynecone.components.component import EVENT_ARG, Component
+from pynecone.components.layout.foreach import Foreach
+from pynecone.components.libs.chakra import ChakraComponent
+from pynecone.components.typography.text import Text
+from pynecone.utils import types
 from pynecone.vars import Var
 
 
-class Option(Base):
-    """An option component for the chakra-react-select Select."""
+class Select(ChakraComponent):
+    """Select component is a component that allows users pick a value from predefined options. Ideally, it should be used when there are more than 5 options, otherwise you might consider using a radio group instead."""
 
-    # What is displayed to the user
-    label: str
-
-    # The value of the option, must be serializable
-    value: Any
-
-    # the variant of the option tag
-    variant: Optional[str] = None
-
-    # [not working yet]
-    # Whether the option is disabled
-    # is_disabled: Optional[bool] = None
-
-    # [not working yet]
-    # The visual color appearance of the component
-    # options: "whiteAlpha" | "blackAlpha" | "gray" | "red" |
-    #  "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" |
-    #  "purple" | "pink" | "linkedin" | "facebook" | "messenger" |
-    #  "whatsapp" | "twitter" | "telegram"
-    # default: "gray"
-    # color_scheme: Optional[str] = None
-
-    # [not working yet]
-    # The icon of the option tag
-    # icon: Optional[str] = None
-
-
-class Select(Component):
-    """The default chakra-react-select Select component.
-    Not every available prop is listed here,
-    for a complete overview check the react-select/chakra-react-select docs.
-    Props added by chakra-react-select are marked with "[chakra]".
-    """
-
-    library = "chakra-react-select"
     tag = "Select"
 
-    # Focus the control when it is mounted
-    auto_focus: Var[bool]
-
-    # Remove the currently focused option when the user presses backspace
-    #  when Select isClearable or isMulti
-    backspace_removes_value: Var[bool]
-
-    # Remove focus from the input when the user selects an option
-    # (handy for dismissing the keyboard on touch devices)
-    blur_input_on_select: Var[bool]
-
-    # When the user reaches the top/bottom of the menu,
-    # prevent scroll on the scroll-parent
-    capture_menu_scroll: Var[bool]
-
-    # [chakra]
-    # To use the chakraStyles prop, first,
-    # check the documentation for the original styles prop from the react-select docs.
-    # This package offers an identical API for the chakraStyles prop, however,
-    # the provided and output style objects use Chakra's sx prop
-    # instead of the default emotion styles the original package offers.
-    # This allows you to both use the shorthand styling props you'd normally use
-    # to style Chakra components, as well as tokens from your theme such as named colors.
-    # All of the style keys offered in the original package can be used in the chakraStyles prop
-    # except for menuPortal. Along with some other caveats, this is explained below.
-    # Most of the components rendered by this package use the basic Chakra <Box /> component with a few exceptions.
-    # Here are the style keys offered and the corresponding Chakra component that is rendered:
-    # - clearIndicator - Box (uses theme styles for Chakra's CloseButton)
-    # - container - Box
-    # - control - Box (uses theme styles for Chakra's Input)
-    # - dropdownIndicator - Box (uses theme styles for Chrakra's InputRightAddon)
-    # - downChevron - Icon
-    # - crossIcon - Icon
-    # - group - Box
-    # - groupHeading - Box (uses theme styles for Chakra's Menu group title)
-    # - indicatorsContainer - Box
-    # - indicatorSeparator - Divider
-    # - input - chakra.input (wrapped in a Box)
-    # - inputContainer - Box
-    # - loadingIndicator - Spinner
-    # - loadingMessage - Box
-    # - menu - Box
-    # - menuList - Box (uses theme styles for Chakra's Menu)
-    # - multiValue - chakra.span (uses theme styles for Chakra's Tag)
-    # - multiValueLabel - chakra.span (uses theme styles for Chakra's TagLabel)
-    # - multiValueRemove - Box (uses theme styles for Chakra's TagCloseButton)
-    # - noOptionsMessage - Box
-    # - option - Box (uses theme styles for Chakra's MenuItem)
-    # - placeholder - Box
-    # - singleValue - Box
-    # - valueContainer - Box
-    chakra_styles: Var[str]
-
-    # Close the select menu when the user selects an option
-    close_menu_on_select: Var[bool]
-
-    # If true, close the select menu when the user scrolls the document/body.
-    close_menu_on_scroll: Var[bool]
-
-    # [chakra]
-    # The visual color appearance of the component
-    # options: "whiteAlpha" | "blackAlpha" | "gray" | "red" |
-    #  "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" |
-    #  "purple" | "pink" | "linkedin" | "facebook" | "messenger" |
-    #  "whatsapp" | "twitter" | "telegram"
-    # default: "gray"
-    color_scheme: Var[str]
+    # State var to bind the select.
+    value: Var[str]
 
-    # This complex object includes all the compositional components
-    # that are used in react-select. If you wish to overwrite a component,
-    # pass in an object with the appropriate namespace.
-    # If you only wish to restyle a component,
-    # we recommend using the styles prop instead.
-    components: Var[Dict[str, Component]]
+    # The default value of the select.
+    default_value: Var[str]
 
-    # Whether the value of the select, e.g. SingleValue,
-    # should be displayed in the control.
-    control_should_render_value: Var[bool]
+    # The placeholder text.
+    placeholder: Var[str]
 
-    # Delimiter used to join multiple values into a single HTML Input value
-    delimiter: Var[str]
-
-    # [chakra]
-    # Colors the component border with the given chakra color string on error state
-    # default: "red.500"
+    # The border color when the select is invalid.
     error_border_color: Var[str]
 
-    # Clear all values when the user presses escape AND the menu is closed
-    escape_clears_value: Var[bool]
-
-    # [chakra]
-    # Colors the component border with the given chakra color string on focus
-    # default: "blue.500"
+    # The border color when the select is focused.
     focus_border_color: Var[str]
 
-    # Sets the form attribute on the input
-    form: Var[str]
-
-    # Hide the selected option from the menu
-    hide_selected_options: Var[bool]
-
-    # The id to set on the SelectContainer component.
-    # id: Var[str]
-
-    # The value of the search input
-    input_value: Var[str]
-
-    # The id of the search input
-    input_id: Var[str]
-
-    # Is the select value clearable
-    is_clearable: Var[bool]
-
-    # Is the select disabled
+    # If true, the select will be disabled.
     is_disabled: Var[bool]
 
-    # [chakra]
-    # Style component in the chakra invalid style
-    # default: False
+    # If true, the form control will be invalid. This has 2 side effects: - The FormLabel and FormErrorIcon will have `data-invalid` set to true - The form element (e.g, Input) will have `aria-invalid` set to true
     is_invalid: Var[bool]
 
-    # Support multiple selected options
-    is_multi: Var[bool]
-
-    # [chakra]
-    # Style component as disabled (chakra style)
-    # default: False
+    # If true, the form control will be readonly
     is_read_only: Var[bool]
 
-    # Is the select direction right-to-left
-    is_rtl: Var[bool]
-
-    # Whether to enable search functionality
-    is_searchable: Var[bool]
-
-    # Minimum height of the menu before flipping
-    min_menu_height: Var[int]
-
-    # Maximum height of the menu before scrolling
-    max_menu_height: Var[int]
-
-    # Default placement of the menu in relation to the control.
-    # 'auto' will flip when there isn't enough space below the control.
-    # options: "bottom" | "auto" | "top"
-    menu_placement: Var[str]
-
-    # The CSS position value of the menu,
-    #  when "fixed" extra layout management is required
-    # options: "absolute" | "fixed"
-    menu_position: Var[str]
+    # If true, the form control will be required. This has 2 side effects: - The FormLabel will show a required indicator - The form element (e.g, Input) will have `aria-required` set to true
+    is_required: Var[bool]
 
-    # Whether to block scroll events when the menu is open
-    menu_should_block_scroll: Var[bool]
-
-    # Whether the menu should be scrolled into view when it opens
-    menu_should_scroll_into_view: Var[bool]
-
-    # Name of the HTML Input (optional - without this, no input will be rendered)
-    name: Var[str]
-
-    # Allows control of whether the menu is opened when the Select is focused
-    open_menu_on_focus: Var[bool]
-
-    # Allows control of whether the menu is opened when the Select is clicked
-    open_menu_on_click: Var[bool]
-
-    # Array of options that populate the select menu
-    options: Var[List[Dict]]
-
-    # Number of options to jump in menu when page{up|down} keys are used
-    page_size: Var[int]
-
-    # Placeholder for the select value
-    placeholder: Var[Optional[str]]
-
-    # Marks the value-holding input as required for form validation
-    required: Var[bool]
-
-    # [chakra]
-    # If you choose to stick with the default selectedOptionStyle="color",
-    # you have one additional styling option.
-    # If you do not like the default of blue for the highlight color,
-    # you can pass the selectedOptionColorScheme prop to change it.
-    # This prop will accept any named color from your theme's color palette,
-    # and it will use the 500 value in light mode or the 300 value in dark mode.
-    # This prop can only be used for named colors from your theme, not arbitrary hex/rgb colors.
-    # If you would like to use a specific color for the background that's not a part of your theme,
-    # use the chakraStyles prop to customize it.
-    # default: "blue"
-    selected_option_color_scheme: Var[str]
-
-    # [chakra]
-    # The default option "color" will style a selected option
-    # similar to how react-select does it,
-    # by highlighting the selected option in the color blue.
-    # Alternatively, if you pass "check" for the value,
-    # the selected option will be styled like the Chakra UI Menu component
-    # and include a check icon next to the selected option(s).
-    # If is_multi and selected_option_style="check" are passed,
-    # space will only be added for the check marks
-    # if hide_selected_options=False is also passed.
-    # options: "color" | "check"
-    # default: "color"
-    selected_option_style: Var[str]
-
-    # [chakra]
-    # The size of the component.
-    # options: "sm" | "md" | "lg"
-    # default: "md"
-    size: Var[str]
-
-    # Sets the tabIndex attribute on the input
-    tab_index: Var[int]
-
-    # Select the currently focused option when the user presses tab
-    tab_selects_value: Var[bool]
-
-    # [chakra]
-    # Variant of multi-select tags
-    # options: "subtle" | "solid" | "outline"
-    # default: "subtle"
-    tag_variant: Var[str]
-
-    # Remove all non-essential styles
-    unstyled: Var[bool]
-
-    # [chakra]
-    # If this prop is passed,
-    # the dropdown indicator at the right of the component will be styled
-    # in the same way the original Chakra Select component is styled,
-    # instead of being styled as an InputRightAddon.
-    # The original purpose of styling it as an addon
-    # was to create a visual separation between the dropdown indicator
-    # and the button for clearing the selected options.
-    # However, as this button only appears when isMulti is passed,
-    # using this style could make more sense for a single select.
-    # default: False
-    use_basic_style: Var[bool]
-
-    # [chakra]
-    # The variant of the Select. If no variant is passed,
-    # it will default to defaultProps.variant from the theme for Chakra's Input component.
-    # If your component theme for Input is not modified, it will be outline.
-    # options: "outline" | "filled" | "flushed" | "unstyled"
-    # default: "outline"
+    # "outline" | "filled" | "flushed" | "unstyled"
     variant: Var[str]
 
-    # How the options should be displayed in the menu.
-    menu_position: Var[str] = "fixed"  # type: ignore
+    # The size of the select.
+    size: Var[str]
 
-    def get_controlled_triggers(self) -> Dict[str, Var]:
+    @classmethod
+    def get_controlled_triggers(cls) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
 
         Returns:
             A dict mapping the event trigger to the var that is passed to the handler.
         """
-        # A normal select returns the value.
-        value = EVENT_ARG.value
-
-        # Multi-select returns a list of values.
-        if self.is_multi:
-            value = Var.create_safe(f"{EVENT_ARG}.map(e => e.value)", is_local=True)
-        return {"on_change": value}
+        return {
+            "on_change": EVENT_ARG.target.value,
+        }
 
     @classmethod
-    def get_initial_props(cls) -> Set[str]:
-        """Get the initial props to set for the component.
+    def create(cls, *children, **props) -> Component:
+        """Create a select component.
+
+        If a list is provided as the first children, a default component
+        will be created for each item in the list.
+
+        Args:
+            *children: The children of the component.
+            **props: The props of the component.
 
         Returns:
-            The initial props to set.
+            The component.
         """
-        return super().get_initial_props() | {"is_multi"}
+        if len(children) == 1 and isinstance(children[0], list):
+            children = [Option.create(child) for child in children[0]]
+        if (
+            len(children) == 1
+            and isinstance(children[0], Var)
+            and types._issubclass(children[0].type_, List)
+        ):
+            children = [Foreach.create(children[0], lambda item: Option.create(item))]
+        return super().create(*children, **props)
+
+
+class Option(Text):
+    """A select option."""
+
+    tag = "option"
+
+    value: Var[Any]
 
     @classmethod
-    def create(
-        cls, options: List[Union[Option, str, int, float, bool]], **props
-    ) -> Component:
-        """Takes a list of options and additional properties, checks if each option is an
-        instance of Option, and returns a Select component with the given options and
-        properties. No children allowed.
+    def create(cls, *children, **props) -> Component:
+        """Create a select option component.
+
+        By default, the value of the option is the text of the option.
 
         Args:
-            options (List[Option | str | int | float | bool]): A list of values.
-            **props: Additional properties to be passed to the Select component.
+            *children: The children of the component.
+            **props: The props of the component.
 
         Returns:
-            The `create` method is returning an instance of the `Select` class.
+            The component.
         """
-        converted_options: List[Option] = []
-        for option in options:
-            if not isinstance(option, Option):
-                converted_options.append(Option(label=str(option), value=option))
-            else:
-                converted_options.append(option)
-        props["options"] = [o.dict() for o in converted_options]
-        return super().create(*[], **props)
+        if "value" not in props:
+            assert len(children) == 1
+            props["value"] = children[0]
+        return super().create(*children, **props)