Migrate selection components to Zod props

This commit is contained in:
Mats Bosson 2026-03-29 20:39:35 +07:00
parent 6dd06986cc
commit fdd12f95c6
13 changed files with 223 additions and 17 deletions

View File

@ -0,0 +1,42 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const ComboboxRootPropsSchema = z.object({
value: z.string().optional().describe("Controlled selected value"),
defaultValue: z.string().optional().describe("Initial selected value (uncontrolled)"),
inputValue: z.string().optional().describe("Controlled input text value"),
name: z.string().optional().describe("Form field name"),
placeholder: z.string().optional().describe("Placeholder text shown in the input when empty"),
disabled: z.boolean().optional().describe("Whether the combobox is disabled"),
required: z.boolean().optional().describe("Whether selection is required"),
allowCustomValue: z.boolean().optional().describe("Allow selecting a value not in the items list"),
});
export interface ComboboxRootProps extends z.infer<typeof ComboboxRootPropsSchema> {
items: string[];
onValueChange?: (value: string) => void;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
onInputChange?: (value: string) => void;
getLabel?: (value: string) => string;
children: JSX.Element;
}
export const ComboboxItemPropsSchema = z.object({
value: z.string().describe("The value this item represents"),
disabled: z.boolean().optional().describe("Whether this item is disabled"),
textValue: z.string().optional().describe("Accessible text used for typeahead matching"),
});
export interface ComboboxItemProps extends z.infer<typeof ComboboxItemPropsSchema>, JSX.HTMLAttributes<HTMLDivElement> {
children?: JSX.Element;
}
export const ComboboxMeta: ComponentMeta = {
name: "Combobox",
description: "Searchable select that filters options as the user types, with keyboard navigation",
parts: ["Root", "Input", "Trigger", "Portal", "Content", "Listbox", "Item", "ItemLabel", "ItemIndicator", "Group", "GroupLabel"] as const,
requiredParts: ["Root", "Input", "Content", "Item"] as const,
} as const;

View File

@ -7,3 +7,5 @@ export { ComboboxEmpty } from "./combobox-empty";
export { ComboboxGroup } from "./combobox-group"; export { ComboboxGroup } from "./combobox-group";
export { ComboboxGroupLabel } from "./combobox-group-label"; export { ComboboxGroupLabel } from "./combobox-group-label";
export { useComboboxContext } from "./combobox-context"; export { useComboboxContext } from "./combobox-context";
export { ComboboxRootPropsSchema, ComboboxItemPropsSchema, ComboboxMeta } from "./combobox.props";
export type { ComboboxRootProps, ComboboxItemProps } from "./combobox.props";

View File

@ -0,0 +1,32 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const DropdownMenuRootPropsSchema = z.object({
open: z.boolean().optional().describe("Controlled open state"),
defaultOpen: z.boolean().optional().describe("Initial open state (uncontrolled)"),
});
export interface DropdownMenuRootProps extends z.infer<typeof DropdownMenuRootPropsSchema> {
onOpenChange?: (open: boolean) => void;
children: JSX.Element;
}
export const DropdownMenuItemPropsSchema = z.object({
disabled: z.boolean().optional().describe("Whether this item is disabled"),
textValue: z.string().optional().describe("Accessible text used for typeahead matching"),
closeOnSelect: z.boolean().optional().describe("Whether the menu closes when this item is selected"),
});
export interface DropdownMenuItemProps extends z.infer<typeof DropdownMenuItemPropsSchema>, JSX.HTMLAttributes<HTMLDivElement> {
value: string;
onSelect?: () => void;
children?: JSX.Element;
}
export const DropdownMenuMeta: ComponentMeta = {
name: "DropdownMenu",
description: "Menu of actions triggered by a button, with keyboard navigation, grouping, and sub-menus",
parts: ["Root", "Trigger", "Portal", "Content", "Item", "Group", "GroupLabel", "Separator", "Sub", "SubTrigger", "SubContent"] as const,
requiredParts: ["Root", "Trigger", "Content"] as const,
} as const;

View File

@ -9,3 +9,5 @@ export { DropdownMenuCheckboxItem } from "./dropdown-menu-checkbox-item";
export { DropdownMenuRadioGroup } from "./dropdown-menu-radio-group"; export { DropdownMenuRadioGroup } from "./dropdown-menu-radio-group";
export { DropdownMenuRadioItem } from "./dropdown-menu-radio-item"; export { DropdownMenuRadioItem } from "./dropdown-menu-radio-item";
export { useDropdownMenuContext } from "./dropdown-menu-context"; export { useDropdownMenuContext } from "./dropdown-menu-context";
export { DropdownMenuRootPropsSchema, DropdownMenuItemPropsSchema, DropdownMenuMeta } from "./dropdown-menu.props";
export type { DropdownMenuRootProps, DropdownMenuItemProps } from "./dropdown-menu.props";

View File

@ -11,8 +11,8 @@ export const Listbox = Object.assign(ListboxRoot, {
useContext: useListboxContext, useContext: useListboxContext,
}); });
export type { ListboxRootProps } from "./listbox-root";
export type { ListboxItemProps } from "./listbox-item";
export type { ListboxGroupProps } from "./listbox-group"; export type { ListboxGroupProps } from "./listbox-group";
export type { ListboxGroupLabelProps } from "./listbox-group-label"; export type { ListboxGroupLabelProps } from "./listbox-group-label";
export type { ListboxContextValue } from "./listbox-context"; export type { ListboxContextValue } from "./listbox-context";
export { ListboxRootPropsSchema, ListboxItemPropsSchema, ListboxMeta } from "./listbox.props";
export type { ListboxRootProps, ListboxItemProps } from "./listbox.props";

View File

@ -0,0 +1,43 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const ListboxRootPropsSchema = z.object({
value: z.union([z.string(), z.array(z.string())]).optional().describe("Controlled selected value or values"),
defaultValue: z.union([z.string(), z.array(z.string())]).optional().describe("Initial selected value or values (uncontrolled)"),
multiple: z.boolean().optional().describe("Whether multiple items can be selected"),
disabled: z.boolean().optional().describe("Whether the listbox is disabled"),
orientation: z.enum(["horizontal", "vertical"]).optional().describe("Navigation orientation. Defaults to vertical"),
});
export interface ListboxRootProps extends JSX.HTMLAttributes<HTMLDivElement> {
items: string[];
value?: string;
defaultValue?: string;
onValueChange?: (value: string) => void;
selectionMode?: "single" | "multiple";
values?: string[];
defaultValues?: string[];
onValuesChange?: (values: string[]) => void;
orientation?: "vertical" | "horizontal";
loop?: boolean;
getLabel?: (value: string) => string;
disabled?: boolean;
children: JSX.Element;
}
export const ListboxItemPropsSchema = z.object({
value: z.string().describe("The value this item represents"),
disabled: z.boolean().optional().describe("Whether this item is disabled"),
});
export interface ListboxItemProps extends z.infer<typeof ListboxItemPropsSchema>, JSX.HTMLAttributes<HTMLDivElement> {
children?: JSX.Element;
}
export const ListboxMeta: ComponentMeta = {
name: "Listbox",
description: "Inline list of selectable options with keyboard navigation, not in a dropdown",
parts: ["Root", "Item", "ItemLabel", "ItemIndicator", "Group", "GroupLabel"] as const,
requiredParts: ["Root", "Item"] as const,
} as const;

View File

@ -1,17 +1,6 @@
import { usePaginationContext } from "./pagination-context"; export { Pagination, PaginationRoot } from "./pagination-root";
import { PaginationEllipsis } from "./pagination-ellipsis"; export { PaginationRootPropsSchema, PaginationMeta } from "./pagination.props";
import { PaginationItems } from "./pagination-items"; export type { PaginationRootProps } from "./pagination.props";
import { PaginationNext } from "./pagination-next";
import { PaginationPrevious } from "./pagination-previous";
import { PaginationRoot } from "./pagination-root";
export const Pagination = Object.assign(PaginationRoot, {
Previous: PaginationPrevious,
Next: PaginationNext,
Items: PaginationItems,
Ellipsis: PaginationEllipsis,
useContext: usePaginationContext,
});
export type { PaginationRootProps } from "./pagination-root";
export type { PaginationPreviousProps } from "./pagination-previous"; export type { PaginationPreviousProps } from "./pagination-previous";
export type { PaginationNextProps } from "./pagination-next"; export type { PaginationNextProps } from "./pagination-next";
export type { PaginationItemsProps, PaginationItemData } from "./pagination-items"; export type { PaginationItemsProps, PaginationItemData } from "./pagination-items";

View File

@ -1,6 +1,11 @@
import type { JSX } from "solid-js"; import type { JSX } from "solid-js";
import { splitProps } from "solid-js"; import { splitProps } from "solid-js";
import { PaginationContextProvider, type PaginationContextValue } from "./pagination-context"; import { PaginationContextProvider, type PaginationContextValue } from "./pagination-context";
import { PaginationEllipsis } from "./pagination-ellipsis";
import { PaginationItems } from "./pagination-items";
import { PaginationNext } from "./pagination-next";
import { PaginationPrevious } from "./pagination-previous";
import { usePaginationContext } from "./pagination-context";
/** Props for the Pagination root component. */ /** Props for the Pagination root component. */
export interface PaginationRootProps extends JSX.HTMLAttributes<HTMLElement> { export interface PaginationRootProps extends JSX.HTMLAttributes<HTMLElement> {
@ -42,3 +47,12 @@ export function PaginationRoot(props: PaginationRootProps): JSX.Element {
</PaginationContextProvider> </PaginationContextProvider>
); );
} }
/** Compound Pagination component with all sub-components as static properties. */
export const Pagination = Object.assign(PaginationRoot, {
Previous: PaginationPrevious,
Next: PaginationNext,
Items: PaginationItems,
Ellipsis: PaginationEllipsis,
useContext: usePaginationContext,
});

View File

@ -0,0 +1,23 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const PaginationRootPropsSchema = z.object({
page: z.number().optional().describe("Current page number (1-based)"),
defaultPage: z.number().optional().describe("Initial page number (uncontrolled)"),
count: z.number().describe("Total number of pages"),
siblingCount: z.number().optional().describe("Number of sibling pages shown around the current page. Defaults to 1"),
disabled: z.boolean().optional().describe("Whether the pagination controls are disabled"),
});
export interface PaginationRootProps extends z.infer<typeof PaginationRootPropsSchema>, JSX.HTMLAttributes<HTMLElement> {
onPageChange?: (page: number) => void;
children?: JSX.Element;
}
export const PaginationMeta: ComponentMeta = {
name: "Pagination",
description: "Navigation for paginated content with page numbers, previous/next controls",
parts: ["Root", "Previous", "Next", "Items", "Item", "Ellipsis"] as const,
requiredParts: ["Root"] as const,
} as const;

View File

@ -7,3 +7,5 @@ export { SelectGroup } from "./select-group";
export { SelectGroupLabel } from "./select-group-label"; export { SelectGroupLabel } from "./select-group-label";
export { SelectHiddenSelect } from "./select-hidden-select"; export { SelectHiddenSelect } from "./select-hidden-select";
export { useSelectContext } from "./select-context"; export { useSelectContext } from "./select-context";
export { SelectRootPropsSchema, SelectItemPropsSchema, SelectMeta } from "./select.props";
export type { SelectRootProps, SelectItemProps } from "./select.props";

View File

@ -0,0 +1,39 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const SelectRootPropsSchema = z.object({
value: z.string().optional().describe("Controlled selected value"),
defaultValue: z.string().optional().describe("Initial selected value (uncontrolled)"),
name: z.string().optional().describe("Form field name for hidden input"),
placeholder: z.string().optional().describe("Placeholder text shown when no value is selected"),
disabled: z.boolean().optional().describe("Whether the select is disabled"),
required: z.boolean().optional().describe("Whether selection is required"),
});
export interface SelectRootProps extends z.infer<typeof SelectRootPropsSchema> {
items: string[];
onValueChange?: (value: string) => void;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
getLabel?: (value: string) => string;
children: JSX.Element;
}
export const SelectItemPropsSchema = z.object({
value: z.string().describe("The value this item represents"),
disabled: z.boolean().optional().describe("Whether this item is disabled"),
textValue: z.string().optional().describe("Accessible text used for typeahead matching"),
});
export interface SelectItemProps extends z.infer<typeof SelectItemPropsSchema>, JSX.HTMLAttributes<HTMLDivElement> {
children?: JSX.Element;
}
export const SelectMeta: ComponentMeta = {
name: "Select",
description: "Dropdown for selecting a single option from a list, with keyboard navigation and typeahead",
parts: ["Root", "Trigger", "Value", "Portal", "Content", "Listbox", "Item", "ItemLabel", "ItemIndicator", "Group", "GroupLabel"] as const,
requiredParts: ["Root", "Trigger", "Content", "Item"] as const,
} as const;

View File

@ -1,2 +1,3 @@
export { Separator } from "./separator"; export { Separator } from "./separator";
export type { SeparatorProps } from "./separator"; export { SeparatorPropsSchema, SeparatorMeta } from "./separator.props";
export type { SeparatorProps } from "./separator.props";

View File

@ -0,0 +1,17 @@
import { z } from "zod/v4";
import type { JSX } from "solid-js";
import type { ComponentMeta } from "../../meta";
export const SeparatorPropsSchema = z.object({
orientation: z.enum(["horizontal", "vertical"]).optional().describe("Visual and ARIA orientation. Defaults to horizontal"),
decorative: z.boolean().optional().describe("When true, renders role=none (purely visual, not announced). Defaults to false"),
});
export interface SeparatorProps extends z.infer<typeof SeparatorPropsSchema>, JSX.HTMLAttributes<HTMLHRElement> {}
export const SeparatorMeta: ComponentMeta = {
name: "Separator",
description: "Visual divider between content sections or menu items",
parts: ["Separator"] as const,
requiredParts: ["Separator"] as const,
} as const;