WARNING: This component is in
Draft
status. This means that it is still in development and may have bugs or missing features. It is not intended to be used in production. You may use it for testing purposes.Modal
A window overlaid on either the primary window or another modal window, rendering the content underneath inert.
import { component$, useSignal } from '@builder.io/qwik';
import { cn } from '@qwik-ui/utils';
import { LuX } from '@qwikest/icons/lucide';
import { Button, Input, Label, Modal, buttonVariants } from '~/components/ui';
export default component$(() => {
const show = useSignal(false);
return (
<Modal.Root bind:show={show}>
<Modal.Trigger class={[buttonVariants({ look: 'outline' })]}>
Open modal
</Modal.Trigger>
<Modal.Panel>
<Modal.Title>Edit Profile</Modal.Title>
<Modal.Description>
Make changes to your profile here. Click save when you're done.
</Modal.Description>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">
Name
</Label>
<Input name="name" id="name" defaultValue="Pedro Duarte" class="col-span-3" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">
Username
</Label>
<Input
name="username"
id="username"
defaultValue="@peduarte"
class="col-span-3"
/>
</div>
</div>
<footer>
<Button look="primary" onClick$={() => (show.value = false)}>
Save
</Button>
</footer>
<Modal.Close
class={cn(
buttonVariants({ size: 'icon', look: 'link' }),
'absolute right-3 top-2',
)}
type="submit"
>
<LuX class="h-5 w-5" />
</Modal.Close>
</Modal.Panel>
</Modal.Root>
);
});
Installation
Run the following cli command or copy/paste the component code into your project
qwik-ui add modal
import { type PropsOf, Slot, component$ } from '@builder.io/qwik';
import { Modal as HeadlessModal } from '@qwik-ui/headless';
import { cn } from '@qwik-ui/utils';
import { cva, type VariantProps } from 'class-variance-authority';
const Root = HeadlessModal.Root;
const Trigger = HeadlessModal.Trigger;
const Close = HeadlessModal.Close;
export const panelVariants = cva(
[
'fixed w-full bg-background p-6 text-foreground transition-all backdrop:brightness-50 backdrop:backdrop-blur-sm',
'data-[closing]:duration-300 data-[open]:duration-300 data-[open]:animate-in data-[closing]:animate-out',
'backdrop:data-[closing]:duration-300 backdrop:data-[open]:duration-300 backdrop:data-[open]:animate-in backdrop:data-[closing]:animate-out backdrop:data-[closing]:fade-out backdrop:data-[open]:fade-in',
],
{
variants: {
position: {
center:
'max-w-lg rounded-base shadow-lg data-[state=closed]:fade-out data-[state=open]:fade-in data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-bottom-2 backdrop:data-[closing]:fade-out backdrop:data-[open]:fade-in',
top: 'inset-x-0 top-0 mt-0 rounded-b-base border-b data-[closing]:slide-out-to-top data-[open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 mb-0 rounded-t-base border-t data-[closing]:slide-out-to-bottom data-[open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 ml-0 h-full max-w-sm rounded-r-base border-r data-[closing]:slide-out-to-left data-[open]:slide-in-from-left',
right:
'inset-y-0 right-0 mr-0 h-full max-w-sm rounded-l-base border-l data-[closing]:slide-out-to-right data-[open]:slide-in-from-right',
},
},
defaultVariants: {
position: 'center',
},
},
);
type PanelProps = PropsOf<typeof HeadlessModal.Panel> &
VariantProps<typeof panelVariants>;
const Panel = component$<PanelProps>(({ position, ...props }) => {
return (
<HeadlessModal.Panel {...props} class={cn(panelVariants({ position }), props.class)}>
<Slot />
</HeadlessModal.Panel>
);
});
const Title = component$<PropsOf<'h2'>>(({ ...props }) => {
return (
<HeadlessModal.Title
{...props}
class={cn('text-lg font-semibold tracking-tight', props.class)}
>
<Slot />
</HeadlessModal.Title>
);
});
const Description = component$<PropsOf<'p'>>(({ ...props }) => {
return (
<HeadlessModal.Description
{...props}
class={cn('text-sm text-muted-foreground', props.class)}
>
<Slot />
</HeadlessModal.Description>
);
});
export const Modal = {
Root,
Trigger,
Close,
Panel,
Title,
Description,
};
Usage
import { Button, Modal } from '~/components/ui';
<Modal.Root bind:show={show}>
<Modal.Trigger>Open modal</Modal.Trigger>
<Modal.Panel>
<Modal.Title>Title</Modal.Title>
<Modal.Description>Description</Modal.Description>
<div>...</div>
<footer>
<Button look="primary" onClick$={() => (show.value = false)}>
Save
</Button>
</footer>
<Modal.Close>
<LuX class="h-5 w-5" />
</Modal.Close>
</Modal.Panel>
</Modal.Root>
Examples
import { PropsOf, Slot, component$, useSignal } from '@builder.io/qwik';
import { cn } from '@qwik-ui/utils';
import { LuX } from '@qwikest/icons/lucide';
import { Button, Input, Label, Modal, buttonVariants } from '~/components/ui';
export default component$(() => {
return (
<>
<Sheet position="top">Top</Sheet>
<div class="my-4 flex space-x-24">
<Sheet position="left">Left</Sheet>
<Sheet position="right">Right</Sheet>
</div>
<Sheet position="bottom">Bottom</Sheet>
</>
);
});
export const Sheet = component$<PropsOf<typeof Modal.Panel>>(({ ...props }) => {
const show = useSignal(false);
return (
<Modal.Root bind:show={show}>
<Modal.Trigger class={[buttonVariants({ look: 'ghost' }), 'w-20']}>
<Slot />
</Modal.Trigger>
<Modal.Panel {...props}>
<Modal.Title>Edit Profile</Modal.Title>
<Modal.Description>
Make changes to your profile here. Click save when you're done.
</Modal.Description>
<div class="mt-6">
<Label for="name" class="text-right">
Name
</Label>
<Input
name="name"
id="name"
defaultValue="Pedro Duarte"
class="col-span-3 w-[300px]"
/>
</div>
<footer class="mt-6">
<Button look="primary" onClick$={() => (show.value = false)}>
Save
</Button>
</footer>
<Modal.Close
class={cn(
buttonVariants({ size: 'icon', look: 'link' }),
'absolute right-3 top-2',
)}
type="submit"
>
<LuX class="h-5 w-5" />
</Modal.Close>
</Modal.Panel>
</Modal.Root>
);
});