- Replace .eslintrc.cjs with eslint.config.mjs (ESLint 9 flat config)
using direct eslint-plugin-solid + @typescript-eslint/parser approach
- Add @typescript-eslint/parser to root devDependencies
- Add main/module/types top-level fields to packages/core/package.json
- Add resolve.conditions to packages/core/vite.config.ts
- Create packages/core/tsconfig.test.json for test type-checking
- Remove empty paths:{} from packages/core/tsconfig.json
355 lines
11 KiB
Markdown
355 lines
11 KiB
Markdown
# Polymorphism
|
|
|
|
All component parts that render a DOM element have an `as` prop.
|
|
|
|
## The `as` prop
|
|
|
|
For simple use cases the `as` prop can be used, either with native HTML elements or custom Solid components:
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs } from "@kobalte/core/tabs";
|
|
import { MyCustomButton } from "./components";
|
|
|
|
function App() {
|
|
return (
|
|
<Tabs>
|
|
<Tabs.List>
|
|
{/* Render an anchor tag instead of the default button */}
|
|
<Tabs.Trigger value="one" as="a">
|
|
A Trigger
|
|
</Tabs.Trigger>
|
|
|
|
{/* Render MyCustomButton instead of the default button */}
|
|
<Tabs.Trigger value="one" as={MyCustomButton}>
|
|
Custom Button Trigger
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Content value="one">Content one</Tabs.Content>
|
|
</Tabs>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs } from "@kobalte/core/tabs";
|
|
import { MyCustomButton } from "./components";
|
|
|
|
function App() {
|
|
return (
|
|
<Tabs>
|
|
<Tabs.List>
|
|
{/* Render an anchor tag instead of the default button */}
|
|
<Tabs.Trigger value="one" as="a">
|
|
A Trigger
|
|
</Tabs.Trigger>
|
|
|
|
{/* Render MyCustomButton instead of the default button */}
|
|
<Tabs.Trigger value="one" as={MyCustomButton}>
|
|
Custom Button Trigger
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Content value="one">Content one</Tabs.Content>
|
|
</Tabs>
|
|
);
|
|
}
|
|
```
|
|
|
|
## The `as` prop callback
|
|
|
|
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 callback:
|
|
|
|
- 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.
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs } from "@kobalte/core/tabs";
|
|
import { MyCustomButton } from "./components";
|
|
|
|
function App() {
|
|
return (
|
|
<Tabs>
|
|
<Tabs.List>
|
|
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
|
|
<Tabs.Trigger value="one" as={MyCustomButton}>
|
|
A Trigger
|
|
</Tabs.Trigger>
|
|
|
|
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
|
|
<Tabs.Trigger
|
|
value="one"
|
|
as={props => (
|
|
// The `value` prop is directly passed to MyCustomButton
|
|
<MyCustomButton value="custom" {...props} />
|
|
)}
|
|
>
|
|
Custom Button Trigger
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Content value="one">Content one</Tabs.Content>
|
|
</Tabs>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs } from "@kobalte/core/tabs";
|
|
import { MyCustomButton } from "./components";
|
|
|
|
function App() {
|
|
return (
|
|
<Tabs>
|
|
<Tabs.List>
|
|
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
|
|
<Tabs.Trigger value="one" as={MyCustomButton}>
|
|
A Trigger
|
|
</Tabs.Trigger>
|
|
|
|
{/* The `value` prop is used by Kobalte and not passed to MyCustomButton */}
|
|
<Tabs.Trigger
|
|
value="one"
|
|
as={props => (
|
|
// The `value` prop is directly passed to MyCustomButton
|
|
<MyCustomButton value="custom" {...props} />
|
|
)}
|
|
>
|
|
Custom Button Trigger
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Content value="one">Content one</Tabs.Content>
|
|
</Tabs>
|
|
);
|
|
}
|
|
```
|
|
|
|
You can optionally use a type helper to get the exact types passed to your callback:
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs, TabsTriggerOptions, TabsTriggerRenderProps } from "@kobalte/core/tabs";
|
|
import { PolymorphicCallbackProps } from "@kobalte/core/polymorphic";
|
|
|
|
<Tabs.Trigger
|
|
value="one"
|
|
as={(
|
|
props: PolymorphicCallbackProps<
|
|
MyCustomButtonProps,
|
|
TabsTriggerOptions,
|
|
TabsTriggerRenderProps
|
|
>,
|
|
) => (
|
|
// The `value` prop is directly passed to MyCustomButton
|
|
<MyCustomButton value="custom" {...props} />
|
|
)}
|
|
>
|
|
Custom Button Trigger
|
|
</Tabs.Trigger>;
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs, TabsTriggerOptions, TabsTriggerRenderProps } from "@kobalte/core/tabs";
|
|
import { PolymorphicCallbackProps } from "@kobalte/core/polymorphic";
|
|
|
|
<Tabs.Trigger
|
|
value="one"
|
|
as={(
|
|
props: PolymorphicCallbackProps<
|
|
MyCustomButtonProps,
|
|
TabsTriggerOptions,
|
|
TabsTriggerRenderProps
|
|
>,
|
|
) => (
|
|
// The `value` prop is directly passed to MyCustomButton
|
|
<MyCustomButton value="custom" {...props} />
|
|
)}
|
|
>
|
|
Custom Button Trigger
|
|
</Tabs.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<T>`
|
|
- `ComponentRenderProps`
|
|
- `ComponentProps<T>`
|
|
|
|
For example, `Tabs.Trigger` has the types `TabsTriggerOptions`, `TabsTriggerCommonProps<T>`,
|
|
`TabsTriggerRenderProps` and `TabsTriggerProps<T>`.
|
|
|
|
Components themselves accept props as `PolymorphicProps<T, ComponentProps>` where `T` is a generic
|
|
that extends `ValidComponent` and `ComponentProps` are the props 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<T>`
|
|
|
|
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. The generic is used by `ref` and event handlers,
|
|
by default it is `HTMLElement`.
|
|
|
|
### `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 accessibility.
|
|
|
|
### `ComponentProps<T>`
|
|
|
|
This is the final type exported by components, it is equal to `ComponentOptions & Partial<ComponentCommonProps>`.
|
|
It combines all props expected by Kobalte's component. The generic is used by the CommonProps, by default it is `HTMLElement`.
|
|
|
|
### `PolymorphicProps<T, ComponentProps>`
|
|
|
|
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<T, ComponentProps>` for your props type.
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs, TabsTriggerProps } from "@kobalte/core/tabs";
|
|
import { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
|
|
// Optionally extend `TabsTriggerProps` if you wish to
|
|
// expose Kobalte props to your end user.
|
|
interface CustomProps<T extends ValidComponent = "button"> extends TabsTriggerProps<T> {
|
|
variant: "default" | "outline";
|
|
}
|
|
|
|
// Your generic `T` should extend ValidComponent and have a default value of the default DOM node.
|
|
function CustomTabsTrigger<T extends ValidComponent = "button">(
|
|
props: PolymorphicProps<T, CustomProps<T>>,
|
|
) {
|
|
// 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 (
|
|
<Tabs.Trigger
|
|
// Optional, will default to Kobalte otherwise.
|
|
// This should match with your generic `T` default.
|
|
as="button"
|
|
class={local.variant === "default" ? "default-trigger" : "outline-trigger"}
|
|
// Make sure to spread these props!
|
|
{...others}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
import { Tabs, TabsTriggerProps } from "@kobalte/core/tabs";
|
|
import { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
|
|
// Optionally extend `TabsTriggerProps` if you wish to
|
|
// expose Kobalte props to your end user.
|
|
interface CustomProps<T extends ValidComponent = "button"> extends TabsTriggerProps<T> {
|
|
variant: "default" | "outline";
|
|
}
|
|
|
|
// Your generic `T` should extend ValidComponent and have a default value of the default DOM node.
|
|
function CustomTabsTrigger<T extends ValidComponent = "button">(
|
|
props: PolymorphicProps<T, CustomProps<T>>,
|
|
) {
|
|
// 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 (
|
|
<Tabs.Trigger
|
|
// Optional, will default to Kobalte otherwise.
|
|
// This should match with your generic `T` default.
|
|
as="button"
|
|
class={local.variant === "default" ? "default-trigger" : "outline-trigger"}
|
|
// Make sure to spread these props!
|
|
{...others}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
If you do not wish to allow changing the element type, you can simplify your types by making
|
|
props: `OverrideComponentProps<"button", CustomProps>`, replace `"button"` with the correct
|
|
tagname for other components, imported from `"@kobalte/utils"`.
|
|
|
|
If you also want to export exact types, you can re-export and extends component types:
|
|
|
|
```
|
|
Copytsx
|
|
export interface CustomTabsTriggerOptions extends TabsTriggerOptions {
|
|
variant: "default" | "outline";
|
|
}
|
|
|
|
export interface CustomTabsTriggerCommonProps<T extends HTMLElement = HTMLElement> extends TabsTriggerCommonProps<T> {
|
|
// If you allow users to set classes and extend them.
|
|
//class: string;
|
|
}
|
|
|
|
export interface CustomTabsTriggerRenderProps
|
|
extends CustomTabsTriggerCommonProps,
|
|
TabsTriggerRenderProps {
|
|
// If you do not allow users to set classes and manage all of them.
|
|
class: string;
|
|
}
|
|
|
|
export type CustomTabsTriggerProps<T extends ValidComponent = "button"> = CustomTabsTriggerOptions &
|
|
Partial<CustomTabsTriggerCommonProps<ElementOf<T>>>;
|
|
|
|
export function CustomTabsTrigger<T extends ValidComponent = "button">(
|
|
props: PolymorphicProps<T, CustomTabsTriggerProps<T>,
|
|
) {}
|
|
```
|
|
|
|
```
|
|
Copytsx
|
|
export interface CustomTabsTriggerOptions extends TabsTriggerOptions {
|
|
variant: "default" | "outline";
|
|
}
|
|
|
|
export interface CustomTabsTriggerCommonProps<T extends HTMLElement = HTMLElement> extends TabsTriggerCommonProps<T> {
|
|
// If you allow users to set classes and extend them.
|
|
//class: string;
|
|
}
|
|
|
|
export interface CustomTabsTriggerRenderProps
|
|
extends CustomTabsTriggerCommonProps,
|
|
TabsTriggerRenderProps {
|
|
// If you do not allow users to set classes and manage all of them.
|
|
class: string;
|
|
}
|
|
|
|
export type CustomTabsTriggerProps<T extends ValidComponent = "button"> = CustomTabsTriggerOptions &
|
|
Partial<CustomTabsTriggerCommonProps<ElementOf<T>>>;
|
|
|
|
export function CustomTabsTrigger<T extends ValidComponent = "button">(
|
|
props: PolymorphicProps<T, CustomTabsTriggerProps<T>,
|
|
) {}
|
|
```
|
|
|
|
`ElementOf<T>` is a helper from `"@kobalte/core/polymorphic"` that converts a tag name into its element
|
|
(e.g. `ElementOf<"button"> = HTMLButtonElement`).
|
|
|
|
Previous[←Animation](https://kobalte.dev/docs/core/overview/animation)Next[Server side rendering→](https://kobalte.dev/docs/core/overview/ssr) |