Dynamic Components
All primitive components that render a DOM element are dynamic, which means that you can modify the element or the component they should render as.
Native elements Section titled Native elements
In most cases, you shouldn’t need to change the DOM element that the primitive component renders. corvu has sensible defaults for all components. But there are cases where it makes sense to change them. An example would be the Tooltip
trigger which renders as a button
element. You may want to render a tooltip on a link (a
tag) instead. To do this, you have to specify the as
property on the trigger component:
<Tooltip.Trigger as="a" href="https://corvu.dev">
corvu.dev
</Tooltip.Trigger>
Solid components Section titled Solid components
A much more common use case is to render a primitive component as a custom Solid component. This is useful to apply default styling or to add additional functionality.
For example, you might have your own, custom-styled button component and want to use it as a trigger for a dialog:
import {
ComponentProps,
splitProps,
} from 'solid-js'
import Dialog from '@corvu/dialog'
const CustomButton = (
props: ComponentProps<'button'> & { variant: 'fill' | 'outline' },
) => {
const [local, rest] = splitProps(props, ['variant'])
// Apply your custom styling here
return <button class={local.variant} {...rest} />
}
const DialogTrigger = () => (
<Dialog.Trigger as={CustomButton} variant="outline">
Open
</Dialog.Trigger>
)
Props not belonging to the primitive component will be passed through to your custom component. In this case, the variant
prop is passed to the CustomButton
component.
❗ To ensure functionality and accessibility, your component needs to spread the received props onto your element. Otherwise, corvu can’t define props on the element and things will break.
Component types Section titled Component types
corvu’s dynamic components have a flexible type system. This allows library developers or users who want to create their own components based on corvu’s primitives to have a great developer experience.
Every dynamic component exposes 4 types.
For example, the <Dialog.Trigger>
component exports the types DialogTriggerCorvuProps
, DialogTriggerSharedElementProps<T>
, DialogTriggerElementProps
and DialogTriggerProps<T>
.
Lets have a look at the types:
CorvuProps Section titled CorvuProps
CorvuProps
contains all props that are specific to the primitive component and are not passed through to the rendered element. They are consumed by corvu. For example, the <Resizable.Handle />
has props like minSize
or collapsible
.
SharedElementProps<T extends ValidComponent> Section titled SharedElementProps<T extends ValidComponent>
SharedElementProps
includes all props that get defined by corvu on the rendered element but can be overridden by the user. This usually includes the ref
or style
properties. The generic is used to properly type ref
and event listeners.
ElementProps Section titled ElementProps
ElementProps
element props inherits all SharedElementProps
and additionally includes all props that are set by corvu and can’t be overridden by the user. This includes for example accessibility props like aria-*
, role
and data-*
.
Props<T extends ValidComponent> Section titled Props<T extends ValidComponent>
This is the type that defines what props that corvu expects from the user. It’s equal to CorvuProps & Partial<SharedElementProps>
.
DynamicProps Section titled DynamicProps
DynamicProps
is a helper type that allows you to expose the dynamic aspect of corvu components from your custom component. Let’s look at an example:
import {
type ValidComponent,
ComponentProps,
splitProps,
} from 'solid-js'
import Dialog, { type TriggerProps, type DynamicProps } from '@corvu/dialog'
// Define your custom props, including `TriggerProps` from corvu
export type CustomDisclosureTriggerProps<T extends ValidComponent = 'button'> = TriggerProps<T> & {
variant: 'fill' | 'outline'
}
// The generic `T` allows the user to specify
// the element this component should render as
const CustomDialogTrigger = <T extends ValidComponent = 'button'>(
props: DynamicProps<T, CustomDisclosureTriggerProps<T>>,
) => {
const [local, rest] = splitProps(props as CustomDisclosureTriggerProps, [
'variant',
])
// Define the default dynamic type, in this case 'button'.
// This can be overridden by the user.
return <Dialog.Trigger as="button" class={local.variant} {...rest} />
}
If you don’t want to expose the dynamic aspect of corvu’s components, you can define the generic explicitly:
const CustomDialogTrigger = (
props: DynamicProps<'button', CustomDisclosureTriggerProps<'button'>>,
) => {
Developed and designed by Jasmin