diff --git a/src/components/form/Form.tsx b/src/components/form/Form.tsx index bdb5129..ceaf1d3 100644 --- a/src/components/form/Form.tsx +++ b/src/components/form/Form.tsx @@ -14,11 +14,13 @@ import ValidatedForm, { useFormValidation } from "./ValidatedForm"; import FormField from "./FormField"; import PasswordField from "./PasswordField"; import NumberField from "./NumberField"; +import FormDropdown from "./FormDropdown"; export { type ValidatedFormProps } from "./ValidatedForm"; export { type FormFieldProps } from "./FormField"; export { type PasswordFieldProps } from "./PasswordField"; export { type NumberFieldProps } from "./NumberField"; +export { type FormDropdownProps, type DropdownOption } from "./FormDropdown"; export type FormProps = Omit, "ref"> & IComponentBaseProps & { @@ -116,4 +118,5 @@ export default Object.assign(Form, { Field: FormField, Password: PasswordField, Number: NumberField, + Dropdown: FormDropdown, }); diff --git a/src/components/form/FormDropdown.tsx b/src/components/form/FormDropdown.tsx new file mode 100644 index 0000000..155937e --- /dev/null +++ b/src/components/form/FormDropdown.tsx @@ -0,0 +1,142 @@ +import { Component, Show, createMemo, splitProps, JSX, For } from "solid-js"; +import { twMerge } from "tailwind-merge"; +import { useFormValidation } from "./ValidatedForm"; +import Form from "./Form"; +import Dropdown from "../dropdown"; +import Icon from "../icon"; + +export interface DropdownOption { + value: string; + label: string; +} + +export interface FormDropdownProps { + label: string; + name: string; + options: DropdownOption[]; + value?: string; + placeholder?: string; + required?: boolean; + labelClass?: string; + errorClass?: string; + containerClass?: string; + description?: string; + descriptionClass?: string; + class?: string; + className?: string; + icon?: JSX.Element; + onChange?: (value: string) => void; + disabled?: boolean; +} + +export const FormDropdown: Component = (props) => { + const { errors, touched, setData } = useFormValidation(); + + const [local, _] = splitProps(props, [ + "label", + "name", + "options", + "value", + "placeholder", + "required", + "labelClass", + "errorClass", + "containerClass", + "description", + "descriptionClass", + "class", + "className", + "icon", + "onChange", + "disabled", + ]); + + const containerClasses = createMemo(() => + twMerge("flex flex-col gap-2", local.containerClass) + ); + + const descriptionClasses = createMemo(() => + twMerge("text-sm text-base-content/70", local.descriptionClass) + ); + + const errorClasses = createMemo(() => + twMerge("text-error text-sm", local.errorClass) + ); + + const dropdownClasses = createMemo(() => + twMerge( + "border border-base-300 rounded px-4 py-2 w-full flex justify-between items-center", + local.disabled ? "opacity-70 cursor-not-allowed" : "", + local.class, + local.className + ) + ); + + const hasError = createMemo( + () => touched(local.name) && !!errors(local.name) + ); + + const selectedOption = createMemo(() => + local.options.find((opt) => opt.value === local.value) + ); + + const handleSelect = (value: string) => { + setData(local.name, value); + if (local.onChange) { + local.onChange(value); + } + }; + + return ( +
+ + {local.required && *} + + + {local.description && ( +

{local.description}

+ )} + + + + + {selectedOption()?.label || local.placeholder || "Select an option"} + + {local.icon || ( + + + + )} + + + + {(option) => ( + handleSelect(option.value)} + class={local.value === option.value ? "bg-base-200" : ""} + > + {option.label} + + )} + + + + + +

{errors(local.name)}

+
+
+ ); +}; + +export default FormDropdown; diff --git a/src/components/form/index.ts b/src/components/form/index.ts index f187753..828d3ad 100644 --- a/src/components/form/index.ts +++ b/src/components/form/index.ts @@ -8,3 +8,8 @@ export { useFormValidation, } from "./Form"; export { type LabelProps } from "./Label"; +export { + default as FormDropdown, + type FormDropdownProps, + type DropdownOption, +} from "./FormDropdown";