Skip to content

Commit

Permalink
feat(experimental/primitives): dialog primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
gravitano committed Oct 20, 2023
1 parent f930812 commit d3fe469
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 0 deletions.
77 changes: 77 additions & 0 deletions packages/primitives/src/dialog/dialog.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type {Story} from '@storybook/vue3';
import {
Dialog,
DialogPanel,
DialogTitle,
DialogDescription,
DialogOverlay,
} from './dialog';
import {ref} from 'vue';

export default {
title: 'Primitives/Dialog',
component: Dialog,
subcomponents: {},
};

export const Default: Story = (args) => ({
components: {
Dialog,
DialogPanel,
DialogTitle,
DialogDescription,
DialogOverlay,
},
setup() {
const isOpen = ref(true);
return {args, isOpen};
},
template: `
<button @click="isOpen = !isOpen" class="bg-white border rounded-lg px-3 py-2 text-sm font-medium">
Delete account
</button>
<Dialog
v-bind="args"
v-model="isOpen"
>
<transition name="fade">
<DialogOverlay
class="fixed inset-0 bg-black/50 z-10"
@click="isOpen = false"
/>
</transition>
<transition name="fade">
<DialogPanel
class="
fixed z-20 w-[500px] bg-white rounded-lg shadow-lg p-4
"
:style="{
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
}"
>
<DialogTitle class="font-medium">
Delete account
</DialogTitle>
<DialogDescription class="text-gray-700 mt-2 text-sm">
Are you sure you want to delete your account?
</DialogDescription>
<div class="flex gap-3 mt-4 justify-center">
<button class="border bg-white px-3 py-2 rounded-lg text-sm font-medium"
@click="isOpen = false">
Cancel
</button>
<button class="border bg-white text-red-600 px-3 py-2 rounded-lg text-sm font-medium"
@click="isOpen = false">
Delete
</button>
</div>
</div>
</DialogPanel>
</transition>
</Dialog>
`,
});
Empty file.
197 changes: 197 additions & 0 deletions packages/primitives/src/dialog/dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import {
defineComponent,
provide,
ref,
inject,
watch,
Ref,
InjectionKey,
h,
Teleport,
Transition,
} from 'vue';

export interface DialogContext {
open: Ref<boolean>;
show: () => void;
hide: () => void;
}

export const DialogInjectionKey = Symbol(
'DialogContext',
) as InjectionKey<DialogContext>;

export function useDialog() {
return inject<DialogContext>(DialogInjectionKey) as DialogContext;
}

export const Dialog = defineComponent({
name: 'Dialog',
props: {
modelValue: {
type: Boolean,
default: false,
},
},
emits: {
'update:modelValue'(_payload: boolean) {
return true;
},
},
setup(props, {slots, emit}) {
const open = ref(props.modelValue);

watch(
() => props.modelValue,
(newValue) => {
open.value = newValue;
},
);

watch(open, (newValue) => {
if (newValue !== props.modelValue) {
emit('update:modelValue', newValue);
}
});

function show() {
open.value = true;
}

function hide() {
open.value = false;
}

const context: DialogContext = {
open,
show,
hide,
};

provide(DialogInjectionKey, context);

return () => h(Teleport, {to: 'body'}, slots.default?.({open}));
},
});

export const DialogTitle = defineComponent({
name: 'DialogTitle',
props: {
as: {
type: String,
default: 'h2',
},
},
setup(props, {slots}) {
const {open} = useDialog();
return () =>
h(
props.as,
{
'data-state': open.value ? 'open' : 'closed',
},
slots.default?.({
open,
}),
);
},
});

export const DialogDescription = defineComponent({
name: 'DialogDescription',
props: {
as: {
type: String,
default: 'p',
},
},
setup(props, {slots}) {
const {open} = useDialog();
return () =>
h(
props.as,
{
'data-state': open.value ? 'open' : 'closed',
},
slots.default?.({
open,
}),
);
},
});

export const DialogPanel = defineComponent({
name: 'DialogPanel',
props: {
as: {
type: String,
default: 'div',
},
},
setup(props, {slots}) {
const {open} = useDialog();
return () =>
open.value
? h(
props.as,
{
'data-state': open.value ? 'open' : 'closed',
role: 'dialog',
},
slots.default?.({
open,
}),
)
: null;
},
});

export const DialogContent = defineComponent({
name: 'DialogContent',
props: {
as: {
type: String,
default: 'div',
},
},
setup(props, {slots}) {
const {open} = useDialog();
return () =>
open.value
? h(
props.as,
{
'data-state': open.value ? 'open' : 'closed',
},
slots.default?.({
open,
}),
)
: null;
},
});

export const DialogOverlay = defineComponent({
name: 'DialogOverlay',
props: {
as: {
type: String,
default: 'div',
},
},
setup(props, {slots}) {
const {open} = useDialog();
return () =>
open.value
? h(
props.as,
{
'data-state': open.value ? 'open' : 'closed',
},
slots.default?.({
open,
}),
)
: null;
},
});
1 change: 1 addition & 0 deletions packages/primitives/src/dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dialog';

0 comments on commit d3fe469

Please sign in to comment.