diff --git a/apps/docs/src/examples/select.tsx b/apps/docs/src/examples/select.tsx index 24e0d152..c1825bf9 100644 --- a/apps/docs/src/examples/select.tsx +++ b/apps/docs/src/examples/select.tsx @@ -1,4 +1,4 @@ -import { As, Select } from "@kobalte/core"; +import { Select } from "@kobalte/core"; import { createVirtualizer } from "@tanstack/solid-virtual"; import { For, createSignal } from "solid-js"; @@ -377,46 +377,44 @@ export function MultipleSelectionExample() { - - class={style.select__value}> - {(state) => ( - <> -
- - {(option) => ( - e.stopPropagation()} + class={style.select__value}> + {(state) => ( + <> +
+ + {(option) => ( + e.stopPropagation()} + > + {option} + - - )} - -
- - - )} - - - - - + + +
+ )} +
+
+ + + )} + + + +
diff --git a/apps/docs/src/routes/docs/core.tsx b/apps/docs/src/routes/docs/core.tsx index 43de7b31..b5906e2c 100644 --- a/apps/docs/src/routes/docs/core.tsx +++ b/apps/docs/src/routes/docs/core.tsx @@ -26,11 +26,11 @@ const CORE_NAV_SECTIONS: NavSection[] = [ { title: "Polymorphism", href: "/docs/core/overview/polymorphism", + status: "updated", }, { title: "Server side rendering", href: "/docs/core/overview/ssr", - status: "updated", }, ], }, diff --git a/apps/docs/src/routes/docs/core/components/select.mdx b/apps/docs/src/routes/docs/core/components/select.mdx index 97e69ff4..dd9858fa 100644 --- a/apps/docs/src/routes/docs/core/components/select.mdx +++ b/apps/docs/src/routes/docs/core/components/select.mdx @@ -652,7 +652,7 @@ The `multiple` prop can be used to create a select that allow multi-selection. I The `Select.Value` children _render prop_ expose an array of selected options, and two method for removing an option from the selection and clear the selection. -Additionally, the example below uses the `asChild` pattern to render a `div` for the `Select.Trigger` since HTML button can't contain interactive elements according to the [W3C](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element). +Additionally, the example below uses the `as` prop to render a `div` for the `Select.Trigger` since HTML button can't contain interactive elements according to the [W3C](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element). @@ -681,33 +681,31 @@ function MultipleSelectionExample() { )} > - - - > - {state => ( - <> -
- - {option => ( - e.stopPropagation()}> - {option} - - - )} - -
- - - )} - - - - -
+ + > + {state => ( + <> +
+ + {option => ( + e.stopPropagation()}> + {option} + + + )} + +
+ + + )} + + + +
diff --git a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx index 64eeb3e3..d4181665 100644 --- a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx +++ b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx @@ -1,6 +1,6 @@ # Polymorphism -All component parts that render a DOM element have an `as` and a `asChild` prop. +All component parts that render a DOM element have an `as` prop. ## The `as` prop @@ -30,19 +30,20 @@ function App() { } ``` -## The `asChild` prop +## The `as` prop callback -For more advanced use cases the `asChild` prop and the `` component can be used. -The main reason to use `asChild` over the `as` prop is being able to set props without interfering with Kobalte. +For more advanced use cases the `as` prop can accept a callback. +The main reason to use a callback over the normal `as` prop is being able to set props without interfering with Kobalte. -When using this pattern the following rules apply to the component with the `asChild` prop, and it's child `` component: +When using this pattern the following rules apply to the callback: -- CSS classes are combined. -- Props are combined, if same attribute exists the one from `` win. -- Event handlers are chained, the one from `` get called first. +- You must spread the props forwarded to your callback onto your node/component. +- Custom props are passed as is from the parent. +- Kobalte options are not passed to the callback, only the resulting html attributes. +- You should set your event handlers on the parent and not inside your callback. ```tsx {15} -import { As, Tabs as KTabs } from "@kobalte/core"; +import { Tabs as KTabs } from "@kobalte/core"; import { MyCustomButton } from "./components"; function App() { @@ -55,11 +56,14 @@ function App() { {/* The `value` prop is used by Kobalte and not passed to MyCustomButton */} - - {/* The `value` prop is directly passed to MyCustomButton */} - - Custom Button Trigger - + ( + // The `value` prop is directly passed to MyCustomButton + + )} + > + Custom Button Trigger Content one @@ -67,3 +71,132 @@ function App() { ); } ``` + +You can optionally use a type helper to get the exact types passed to your callback: + +```tsx {4} +import { Tabs as KTabs, PolymorphicCallbackProps } from "@kobalte/core"; + +, + ) => ( + // The `value` prop is directly passed to MyCustomButton + + )} +> + Custom Button Trigger +; +``` + +## Event lifecycle + +Setting custom event handlers on component will call your custom handler before Kobalte's. + +## Types + +This section is mainly for library author that want to build on top of Kobalte and expose the correct types +to your end users. + +Every component that renders an HTML element has the following types: + +- `ComponentOptions` +- `ComponentCommonProps` +- `ComponentRenderProps` +- `ComponentProps` + +For example, `Tabs.Trigger` has the types `TabsTriggerOptions`, `TabsTriggerCommonProps`, +`TabsTriggerRenderProps` and `TabsTriggerProps` namespaced as `Tabs.TabsTriggerOptions`, etc. + +Components themselves accept props as `PolymorphicProps` where `T` is a generic +that extends `ValidComponent` and the `ComponentProps` of the Kobalte component. +This type allows components to accept Kobalte's props and all other props accepted by `T`. + +### `ComponentOptions` + +This type contains all custom props consumed by Kobalte, these props do not exist in HTML. +These are not passed to the HTML element nor to the `as` callback. + +### `ComponentCommonProps` + +This type contains HTML attributes optionally accepted by the Kobalte component and will +be forwarded to the rendered DOM node. These are managed by Kobalte but can be customized by the end +user. It includes attributes such as `id`, `ref`, event handlers, etc. + +### `ComponentRenderProps` + +This type extends `ComponentCommonProps` and additionally contains attributes that are passed +to the DOM node and fully managed by Kobalte. You should never assign these yourself or set them on +the Kobalte component. Modifying these props will break your component's behavior and accessibity. + +### `ComponentProps` + +This is the final type exported by components, it is equal to `ComponentOptions & Partial`. +It combines all props expected by Kobalte's component. + +### `PolymorphicProps` + +If you're writing a custom component and want to expose Kobalte's `as` prop to the end user +and keep proper typing, be sure to use `PolymorphicProps` for your props type. + +```tsx +import { Tabs as KTabs, PolymorphicProps } from "@kobalte/core"; + +// Optionally extend `KTabs.TabsTriggerProps` if you wish to +// expose Kobalte props to your end user. +interface CustomProps extends KTabs.TabsTriggerProps { + variant: "default" | "outline"; +} + +// Your generic `T` should extend ValidComponent and have a default value of the default DOM node. +function CustomTabsTrigger( + props: PolymorphicProps, +) { + // Typescript degrades typechecking when using generics, as long as we + // spread `others` to our element, we can effectively ignore them. + const [local, others] = splitProps(props as CustomProps, ["variant"]); + + return ( + + ); +} +``` + +If you also want to export exact types, you can re-export and extends component types: + +```tsx +export interface CustomTabsTriggerOptions extends KTabs.TabsTriggerOptions { + variant: "default" | "outline"; +} + +export interface CustomTabsTriggerCommonProps extends KTabs.TabsTriggerCommonProps { + // If you allow users to set classes and extend them. + //class: string; +} + +export interface CustomTabsTriggerRenderProps + extends CustomTabsTriggerCommonProps, + KTabs.TabsTriggerRenderProps { + // If you do not allow users to set classes and manage all of them. + class: string; +} + +export type CustomTabsTriggerProps = CustomTabsTriggerOptions & + Partial; + +export function CustomTabsTrigger( + props: PolymorphicProps, +) {} +``` diff --git a/package.json b/package.json index e16feb63..491da0d9 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,8 @@ "typescript": "4.9.5", "vite": "5.0.11", "vite-plugin-solid": "2.9.1", - "vitest": "1.3.1" + "vitest": "1.3.1", + "@vitest/ui": "^1.5.2" }, "packageManager": "pnpm@8.14.0" } diff --git a/packages/core/src/accordion/accordion-content.tsx b/packages/core/src/accordion/accordion-content.tsx index e0bebc3b..ceac738c 100644 --- a/packages/core/src/accordion/accordion-content.tsx +++ b/packages/core/src/accordion/accordion-content.tsx @@ -6,34 +6,64 @@ * https://github.com/adobe/react-spectrum/blob/c183944ce6a8ca1cf280a1c7b88d2ba393dd0252/packages/@react-aria/accordion/src/useAccordion.ts */ -import { OverrideComponentProps, mergeDefaultProps } from "@kobalte/utils"; -import { createEffect, onCleanup, splitProps } from "solid-js"; +import { mergeDefaultProps } from "@kobalte/utils"; +import { + Component, + ValidComponent, + createEffect, + onCleanup, + splitProps, +} from "solid-js"; import * as Collapsible from "../collapsible"; +import { PolymorphicProps } from "../polymorphic"; import { useAccordionItemContext } from "./accordion-item-context"; -export interface AccordionContentOptions - extends Collapsible.CollapsibleContentOptions {} +export interface AccordionContentOptions {} -export interface AccordionContentProps - extends OverrideComponentProps<"div", AccordionContentOptions> {} +export interface AccordionContentCommonProps + extends Collapsible.CollapsibleContentCommonProps { + id: string; +} + +export interface AccordionContentRenderProps + extends AccordionContentCommonProps, + Collapsible.CollapsibleContentRenderProps { + role: "region"; + "aria-labelledby": string | undefined; +} + +export type AccordionContentProps = AccordionContentOptions & + Partial; /** * Contains the content to be rendered when the `Accordion.Item` is expanded. */ -export function AccordionContent(props: AccordionContentProps) { +export function AccordionContent( + props: PolymorphicProps, +) { const itemContext = useAccordionItemContext(); const defaultId = itemContext.generateId("content"); - const mergedProps = mergeDefaultProps({ id: defaultId }, props); + const mergedProps = mergeDefaultProps( + { id: defaultId }, + props as AccordionContentProps, + ); - const [local, others] = splitProps(mergedProps, ["style"]); + const [local, others] = splitProps(mergedProps, ["id", "style"]); - createEffect(() => onCleanup(itemContext.registerContentId(others.id!))); + createEffect(() => onCleanup(itemContext.registerContentId(local.id))); return ( - + > + > role="region" aria-labelledby={itemContext.triggerId()} style={{ diff --git a/packages/core/src/accordion/accordion-header.tsx b/packages/core/src/accordion/accordion-header.tsx index 5d9f4cea..634040b2 100644 --- a/packages/core/src/accordion/accordion-header.tsx +++ b/packages/core/src/accordion/accordion-header.tsx @@ -1,18 +1,38 @@ import { OverrideComponentProps } from "@kobalte/utils"; +import { ValidComponent } from "solid-js"; -import { useCollapsibleContext } from "../collapsible/collapsible-context"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { + CollapsibleDataSet, + useCollapsibleContext, +} from "../collapsible/collapsible-context"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; -export interface AccordionHeaderProps - extends OverrideComponentProps<"h3", AsChildProp> {} +export interface AccordionHeaderOptions {} + +export interface AccordionHeaderCommonProps {} + +export interface AccordionHeaderRenderProps + extends AccordionHeaderCommonProps, + CollapsibleDataSet {} + +export type AccordionHeaderProps = AccordionHeaderOptions & + Partial; /** * Wraps an `Accordion.Trigger`. * Use the `as` prop to update it to the appropriate heading level for your page. */ -export function AccordionHeader(props: AccordionHeaderProps) { +export function AccordionHeader( + props: PolymorphicProps, +) { // `Accordion.Item` is a `Collapsible.Root`. const context = useCollapsibleContext(); - return ; + return ( + + as="h3" + {...context.dataset()} + {...props} + /> + ); } diff --git a/packages/core/src/accordion/accordion-item.tsx b/packages/core/src/accordion/accordion-item.tsx index aa129197..0940ae2a 100644 --- a/packages/core/src/accordion/accordion-item.tsx +++ b/packages/core/src/accordion/accordion-item.tsx @@ -6,15 +6,18 @@ * https://github.com/adobe/react-spectrum/blob/c183944ce6a8ca1cf280a1c7b88d2ba393dd0252/packages/@react-aria/accordion/src/useAccordion.ts */ +import { createGenerateId, mergeDefaultProps } from "@kobalte/utils"; import { - OverrideComponentProps, - createGenerateId, - mergeDefaultProps, -} from "@kobalte/utils"; -import { createSignal, createUniqueId, splitProps } from "solid-js"; + Component, + JSX, + ValidComponent, + createSignal, + createUniqueId, + splitProps, +} from "solid-js"; import * as Collapsible from "../collapsible"; -import { AsChildProp } from "../polymorphic"; +import { PolymorphicProps } from "../polymorphic"; import { createRegisterId } from "../primitives"; import { useAccordionContext } from "./accordion-context"; import { @@ -22,7 +25,7 @@ import { AccordionItemContextValue, } from "./accordion-item-context"; -export interface AccordionItemOptions extends AsChildProp { +export interface AccordionItemOptions { /** A unique value for the item. */ value: string; @@ -36,20 +39,32 @@ export interface AccordionItemOptions extends AsChildProp { forceMount?: boolean; } -export interface AccordionItemProps - extends OverrideComponentProps<"div", AccordionItemOptions> {} +export interface AccordionItemCommonProps + extends Collapsible.CollapsibleRootCommonProps {} + +export interface AccordionItemRenderProps + extends AccordionItemCommonProps, + Collapsible.CollapsibleRootRenderProps {} + +export type AccordionItemProps = AccordionItemOptions & + Partial; /** * An item of the accordion, contains all the parts of a collapsible section. */ -export function AccordionItem(props: AccordionItemProps) { +export function AccordionItem( + props: PolymorphicProps, +) { const accordionContext = useAccordionContext(); const defaultId = `${accordionContext.generateId( "item", )}-${createUniqueId()}`; - const mergedProps = mergeDefaultProps({ id: defaultId }, props); + const mergedProps = mergeDefaultProps( + { id: defaultId }, + props as AccordionItemProps, + ); const [local, others] = splitProps(mergedProps, ["value", "disabled"]); @@ -74,7 +89,14 @@ export function AccordionItem(props: AccordionItemProps) { return ( - + > + > open={isExpanded()} disabled={local.disabled} {...others} diff --git a/packages/core/src/accordion/accordion-root.tsx b/packages/core/src/accordion/accordion-root.tsx index 8b62788b..01072680 100644 --- a/packages/core/src/accordion/accordion-root.tsx +++ b/packages/core/src/accordion/accordion-root.tsx @@ -7,21 +7,26 @@ */ import { - OverrideComponentProps, composeEventHandlers, createGenerateId, mergeDefaultProps, mergeRefs, } from "@kobalte/utils"; -import { createSignal, createUniqueId, splitProps } from "solid-js"; +import { + JSX, + ValidComponent, + createSignal, + createUniqueId, + splitProps, +} from "solid-js"; import { createListState, createSelectableList } from "../list"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { CollectionItemWithRef } from "../primitives"; import { createDomCollection } from "../primitives/create-dom-collection"; import { AccordionContext, AccordionContextValue } from "./accordion-context"; -export interface AccordionRootOptions extends AsChildProp { +export interface AccordionRootOptions { /** The controlled value of the accordion item(s) to expand. */ value?: string[]; @@ -44,14 +49,27 @@ export interface AccordionRootOptions extends AsChildProp { shouldFocusWrap?: boolean; } -export interface AccordionRootProps - extends OverrideComponentProps<"div", AccordionRootOptions> {} +export interface AccordionRootCommonProps { + id: string; + ref: HTMLElement | ((el: HTMLElement) => void); + onKeyDown: JSX.EventHandlerUnion; + onMouseDown: JSX.EventHandlerUnion; + onFocusIn: JSX.EventHandlerUnion; + onFocusOut: JSX.EventHandlerUnion; +} + +export interface AccordionRootRenderProps extends AccordionRootCommonProps {} + +export type AccordionRootProps = AccordionRootOptions & + Partial; /** * A vertically stacked set of interactive headings that each reveal an associated section of content. */ -export function AccordionRoot(props: AccordionRootProps) { - let ref: HTMLDivElement | undefined; +export function AccordionRoot( + props: PolymorphicProps, +) { + let ref: HTMLElement | undefined; const defaultId = `accordion-${createUniqueId()}`; @@ -62,10 +80,11 @@ export function AccordionRoot(props: AccordionRootProps) { collapsible: false, shouldFocusWrap: true, }, - props, + props as AccordionRootProps, ); const [local, others] = splitProps(mergedProps, [ + "id", "ref", "value", "defaultValue", @@ -110,14 +129,15 @@ export function AccordionRoot(props: AccordionRootProps) { const context: AccordionContextValue = { listState: () => listState, - generateId: createGenerateId(() => others.id!), + generateId: createGenerateId(() => local.id), }; return ( - as="div" + id={local.id} ref={mergeRefs((el) => (ref = el), local.ref)} onKeyDown={composeEventHandlers([ local.onKeyDown, diff --git a/packages/core/src/accordion/accordion-trigger.tsx b/packages/core/src/accordion/accordion-trigger.tsx index a50bc9e8..8ed8f7af 100644 --- a/packages/core/src/accordion/accordion-trigger.tsx +++ b/packages/core/src/accordion/accordion-trigger.tsx @@ -7,30 +7,56 @@ */ import { - OverrideComponentProps, callHandler, composeEventHandlers, mergeDefaultProps, mergeRefs, } from "@kobalte/utils"; -import { JSX, createEffect, onCleanup, splitProps } from "solid-js"; +import { + Component, + JSX, + ValidComponent, + createEffect, + onCleanup, + splitProps, +} from "solid-js"; import * as Collapsible from "../collapsible"; import { useCollapsibleContext } from "../collapsible/collapsible-context"; -import { AsChildProp } from "../polymorphic"; +import { PolymorphicProps } from "../polymorphic"; import { CollectionItemWithRef } from "../primitives"; import { createDomCollectionItem } from "../primitives/create-dom-collection"; import { createSelectableItem } from "../selection"; import { useAccordionContext } from "./accordion-context"; import { useAccordionItemContext } from "./accordion-item-context"; -export interface AccordionTriggerProps - extends OverrideComponentProps<"button", AsChildProp> {} +export interface AccordionTriggerOptions {} + +export interface AccordionTriggerCommonProps + extends Collapsible.CollapsibleTriggerCommonProps { + id: string; + onPointerDown: JSX.EventHandlerUnion; + onPointerUp: JSX.EventHandlerUnion; + onKeyDown: JSX.EventHandlerUnion; + onMouseDown: JSX.EventHandlerUnion; + onFocus: JSX.EventHandlerUnion; +} + +export interface AccordionTriggerRenderProps + extends AccordionTriggerCommonProps, + Collapsible.CollapsibleTriggerRenderProps { + "data-key": string | undefined; +} + +export type AccordionTriggerProps = AccordionTriggerOptions & + Partial; /** * Toggles the collapsed state of its associated item. It should be nested inside an `Accordion.Header`. */ -export function AccordionTrigger(props: AccordionTriggerProps) { +export function AccordionTrigger( + props: PolymorphicProps, +) { let ref: HTMLElement | undefined; const accordionContext = useAccordionContext(); @@ -39,7 +65,10 @@ export function AccordionTrigger(props: AccordionTriggerProps) { const defaultId = itemContext.generateId("trigger"); - const mergedProps = mergeDefaultProps({ id: defaultId }, props); + const mergedProps = mergeDefaultProps( + { id: defaultId }, + props as AccordionTriggerProps, + ); const [local, others] = splitProps(mergedProps, [ "ref", @@ -84,7 +113,14 @@ export function AccordionTrigger(props: AccordionTriggerProps) { createEffect(() => onCleanup(itemContext.registerTriggerId(others.id!))); return ( - + > + > ref={mergeRefs((el) => (ref = el), local.ref)} data-key={selectableItem.dataKey()} onPointerDown={composeEventHandlers([ diff --git a/packages/core/src/accordion/index.tsx b/packages/core/src/accordion/index.tsx index f617b378..11207a6b 100644 --- a/packages/core/src/accordion/index.tsx +++ b/packages/core/src/accordion/index.tsx @@ -1,35 +1,59 @@ import { AccordionContent as Content, + type AccordionContentCommonProps, type AccordionContentOptions, type AccordionContentProps, + type AccordionContentRenderProps, } from "./accordion-content"; import { AccordionHeader as Header, + type AccordionHeaderCommonProps, + type AccordionHeaderOptions, type AccordionHeaderProps, + type AccordionHeaderRenderProps, } from "./accordion-header"; import { AccordionItem as Item, + type AccordionItemCommonProps, type AccordionItemOptions, type AccordionItemProps, + type AccordionItemRenderProps, } from "./accordion-item"; import { AccordionRoot as Root, + type AccordionRootCommonProps, type AccordionRootOptions, type AccordionRootProps, + type AccordionRootRenderProps, } from "./accordion-root"; import { AccordionTrigger as Trigger, + type AccordionTriggerCommonProps, + type AccordionTriggerOptions, type AccordionTriggerProps, + type AccordionTriggerRenderProps, } from "./accordion-trigger"; export type { AccordionContentOptions, + AccordionContentCommonProps, + AccordionContentRenderProps, AccordionContentProps, + AccordionHeaderOptions, + AccordionHeaderCommonProps, + AccordionHeaderRenderProps, AccordionHeaderProps, AccordionItemOptions, + AccordionItemCommonProps, + AccordionItemRenderProps, AccordionItemProps, AccordionRootOptions, + AccordionRootCommonProps, + AccordionRootRenderProps, AccordionRootProps, + AccordionTriggerOptions, + AccordionTriggerCommonProps, + AccordionTriggerRenderProps, AccordionTriggerProps, }; export { Content, Header, Item, Root, Trigger }; diff --git a/packages/core/src/alert-dialog/alert-dialog-content.tsx b/packages/core/src/alert-dialog/alert-dialog-content.tsx index 39d09a6e..420bc2ee 100644 --- a/packages/core/src/alert-dialog/alert-dialog-content.tsx +++ b/packages/core/src/alert-dialog/alert-dialog-content.tsx @@ -1,11 +1,43 @@ -import { DialogContentProps } from "../dialog"; -import { DialogContent } from "../dialog/dialog-content"; +import { Component, ValidComponent } from "solid-js"; +import { + DialogContent, + DialogContentCommonProps, + DialogContentOptions, + DialogContentRenderProps, +} from "../dialog/dialog-content"; +import { PolymorphicProps } from "../polymorphic"; -export interface AlertDialogContentProps extends DialogContentProps {} +export interface AlertDialogContentOptions extends DialogContentOptions {} + +export interface AlertDialogContentCommonProps + extends DialogContentCommonProps {} + +export interface AlertDialogContentRenderProps + extends AlertDialogContentCommonProps, + DialogContentRenderProps { + role: "alertdialog"; +} + +export type AlertDialogContentProps = AlertDialogContentOptions & + Partial; /** * Overrides the regular `Dialog.Content` with role="alertdialog" to interrupt the user. */ -export function AlertDialogContent(props: AlertDialogContentProps) { - return ; +export function AlertDialogContent( + props: PolymorphicProps, +) { + return ( + + > + > + > + role="alertdialog" + {...(props as AlertDialogContentProps)} + /> + ); } diff --git a/packages/core/src/alert-dialog/alert-dialog.test.tsx b/packages/core/src/alert-dialog/alert-dialog.test.tsx index 6fe89c31..60f11977 100644 --- a/packages/core/src/alert-dialog/alert-dialog.test.tsx +++ b/packages/core/src/alert-dialog/alert-dialog.test.tsx @@ -6,7 +6,7 @@ * https://github.com/adobe/react-spectrum/blob/810579b671791f1593108f62cdc1893de3a220e3/packages/@react-spectrum/dialog/test/Dialog.test.js */ -import { render, screen } from "@solidjs/testing-library"; +import { render } from "@solidjs/testing-library"; import * as AlertDialog from "."; diff --git a/packages/core/src/alert-dialog/index.tsx b/packages/core/src/alert-dialog/index.tsx index 4cb381d9..970a53c5 100644 --- a/packages/core/src/alert-dialog/index.tsx +++ b/packages/core/src/alert-dialog/index.tsx @@ -1,16 +1,29 @@ import { CloseButton, Description, + type DialogCloseButtonCommonProps as AlertDialogCloseButtonCommonProps, + type DialogCloseButtonOptions as AlertDialogCloseButtonOptions, type DialogCloseButtonProps as AlertDialogCloseButtonProps, - type DialogContentOptions as AlertDialogContentOptions, + type DialogCloseButtonRenderProps as AlertDialogCloseButtonRenderProps, + type DialogDescriptionCommonProps as AlertDialogDescriptionCommonProps, + type DialogDescriptionOptions as AlertDialogDescriptionOptions, type DialogDescriptionProps as AlertDialogDescriptionProps, + type DialogDescriptionRenderProps as AlertDialogDescriptionRenderProps, + type DialogOverlayCommonProps as AlertDialogOverlayCommonProps, type DialogOverlayOptions as AlertDialogOverlayOptions, type DialogOverlayProps as AlertDialogOverlayProps, + type DialogOverlayRenderProps as AlertDialogOverlayRenderProps, type DialogPortalProps as AlertDialogPortalProps, type DialogRootOptions as AlertDialogRootOptions, type DialogRootProps as AlertDialogRootProps, + type DialogTitleCommonProps as AlertDialogTitleCommonProps, + type DialogTitleOptions as AlertDialogTitleOptions, type DialogTitleProps as AlertDialogTitleProps, + type DialogTitleRenderProps as AlertDialogTitleRenderProps, + type DialogTriggerCommonProps as AlertDialogTriggerCommonProps, + type DialogTriggerOptions as AlertDialogTriggerOptions, type DialogTriggerProps as AlertDialogTriggerProps, + type DialogTriggerRenderProps as AlertDialogTriggerRenderProps, Overlay, Portal, Root, @@ -19,20 +32,39 @@ import { } from "../dialog"; import { AlertDialogContent as Content, + AlertDialogContentCommonProps, + AlertDialogContentOptions, AlertDialogContentProps, + AlertDialogContentRenderProps, } from "./alert-dialog-content"; export type { + AlertDialogCloseButtonOptions, + AlertDialogCloseButtonCommonProps, + AlertDialogCloseButtonRenderProps, AlertDialogCloseButtonProps, AlertDialogContentOptions, + AlertDialogContentCommonProps, + AlertDialogContentRenderProps, AlertDialogContentProps, + AlertDialogDescriptionOptions, + AlertDialogDescriptionCommonProps, + AlertDialogDescriptionRenderProps, AlertDialogDescriptionProps, AlertDialogOverlayOptions, + AlertDialogOverlayCommonProps, + AlertDialogOverlayRenderProps, AlertDialogOverlayProps, AlertDialogPortalProps, AlertDialogRootOptions, AlertDialogRootProps, + AlertDialogTitleOptions, + AlertDialogTitleCommonProps, + AlertDialogTitleRenderProps, AlertDialogTitleProps, + AlertDialogTriggerOptions, + AlertDialogTriggerCommonProps, + AlertDialogTriggerRenderProps, AlertDialogTriggerProps, }; diff --git a/packages/core/src/alert/alert-root.tsx b/packages/core/src/alert/alert-root.tsx index c21369ba..4c25e9f8 100644 --- a/packages/core/src/alert/alert-root.tsx +++ b/packages/core/src/alert/alert-root.tsx @@ -1,14 +1,29 @@ -import { OverrideComponentProps } from "@kobalte/utils"; +import { ValidComponent } from "solid-js"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; -export interface AlertRootProps - extends OverrideComponentProps<"div", AsChildProp> {} +export interface AlertRootOptions {} + +export interface AlertRootCommonProps {} + +export interface AlertRootRenderProps extends AlertRootCommonProps { + role: "alert"; +} + +export type AlertRootProps = AlertRootOptions & Partial; /** * Alert displays a brief, important message * in a way that attracts the user's attention without interrupting the user's task. */ -export function AlertRoot(props: AlertRootProps) { - return ; +export function AlertRoot( + props: PolymorphicProps, +) { + return ( + + as="div" + role="alert" + {...(props as AlertRootProps)} + /> + ); } diff --git a/packages/core/src/alert/alert.test.tsx b/packages/core/src/alert/alert.test.tsx index 882ef87b..3f3711f7 100644 --- a/packages/core/src/alert/alert.test.tsx +++ b/packages/core/src/alert/alert.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@solidjs/testing-library"; +import { render } from "@solidjs/testing-library"; import * as Alert from "."; diff --git a/packages/core/src/alert/index.tsx b/packages/core/src/alert/index.tsx index 3e116098..43ea4380 100644 --- a/packages/core/src/alert/index.tsx +++ b/packages/core/src/alert/index.tsx @@ -1,4 +1,15 @@ -import { AlertRoot as Root, type AlertRootProps } from "./alert-root"; +import { + AlertRoot as Root, + type AlertRootCommonProps, + type AlertRootOptions, + type AlertRootProps, + type AlertRootRenderProps, +} from "./alert-root"; -export type { AlertRootProps }; +export type { + AlertRootOptions, + AlertRootCommonProps, + AlertRootRenderProps, + AlertRootProps, +}; export { Root }; diff --git a/packages/core/src/breadcrumbs/breadcrumbs-link.tsx b/packages/core/src/breadcrumbs/breadcrumbs-link.tsx index e9f7cc4a..1d641498 100644 --- a/packages/core/src/breadcrumbs/breadcrumbs-link.tsx +++ b/packages/core/src/breadcrumbs/breadcrumbs-link.tsx @@ -6,27 +6,36 @@ * https://github.com/adobe/react-spectrum/blob/38a57d3360268fb0cb55c6b42b9a5f6f13bb57d6/packages/@react-aria/breadcrumbs/src/useBreadcrumbItem.ts */ -import { OverrideComponentProps } from "@kobalte/utils"; -import { splitProps } from "solid-js"; +import { Component, ValidComponent, splitProps } from "solid-js"; import * as Link from "../link"; +import { PolymorphicProps } from "../polymorphic"; -export interface BreadcrumbsLinkOptions extends Link.LinkRootOptions { +export interface BreadcrumbsLinkOptions { /** Whether the breadcrumb link represents the current page. */ current?: boolean; +} +export interface BreadcrumbsLinkCommonProps { /** Whether the breadcrumb link is disabled. */ - disabled?: boolean; + disabled: boolean; + "aria-current": string | undefined; +} + +export interface BreadcrumbsLinkRenderProps extends BreadcrumbsLinkCommonProps { + "data-current": string | undefined; } -export interface BreadcrumbsLinkProps - extends OverrideComponentProps<"a", BreadcrumbsLinkOptions> {} +export type BreadcrumbsLinkProps = BreadcrumbsLinkOptions & + Partial; /** * The breadcrumbs link. */ -export function BreadcrumbsLink(props: BreadcrumbsLinkProps) { - const [local, others] = splitProps(props, [ +export function BreadcrumbsLink( + props: PolymorphicProps, +) { + const [local, others] = splitProps(props as BreadcrumbsLinkProps, [ "current", "disabled", "aria-current", @@ -41,7 +50,11 @@ export function BreadcrumbsLink(props: BreadcrumbsLinkProps) { }; return ( - + > + > disabled={local.disabled || local.current} aria-current={ariaCurrent()} data-current={local.current ? "" : undefined} diff --git a/packages/core/src/breadcrumbs/breadcrumbs-root.tsx b/packages/core/src/breadcrumbs/breadcrumbs-root.tsx index cc3cd7b4..01f09f69 100644 --- a/packages/core/src/breadcrumbs/breadcrumbs-root.tsx +++ b/packages/core/src/breadcrumbs/breadcrumbs-root.tsx @@ -6,10 +6,10 @@ * https://github.com/adobe/react-spectrum/blob/38a57d3360268fb0cb55c6b42b9a5f6f13bb57d6/packages/@react-aria/breadcrumbs/src/useBreadcrumbs.ts */ -import { OverrideComponentProps, mergeDefaultProps } from "@kobalte/utils"; -import { JSX, splitProps } from "solid-js"; +import { mergeDefaultProps } from "@kobalte/utils"; +import { JSX, ValidComponent, splitProps } from "solid-js"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { BreadcrumbsContext, BreadcrumbsContextValue, @@ -19,7 +19,7 @@ import { BreadcrumbsIntlTranslations, } from "./breadcrumbs.intl"; -export interface BreadcrumbsRootOptions extends AsChildProp { +export interface BreadcrumbsRootOptions { /** * The visual separator between each breadcrumb item. * It will be used as the default children of `Breadcrumbs.Separator`. @@ -30,19 +30,27 @@ export interface BreadcrumbsRootOptions extends AsChildProp { translations?: BreadcrumbsIntlTranslations; } -export interface BreadcrumbsRootProps - extends OverrideComponentProps<"nav", BreadcrumbsRootOptions> {} +export interface BreadcrumbsRootCommonProps {} + +export interface BreadcrumbsRootRenderProps extends BreadcrumbsRootCommonProps { + "aria-label": string; +} + +export type BreadcrumbsRootProps = BreadcrumbsRootOptions & + Partial; /** * Breadcrumbs show hierarchy and navigational context for a user’s location within an application. */ -export function BreadcrumbsRoot(props: BreadcrumbsRootProps) { +export function BreadcrumbsRoot( + props: PolymorphicProps, +) { const mergedProps = mergeDefaultProps( { separator: "/", translations: BREADCRUMBS_INTL_TRANSLATIONS, }, - props, + props as BreadcrumbsRootProps, ); const [local, others] = splitProps(mergedProps, [ @@ -56,9 +64,9 @@ export function BreadcrumbsRoot(props: BreadcrumbsRootProps) { return ( - as="nav" - aria-label={local.translations?.breadcrumbs} + aria-label={local.translations.breadcrumbs} {...others} /> diff --git a/packages/core/src/breadcrumbs/breadcrumbs-separator.tsx b/packages/core/src/breadcrumbs/breadcrumbs-separator.tsx index d9ae632c..bfa5c4b4 100644 --- a/packages/core/src/breadcrumbs/breadcrumbs-separator.tsx +++ b/packages/core/src/breadcrumbs/breadcrumbs-separator.tsx @@ -1,24 +1,37 @@ import { OverrideComponentProps } from "@kobalte/utils"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { JSX, ValidComponent } from "solid-js"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { useBreadcrumbsContext } from "./breadcrumbs-context"; -export interface BreadcrumbsSeparatorProps - extends OverrideComponentProps<"span", AsChildProp> {} +export interface BreadcrumbsSeparatorOptions {} + +export interface BreadcrumbsSeparatorCommonProps {} + +export interface BreadcrumbsSeparatorRenderProps + extends BreadcrumbsSeparatorCommonProps { + children: JSX.Element; + "aria-hidden": "true"; +} + +export type BreadcrumbsSeparatorProps = BreadcrumbsSeparatorOptions & + Partial; /** * The visual separator between each breadcrumb items. * It will not be visible by screen readers. */ -export function BreadcrumbsSeparator(props: BreadcrumbsSeparatorProps) { +export function BreadcrumbsSeparator( + props: PolymorphicProps, +) { const context = useBreadcrumbsContext(); return ( - as="span" children={context.separator()} aria-hidden="true" - {...props} + {...(props as BreadcrumbsSeparatorProps)} /> ); } diff --git a/packages/core/src/breadcrumbs/breadcrumbs.test.tsx b/packages/core/src/breadcrumbs/breadcrumbs.test.tsx index 6737b8cf..952595a0 100644 --- a/packages/core/src/breadcrumbs/breadcrumbs.test.tsx +++ b/packages/core/src/breadcrumbs/breadcrumbs.test.tsx @@ -7,7 +7,7 @@ * https://github.com/adobe/react-spectrum/blob/38a57d3360268fb0cb55c6b42b9a5f6f13bb57d6/packages/@react-aria/breadcrumbs/test/useBreadcrumbItem.test.js */ -import { render, screen } from "@solidjs/testing-library"; +import { render } from "@solidjs/testing-library"; import * as Breadcrumbs from "."; diff --git a/packages/core/src/breadcrumbs/index.tsx b/packages/core/src/breadcrumbs/index.tsx index 1e964f85..b9f99fb4 100644 --- a/packages/core/src/breadcrumbs/index.tsx +++ b/packages/core/src/breadcrumbs/index.tsx @@ -1,7 +1,9 @@ import { BreadcrumbsLink as Link, + type BreadcrumbsLinkCommonProps, type BreadcrumbsLinkOptions, type BreadcrumbsLinkProps, + type BreadcrumbsLinkRenderProps, } from "./breadcrumbs-link"; import { BreadcrumbsRoot as Root, @@ -10,14 +12,22 @@ import { } from "./breadcrumbs-root"; import { BreadcrumbsSeparator as Separator, + type BreadcrumbsSeparatorCommonProps, + type BreadcrumbsSeparatorOptions, type BreadcrumbsSeparatorProps, + type BreadcrumbsSeparatorRenderProps, } from "./breadcrumbs-separator"; export type { BreadcrumbsLinkOptions, + BreadcrumbsLinkCommonProps, + BreadcrumbsLinkRenderProps, BreadcrumbsLinkProps, BreadcrumbsRootOptions, BreadcrumbsRootProps, + BreadcrumbsSeparatorOptions, + BreadcrumbsSeparatorCommonProps, + BreadcrumbsSeparatorRenderProps, BreadcrumbsSeparatorProps, }; export { Link, Root, Separator }; diff --git a/packages/core/src/button/button-root.tsx b/packages/core/src/button/button-root.tsx index 50e7a844..8e85d7b4 100644 --- a/packages/core/src/button/button-root.tsx +++ b/packages/core/src/button/button-root.tsx @@ -12,34 +12,46 @@ * https://github.com/ariakit/ariakit/blob/8a13899ff807bbf39f3d89d2d5964042ba4d5287/packages/ariakit/src/button/button.ts */ -import { - OverrideComponentProps, - mergeDefaultProps, - mergeRefs, -} from "@kobalte/utils"; -import { createMemo, splitProps } from "solid-js"; - -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { mergeDefaultProps, mergeRefs } from "@kobalte/utils"; +import { ValidComponent, createMemo, splitProps } from "solid-js"; + +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { createTagName } from "../primitives"; import { isButton } from "./is-button"; -export interface ButtonRootOptions extends AsChildProp { +export interface ButtonRootOptions {} + +export interface ButtonRootCommonProps { /** Whether the button is disabled. */ - disabled?: boolean; + disabled: boolean | undefined; + type: "submit" | "reset" | "button" | undefined; + ref: HTMLElement | ((el: HTMLElement) => void); + tabIndex: number | string | undefined; } -export interface ButtonRootProps - extends OverrideComponentProps<"button", ButtonRootOptions> {} +export interface ButtonRootRenderProps extends ButtonRootCommonProps { + role: "menuitem" | "button" | undefined; + "aria-disabled": boolean | undefined; + "data-disabled": string | undefined; +} + +export type ButtonRootProps = ButtonRootOptions & + Partial; /** * Button enables users to trigger an action or event, such as submitting a form, * opening a dialog, canceling an action, or performing a delete operation. * This component is based on the [WAI-ARIA Button Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/) */ -export function ButtonRoot(props: ButtonRootProps) { - let ref: HTMLButtonElement | undefined; +export function ButtonRoot( + props: PolymorphicProps, +) { + let ref: HTMLElement | undefined; - const mergedProps = mergeDefaultProps({ type: "button" }, props); + const mergedProps = mergeDefaultProps( + { type: "button" }, + props as ButtonRootProps, + ); const [local, others] = splitProps(mergedProps, ["ref", "type", "disabled"]); @@ -67,7 +79,7 @@ export function ButtonRoot(props: ButtonRootProps) { }); return ( - as="button" ref={mergeRefs((el) => (ref = el), local.ref)} type={isNativeButton() || isNativeInput() ? local.type : undefined} diff --git a/packages/core/src/button/button.test.tsx b/packages/core/src/button/button.test.tsx index 8cd546c9..ad8e1cb2 100644 --- a/packages/core/src/button/button.test.tsx +++ b/packages/core/src/button/button.test.tsx @@ -1,8 +1,7 @@ import { installPointerEvent } from "@kobalte/tests"; -import { render, screen } from "@solidjs/testing-library"; +import { render } from "@solidjs/testing-library"; import * as Button from "."; -import { As } from "../polymorphic"; describe("Button", () => { installPointerEvent(); @@ -19,8 +18,8 @@ describe("Button", () => { it("should not have attribute 'type=button' by default when it's not a 'button' tag", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -31,10 +30,8 @@ describe("Button", () => { it("should keep attribute 'type' when provided and it's a native 'button' or 'input'", () => { const { getByTestId } = render(() => ( - - - Button - + + Button )); @@ -55,10 +52,8 @@ describe("Button", () => { it("should not have attribute 'role=button' when it's an 'a' tag with 'href'", () => { const { getByTestId } = render(() => ( - - - Button - + + Button )); @@ -69,8 +64,8 @@ describe("Button", () => { it("should have attribute 'role=button' when it's not a native button", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -81,8 +76,8 @@ describe("Button", () => { it("should have attribute 'role=button' when it's an 'a' tag without 'href'", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -93,8 +88,8 @@ describe("Button", () => { it("should have attribute 'tabindex=0' when it's not a native button", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -105,10 +100,8 @@ describe("Button", () => { it("should not have attribute 'tabindex=0' when it's an 'a' tag with 'href'", () => { const { getByTestId } = render(() => ( - - - Button - + + Button )); @@ -119,8 +112,8 @@ describe("Button", () => { it("should not have attribute 'tabindex=0' when it's disabled", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -144,8 +137,8 @@ describe("Button", () => { it("should have correct 'disabled' attribute when disabled and it's an input", () => { const { getByTestId } = render(() => ( - - Button + + Button )); @@ -157,8 +150,8 @@ describe("Button", () => { it("should have correct 'disabled' attribute when disabled and it's not a native button nor input", () => { const { getByTestId } = render(() => ( - - Button + + Button )); diff --git a/packages/core/src/button/index.tsx b/packages/core/src/button/index.tsx index 24ad6811..17421807 100644 --- a/packages/core/src/button/index.tsx +++ b/packages/core/src/button/index.tsx @@ -1,8 +1,15 @@ import { ButtonRoot as Root, + type ButtonRootCommonProps, type ButtonRootOptions, type ButtonRootProps, + type ButtonRootRenderProps, } from "./button-root"; -export type { ButtonRootOptions, ButtonRootProps }; +export type { + ButtonRootOptions, + ButtonRootCommonProps, + ButtonRootRenderProps, + ButtonRootProps, +}; export { Root }; diff --git a/packages/core/src/checkbox/checkbox-control.tsx b/packages/core/src/checkbox/checkbox-control.tsx index 428e0fe5..d4a8ff31 100644 --- a/packages/core/src/checkbox/checkbox-control.tsx +++ b/packages/core/src/checkbox/checkbox-control.tsx @@ -1,22 +1,32 @@ -import { - EventKey, - OverrideComponentProps, - callHandler, - mergeDefaultProps, -} from "@kobalte/utils"; -import { JSX, splitProps } from "solid-js"; +import { EventKey, callHandler, mergeDefaultProps } from "@kobalte/utils"; +import { JSX, ValidComponent, splitProps } from "solid-js"; -import { useFormControlContext } from "../form-control"; -import { AsChildProp, Polymorphic } from "../polymorphic"; -import { useCheckboxContext } from "./checkbox-context"; +import { FormControlDataSet, useFormControlContext } from "../form-control"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxControlProps - extends OverrideComponentProps<"div", AsChildProp> {} +export interface CheckboxControlOptions {} + +export interface CheckboxControlCommonProps { + id: string; + onClick: JSX.EventHandlerUnion; + onKeyDown: JSX.EventHandlerUnion; +} + +export interface CheckboxControlRenderProps + extends CheckboxControlCommonProps, + FormControlDataSet, + CheckboxDataSet {} + +export type CheckboxControlProps = CheckboxControlOptions & + Partial; /** * The element that visually represents a checkbox. */ -export function CheckboxControl(props: CheckboxControlProps) { +export function CheckboxControl( + props: PolymorphicProps, +) { const formControlContext = useFormControlContext(); const context = useCheckboxContext(); @@ -24,20 +34,20 @@ export function CheckboxControl(props: CheckboxControlProps) { { id: context.generateId("control"), }, - props, + props as CheckboxControlProps, ); const [local, others] = splitProps(mergedProps, ["onClick", "onKeyDown"]); const onClick: JSX.EventHandlerUnion = (e) => { - callHandler(e, local.onClick as typeof onClick); + callHandler(e, local.onClick); context.toggle(); context.inputRef()?.focus(); }; const onKeyDown: JSX.EventHandlerUnion = (e) => { - callHandler(e, local.onKeyDown as typeof onKeyDown); + callHandler(e, local.onKeyDown); if (e.key === EventKey.Space) { context.toggle(); @@ -46,7 +56,7 @@ export function CheckboxControl(props: CheckboxControlProps) { }; return ( - as="div" onClick={onClick} onKeyDown={onKeyDown} diff --git a/packages/core/src/checkbox/checkbox-description.tsx b/packages/core/src/checkbox/checkbox-description.tsx index acdc8fda..c77d72bd 100644 --- a/packages/core/src/checkbox/checkbox-description.tsx +++ b/packages/core/src/checkbox/checkbox-description.tsx @@ -1,16 +1,46 @@ +import { Component, ValidComponent } from "solid-js"; import { FormControlDescription, + FormControlDescriptionCommonProps, FormControlDescriptionProps, + FormControlDescriptionRenderProps, } from "../form-control"; -import { useCheckboxContext } from "./checkbox-context"; +import { PolymorphicProps } from "../polymorphic"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxDescriptionProps extends FormControlDescriptionProps {} +export interface CheckboxDescriptionOptions + extends FormControlDescriptionProps {} + +export interface CheckboxDescriptionCommonProps + extends FormControlDescriptionCommonProps {} + +export interface CheckboxDescriptionRenderProps + extends CheckboxDescriptionCommonProps, + CheckboxDataSet, + FormControlDescriptionRenderProps {} + +export type CheckboxDescriptionProps = CheckboxDescriptionOptions & + Partial; /** * The description that gives the user more information on the checkbox. */ -export function CheckboxDescription(props: CheckboxDescriptionProps) { +export function CheckboxDescription( + props: PolymorphicProps, +) { const context = useCheckboxContext(); - return ; + return ( + + > + > + {...context.dataset()} + {...(props as CheckboxDescriptionProps)} + /> + ); } diff --git a/packages/core/src/checkbox/checkbox-error-message.tsx b/packages/core/src/checkbox/checkbox-error-message.tsx index 12f852ac..cb66aae7 100644 --- a/packages/core/src/checkbox/checkbox-error-message.tsx +++ b/packages/core/src/checkbox/checkbox-error-message.tsx @@ -1,17 +1,46 @@ +import { Component, ValidComponent } from "solid-js"; import { FormControlErrorMessage, + FormControlErrorMessageCommonProps, FormControlErrorMessageProps, + FormControlErrorMessageRenderProps, } from "../form-control"; -import { useCheckboxContext } from "./checkbox-context"; +import { PolymorphicProps } from "../polymorphic"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxErrorMessageProps +export interface CheckboxErrorMessageOptions extends FormControlErrorMessageProps {} +export interface CheckboxErrorMessageCommonProps + extends FormControlErrorMessageCommonProps {} + +export interface CheckboxErrorMessageRenderProps + extends CheckboxErrorMessageCommonProps, + CheckboxDataSet, + FormControlErrorMessageRenderProps {} + +export type CheckboxErrorMessageProps = CheckboxErrorMessageOptions & + Partial; + /** * The error message that gives the user information about how to fix a validation error on the checkbox. */ -export function CheckboxErrorMessage(props: CheckboxErrorMessageProps) { +export function CheckboxErrorMessage( + props: PolymorphicProps, +) { const context = useCheckboxContext(); - return ; + return ( + + > + > + {...context.dataset()} + {...(props as CheckboxErrorMessageProps)} + /> + ); } diff --git a/packages/core/src/checkbox/checkbox-indicator.tsx b/packages/core/src/checkbox/checkbox-indicator.tsx index e1b68c42..32325ccb 100644 --- a/packages/core/src/checkbox/checkbox-indicator.tsx +++ b/packages/core/src/checkbox/checkbox-indicator.tsx @@ -3,14 +3,14 @@ import { mergeDefaultProps, mergeRefs, } from "@kobalte/utils"; -import { Show, splitProps } from "solid-js"; +import { Show, ValidComponent, splitProps } from "solid-js"; -import { useFormControlContext } from "../form-control"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { FormControlDataSet, useFormControlContext } from "../form-control"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { createPresence } from "../primitives"; -import { useCheckboxContext } from "./checkbox-context"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxIndicatorOptions extends AsChildProp { +export interface CheckboxIndicatorOptions { /** * Used to force mounting when more control is needed. * Useful when controlling animation with SolidJS animation libraries. @@ -18,14 +18,26 @@ export interface CheckboxIndicatorOptions extends AsChildProp { forceMount?: boolean; } -export interface CheckboxIndicatorProps - extends OverrideComponentProps<"div", CheckboxIndicatorOptions> {} +export interface CheckboxIndicatorCommonProps { + id: string; + ref: HTMLElement | ((el: HTMLElement) => void); +} + +export interface CheckboxIndicatorRenderProps + extends CheckboxIndicatorCommonProps, + FormControlDataSet, + CheckboxDataSet {} + +export type CheckboxIndicatorProps = CheckboxIndicatorOptions & + Partial; /** * The visual indicator rendered when the checkbox is in a checked or indeterminate state. * You can style this element directly, or you can use it as a wrapper to put an icon into, or both. */ -export function CheckboxIndicator(props: CheckboxIndicatorProps) { +export function CheckboxIndicator( + props: PolymorphicProps, +) { const formControlContext = useFormControlContext(); const context = useCheckboxContext(); @@ -33,7 +45,7 @@ export function CheckboxIndicator(props: CheckboxIndicatorProps) { { id: context.generateId("indicator"), }, - props, + props as CheckboxIndicatorProps, ); const [local, others] = splitProps(mergedProps, ["ref", "forceMount"]); @@ -44,7 +56,7 @@ export function CheckboxIndicator(props: CheckboxIndicatorProps) { return ( - as="div" ref={mergeRefs(presence.setRef, local.ref)} {...formControlContext.dataset()} diff --git a/packages/core/src/checkbox/checkbox-input.tsx b/packages/core/src/checkbox/checkbox-input.tsx index de71e329..a7718347 100644 --- a/packages/core/src/checkbox/checkbox-input.tsx +++ b/packages/core/src/checkbox/checkbox-input.tsx @@ -8,15 +8,14 @@ */ import { - OverrideComponentProps, callHandler, mergeDefaultProps, mergeRefs, visuallyHiddenStyles, } from "@kobalte/utils"; import { - ComponentProps, JSX, + ValidComponent, createEffect, createSignal, on, @@ -25,23 +24,54 @@ import { import { FORM_CONTROL_FIELD_PROP_NAMES, + FormControlDataSet, createFormControlField, useFormControlContext, } from "../form-control"; -import { useCheckboxContext } from "./checkbox-context"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxInputOptions { +export interface CheckboxInputOptions {} + +export interface CheckboxInputCommonProps { + id: string; + ref: HTMLInputElement | ((el: HTMLInputElement) => void); /** The HTML styles attribute (object form only). */ - style?: JSX.CSSProperties; + style: JSX.CSSProperties; + onChange: JSX.EventHandlerUnion; + onFocus: JSX.FocusEventHandlerUnion; + onBlur: JSX.FocusEventHandlerUnion; + "aria-label": string | undefined; + "aria-labelledby": string | undefined; + "aria-describedby": string | undefined; +} + +export interface CheckboxInputRenderProps + extends CheckboxInputCommonProps, + FormControlDataSet, + CheckboxDataSet { + type: "checkbox"; + name: string; + value: string; + checked: boolean; + required: boolean | undefined; + disabled: boolean | undefined; + readonly: boolean | undefined; + "aria-invalid": boolean | undefined; + "aria-required": boolean | undefined; + "aria-disabled": boolean | undefined; + "aria-readonly": boolean | undefined; } -export interface CheckboxInputProps - extends OverrideComponentProps<"input", CheckboxInputOptions> {} +export type CheckboxInputProps = CheckboxInputOptions & + Partial; /** * The native html input that is visually hidden in the checkbox. */ -export function CheckboxInput(props: CheckboxInputProps) { +export function CheckboxInput( + props: PolymorphicProps, +) { let ref: HTMLInputElement | undefined; const formControlContext = useFormControlContext(); @@ -51,7 +81,7 @@ export function CheckboxInput(props: CheckboxInputProps) { { id: context.generateId("input"), }, - props, + props as CheckboxInputProps, ); const [local, formControlFieldProps, others] = splitProps( @@ -64,9 +94,7 @@ export function CheckboxInput(props: CheckboxInputProps) { const [isInternalChangeEvent, setIsInternalChangeEvent] = createSignal(false); - const onChange: JSX.ChangeEventHandlerUnion = ( - e, - ) => { + const onChange: JSX.EventHandlerUnion = (e) => { callHandler(e, local.onChange); e.stopPropagation(); @@ -88,13 +116,17 @@ export function CheckboxInput(props: CheckboxInputProps) { setIsInternalChangeEvent(false); }; - const onFocus: JSX.FocusEventHandlerUnion = (e) => { - callHandler(e, local.onFocus as typeof onFocus); + const onFocus: JSX.FocusEventHandlerUnion = ( + e, + ) => { + callHandler(e, local.onFocus); context.setIsFocused(true); }; - const onBlur: JSX.FocusEventHandlerUnion = (e) => { - callHandler(e, local.onBlur as typeof onBlur); + const onBlur: JSX.FocusEventHandlerUnion = ( + e, + ) => { + callHandler(e, local.onBlur); context.setIsFocused(false); }; @@ -134,7 +166,8 @@ export function CheckboxInput(props: CheckboxInputProps) { ); return ( - + as="input" ref={mergeRefs((el) => { context.setInputRef(el); ref = el; @@ -154,15 +187,15 @@ export function CheckboxInput(props: CheckboxInputProps) { aria-invalid={ formControlContext.validationState() === "invalid" || undefined } - aria-required={formControlContext.isRequired() || undefined} - aria-disabled={formControlContext.isDisabled() || undefined} - aria-readonly={formControlContext.isReadOnly() || undefined} + aria-required={formControlContext.isRequired()} + aria-disabled={formControlContext.isDisabled()} + aria-readonly={formControlContext.isReadOnly()} onChange={onChange} onFocus={onFocus} onBlur={onBlur} {...formControlContext.dataset()} {...context.dataset()} - {...(others as ComponentProps<"input">)} + {...others} /> ); } diff --git a/packages/core/src/checkbox/checkbox-label.tsx b/packages/core/src/checkbox/checkbox-label.tsx index 6fb63394..802cff6b 100644 --- a/packages/core/src/checkbox/checkbox-label.tsx +++ b/packages/core/src/checkbox/checkbox-label.tsx @@ -1,13 +1,41 @@ -import { FormControlLabel, FormControlLabelProps } from "../form-control"; -import { useCheckboxContext } from "./checkbox-context"; +import { Component, ValidComponent } from "solid-js"; +import { + FormControlLabel, + FormControlLabelCommonProps, + FormControlLabelOptions, + FormControlLabelRenderProps, +} from "../form-control"; +import { PolymorphicProps } from "../polymorphic"; +import { CheckboxDataSet, useCheckboxContext } from "./checkbox-context"; -export interface CheckboxLabelProps extends FormControlLabelProps {} +export interface CheckboxLabelOptions extends FormControlLabelOptions {} + +export interface CheckboxLabelCommonProps extends FormControlLabelCommonProps {} + +export interface CheckboxLabelRenderProps + extends CheckboxLabelCommonProps, + FormControlLabelRenderProps, + CheckboxDataSet {} + +export type CheckboxLabelProps = CheckboxLabelOptions & + Partial; /** * The label that gives the user information on the checkbox. */ -export function CheckboxLabel(props: CheckboxLabelProps) { +export function CheckboxLabel( + props: PolymorphicProps, +) { const context = useCheckboxContext(); - return ; + return ( + + > + > + {...context.dataset()} + {...(props as CheckboxLabelProps)} + /> + ); } diff --git a/packages/core/src/checkbox/checkbox-root.tsx b/packages/core/src/checkbox/checkbox-root.tsx index d99546c1..208668a7 100644 --- a/packages/core/src/checkbox/checkbox-root.tsx +++ b/packages/core/src/checkbox/checkbox-root.tsx @@ -19,6 +19,7 @@ import { import { Accessor, JSX, + ValidComponent, children, createMemo, createSignal, @@ -29,9 +30,10 @@ import { import { FORM_CONTROL_PROP_NAMES, FormControlContext, + FormControlDataSet, createFormControl, } from "../form-control"; -import { Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { createFormResetListener, createToggleState } from "../primitives"; import { CheckboxContext, @@ -98,14 +100,30 @@ export interface CheckboxRootOptions { children?: JSX.Element | ((state: CheckboxRootState) => JSX.Element); } -export interface CheckboxRootProps - extends OverrideComponentProps<"div", CheckboxRootOptions> {} +export interface CheckboxRootCommonProps { + id: string; + ref: HTMLElement | ((el: HTMLElement) => void); + onPointerDown: JSX.EventHandlerUnion; +} + +export interface CheckboxRootRenderProps + extends CheckboxRootCommonProps, + FormControlDataSet, + CheckboxDataSet { + children: JSX.Element; + role: "group"; +} + +export type CheckboxRootProps = CheckboxRootOptions & + Partial; /** * A control that allows the user to toggle between checked and not checked. */ -export function CheckboxRoot(props: CheckboxRootProps) { - let ref: HTMLDivElement | undefined; +export function CheckboxRoot( + props: PolymorphicProps, +) { + let ref: HTMLElement | undefined; const defaultId = `checkbox-${createUniqueId()}`; @@ -114,7 +132,7 @@ export function CheckboxRoot(props: CheckboxRootProps) { value: "on", id: defaultId, }, - props, + props as CheckboxRootProps, ); const [local, formControlProps, others] = splitProps( @@ -153,7 +171,7 @@ export function CheckboxRoot(props: CheckboxRootProps) { const onPointerDown: JSX.EventHandlerUnion = ( e, ) => { - callHandler(e, local.onPointerDown as typeof onPointerDown); + callHandler(e, local.onPointerDown); // For consistency with native, prevent the input blurs on pointer down. if (isFocused()) { @@ -182,7 +200,7 @@ export function CheckboxRoot(props: CheckboxRootProps) { return ( - as="div" ref={mergeRefs((el) => (ref = el), local.ref)} role="group" diff --git a/packages/core/src/checkbox/index.tsx b/packages/core/src/checkbox/index.tsx index 5c6c5f7e..ce5ba907 100644 --- a/packages/core/src/checkbox/index.tsx +++ b/packages/core/src/checkbox/index.tsx @@ -1,43 +1,77 @@ import { CheckboxControl as Control, + type CheckboxControlCommonProps, + type CheckboxControlOptions, type CheckboxControlProps, + type CheckboxControlRenderProps, } from "./checkbox-control"; import { CheckboxDescription as Description, + type CheckboxDescriptionCommonProps, + type CheckboxDescriptionOptions, type CheckboxDescriptionProps, + type CheckboxDescriptionRenderProps, } from "./checkbox-description"; import { CheckboxErrorMessage as ErrorMessage, + type CheckboxErrorMessageCommonProps, + type CheckboxErrorMessageOptions, type CheckboxErrorMessageProps, + type CheckboxErrorMessageRenderProps, } from "./checkbox-error-message"; import { CheckboxIndicator as Indicator, + type CheckboxIndicatorCommonProps, type CheckboxIndicatorOptions, type CheckboxIndicatorProps, + type CheckboxIndicatorRenderProps, } from "./checkbox-indicator"; import { CheckboxInput as Input, + type CheckboxInputCommonProps, type CheckboxInputOptions, type CheckboxInputProps, + type CheckboxInputRenderProps, } from "./checkbox-input"; import { CheckboxLabel as Label, + type CheckboxLabelCommonProps, + type CheckboxLabelOptions, type CheckboxLabelProps, + type CheckboxLabelRenderProps, } from "./checkbox-label"; import { CheckboxRoot as Root, + type CheckboxRootCommonProps, type CheckboxRootOptions, type CheckboxRootProps, + type CheckboxRootRenderProps, } from "./checkbox-root"; export type { + CheckboxControlOptions, + CheckboxControlCommonProps, + CheckboxControlRenderProps, CheckboxControlProps, + CheckboxDescriptionOptions, + CheckboxDescriptionCommonProps, + CheckboxDescriptionRenderProps, CheckboxDescriptionProps, + CheckboxErrorMessageOptions, + CheckboxErrorMessageCommonProps, + CheckboxErrorMessageRenderProps, CheckboxErrorMessageProps, CheckboxIndicatorOptions, + CheckboxIndicatorCommonProps, + CheckboxIndicatorRenderProps, CheckboxIndicatorProps, CheckboxInputOptions, + CheckboxInputCommonProps, + CheckboxInputRenderProps, CheckboxInputProps, + CheckboxLabelOptions, + CheckboxLabelCommonProps, + CheckboxLabelRenderProps, CheckboxLabelProps, CheckboxRootOptions, CheckboxRootProps, diff --git a/packages/core/src/collapsible/collapsible-content.tsx b/packages/core/src/collapsible/collapsible-content.tsx index d01c68a6..20e47cc2 100644 --- a/packages/core/src/collapsible/collapsible-content.tsx +++ b/packages/core/src/collapsible/collapsible-content.tsx @@ -6,14 +6,11 @@ * https://github.com/radix-ui/primitives/blob/21a7c97dc8efa79fecca36428eec49f187294085/packages/react/collapsible/src/Collapsible.tsx */ -import { - OverrideComponentProps, - mergeDefaultProps, - mergeRefs, -} from "@kobalte/utils"; +import { mergeDefaultProps, mergeRefs } from "@kobalte/utils"; import { JSX, Show, + ValidComponent, createEffect, createSignal, on, @@ -22,29 +19,41 @@ import { splitProps, } from "solid-js"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { createPresence } from "../primitives"; -import { useCollapsibleContext } from "./collapsible-context"; +import { + CollapsibleDataSet, + useCollapsibleContext, +} from "./collapsible-context"; -export interface CollapsibleContentOptions extends AsChildProp { - /** The HTML styles attribute (object form only). */ - style?: JSX.CSSProperties; +export interface CollapsibleContentOptions {} + +export interface CollapsibleContentCommonProps { + id: string; + ref: HTMLElement | ((el: HTMLElement) => void); + style: JSX.CSSProperties; } -export interface CollapsibleContentProps - extends OverrideComponentProps<"div", CollapsibleContentOptions> {} +export interface CollapsibleContentRenderProps + extends CollapsibleContentCommonProps, + CollapsibleDataSet {} + +export type CollapsibleContentProps = CollapsibleContentOptions & + Partial; /** * Contains the content to be rendered when the collapsible is expanded. */ -export function CollapsibleContent(props: CollapsibleContentProps) { - let ref: HTMLDivElement | undefined; +export function CollapsibleContent( + props: PolymorphicProps, +) { + let ref: HTMLElement | undefined; const context = useCollapsibleContext(); const mergedProps = mergeDefaultProps( { id: context.generateId("content") }, - props, + props as CollapsibleContentProps, ); const [local, others] = splitProps(mergedProps, ["ref", "id", "style"]); @@ -107,11 +116,11 @@ export function CollapsibleContent(props: CollapsibleContentProps) { ), ); - createEffect(() => onCleanup(context.registerContentId(local.id!))); + createEffect(() => onCleanup(context.registerContentId(local.id))); return ( - as="div" ref={mergeRefs((el) => { presence.setRef(el); diff --git a/packages/core/src/collapsible/collapsible-root.tsx b/packages/core/src/collapsible/collapsible-root.tsx index 4b00905d..b81c42e4 100644 --- a/packages/core/src/collapsible/collapsible-root.tsx +++ b/packages/core/src/collapsible/collapsible-root.tsx @@ -13,13 +13,14 @@ import { } from "@kobalte/utils"; import { Accessor, + ValidComponent, createMemo, createSignal, createUniqueId, splitProps, } from "solid-js"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { createDisclosureState, createRegisterId } from "../primitives"; import { CollapsibleContext, @@ -27,7 +28,7 @@ import { CollapsibleDataSet, } from "./collapsible-context"; -export interface CollapsibleRootOptions extends AsChildProp { +export interface CollapsibleRootOptions { /** The controlled open state of the collapsible. */ open?: boolean; @@ -50,16 +51,29 @@ export interface CollapsibleRootOptions extends AsChildProp { forceMount?: boolean; } -export interface CollapsibleRootProps - extends OverrideComponentProps<"div", CollapsibleRootOptions> {} +export interface CollapsibleRootCommonProps { + id: string; +} + +export interface CollapsibleRootRenderProps + extends CollapsibleRootCommonProps, + CollapsibleDataSet {} + +export type CollapsibleRootProps = CollapsibleRootOptions & + Partial; /** * An interactive component which expands/collapses a content. */ -export function CollapsibleRoot(props: CollapsibleRootProps) { +export function CollapsibleRoot( + props: PolymorphicProps, +) { const defaultId = `collapsible-${createUniqueId()}`; - const mergedProps = mergeDefaultProps({ id: defaultId }, props); + const mergedProps = mergeDefaultProps( + { id: defaultId }, + props as CollapsibleRootProps, + ); const [local, others] = splitProps(mergedProps, [ "open", @@ -96,7 +110,11 @@ export function CollapsibleRoot(props: CollapsibleRootProps) { return ( - + + as="div" + {...dataset()} + {...others} + /> ); } diff --git a/packages/core/src/collapsible/collapsible-trigger.tsx b/packages/core/src/collapsible/collapsible-trigger.tsx index 8b372e64..c555d17d 100644 --- a/packages/core/src/collapsible/collapsible-trigger.tsx +++ b/packages/core/src/collapsible/collapsible-trigger.tsx @@ -6,31 +6,52 @@ * https://github.com/radix-ui/primitives/blob/21a7c97dc8efa79fecca36428eec49f187294085/packages/react/collapsible/src/Collapsible.tsx */ -import { OverrideComponentProps, callHandler } from "@kobalte/utils"; -import { JSX, splitProps } from "solid-js"; +import { callHandler } from "@kobalte/utils"; +import { Component, JSX, ValidComponent, splitProps } from "solid-js"; import * as Button from "../button"; -import { AsChildProp } from "../polymorphic"; +import { PolymorphicProps } from "../polymorphic"; import { useCollapsibleContext } from "./collapsible-context"; -export interface CollapsibleTriggerProps - extends OverrideComponentProps<"button", AsChildProp> {} +export interface CollapsibleTriggerOptions {} + +export interface CollapsibleTriggerCommonProps + extends Button.ButtonRootCommonProps { + ref: HTMLElement | ((el: HTMLElement) => void); + onClick: JSX.EventHandlerUnion; +} + +export interface CollapsibleTriggerRenderProps + extends CollapsibleTriggerCommonProps, + Button.ButtonRootRenderProps { + "aria-expanded": boolean; + "aria-controls": string | undefined; +} + +export type CollapsibleTriggerProps = CollapsibleTriggerOptions & + Partial; /** * The button that expands/collapses the collapsible content. */ -export function CollapsibleTrigger(props: CollapsibleTriggerProps) { +export function CollapsibleTrigger( + props: PolymorphicProps, +) { const context = useCollapsibleContext(); const [local, others] = splitProps(props, ["onClick"]); const onClick: JSX.EventHandlerUnion = (e) => { - callHandler(e, local.onClick as typeof onClick); + callHandler(e, local.onClick); context.toggle(); }; return ( - + > + > aria-expanded={context.isOpen()} aria-controls={context.isOpen() ? context.contentId() : undefined} disabled={context.disabled()} diff --git a/packages/core/src/collapsible/index.tsx b/packages/core/src/collapsible/index.tsx index 2bd6e1fb..e3a02363 100644 --- a/packages/core/src/collapsible/index.tsx +++ b/packages/core/src/collapsible/index.tsx @@ -1,23 +1,37 @@ import { CollapsibleContent as Content, + type CollapsibleContentCommonProps, type CollapsibleContentOptions, type CollapsibleContentProps, + type CollapsibleContentRenderProps, } from "./collapsible-content"; import { CollapsibleRoot as Root, + type CollapsibleRootCommonProps, type CollapsibleRootOptions, type CollapsibleRootProps, + type CollapsibleRootRenderProps, } from "./collapsible-root"; import { CollapsibleTrigger as Trigger, + type CollapsibleTriggerCommonProps, + type CollapsibleTriggerOptions, type CollapsibleTriggerProps, + type CollapsibleTriggerRenderProps, } from "./collapsible-trigger"; export type { CollapsibleContentOptions, + CollapsibleContentCommonProps, + CollapsibleContentRenderProps, CollapsibleContentProps, CollapsibleRootOptions, + CollapsibleRootCommonProps, + CollapsibleRootRenderProps, CollapsibleRootProps, + CollapsibleTriggerOptions, + CollapsibleTriggerCommonProps, + CollapsibleTriggerRenderProps, CollapsibleTriggerProps, }; export { Content, Root, Trigger }; diff --git a/packages/core/src/combobox/combobox-base.tsx b/packages/core/src/combobox/combobox-base.tsx index b048596f..769e746c 100644 --- a/packages/core/src/combobox/combobox-base.tsx +++ b/packages/core/src/combobox/combobox-base.tsx @@ -8,7 +8,6 @@ */ import { - OverrideComponentProps, ValidationState, access, createGenerateId, @@ -21,6 +20,7 @@ import { Accessor, Component, JSX, + ValidComponent, createEffect, createMemo, createSignal, @@ -32,12 +32,13 @@ import { import { FORM_CONTROL_PROP_NAMES, FormControlContext, + FormControlDataSet, createFormControl, } from "../form-control"; import { createFilter } from "../i18n"; import { ListKeyboardDelegate, createListState } from "../list"; import { announce } from "../live-announcer"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { PopperRoot, PopperRootOptions } from "../popper"; import { CollectionNode, @@ -67,22 +68,21 @@ import { } from "./combobox.intl"; import { ComboboxTriggerMode } from "./types"; -export interface ComboboxBaseItemComponentProps { +export interface ComboboxBaseItemComponentProps