vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
418 lines (369 loc) • 9.55 kB
text/typescript
import { ref } from "vue";
import {
getArgs,
getArgTypes,
getSlotNames,
getSlotsFragment,
getDocsDescription,
} from "../../utils/storybook";
import UModal from "../../ui.container-modal/UModal.vue";
import UButton from "../../ui.button/UButton.vue";
import UIcon from "../../ui.image-icon/UIcon.vue";
import UHeader from "../../ui.text-header/UHeader.vue";
import UInput from "../../ui.form-input/UInput.vue";
import URow from "../../ui.container-row/URow.vue";
import UCol from "../../ui.container-col/UCol.vue";
import UText from "../../ui.text-block/UText.vue";
import UInputPassword from "../../ui.form-input-password/UInputPassword.vue";
import UCheckbox from "../../ui.form-checkbox/UCheckbox.vue";
import UDivider from "../../ui.container-divider/UDivider.vue";
import type { Meta, StoryFn } from "@storybook/vue3-vite";
import type { Props } from "../types";
import type { UnknownObject } from "../../types";
interface UModalArgs extends Props {
slotTemplate?: string;
enum: "size" | "variant";
modelValues?: UnknownObject;
}
export default {
id: "5070",
title: "Containers / Modal",
component: UModal,
args: {
title: "Sign Up",
modelValue: true,
config: { innerWrapper: "h-max" },
},
argTypes: {
...getArgTypes(UModal.__name),
},
parameters: {
docs: {
...getDocsDescription(UModal.__name),
story: {
height: "500px",
},
},
},
} as Meta;
const defaultTemplate = `
<UCol
justify="between"
align="stretch"
class="h-full"
>
<URow
align="center"
justify="between"
gap="sm"
>
<UButton
label="GitHub"
variant="outlined"
block
class="!leading-none"
/>
<UButton
label="Google"
variant="outlined"
block
class="!leading-none"
/>
</URow>
<UDivider label="OR CONTINUE WITH" />
<UCol gap="sm" block>
<UInput
label="Email"
placeholder="johndoe@example.com"
type="email"
/>
<UInputPassword v-model="password" label="Password" />
</UCol>
<UButton label="Create account" block class="mt-4" />
</UCol>
`;
const password = ref("");
function resetBodyOverflow(delay = 500) {
setTimeout(() => {
document.body.style.overflow = "auto";
}, delay);
}
const DefaultTemplate: StoryFn<UModalArgs> = (args: UModalArgs) => ({
components: {
UModal,
URow,
UCol,
UButton,
UIcon,
UHeader,
UInput,
UInputPassword,
UCheckbox,
UDivider,
},
setup() {
function onClick() {
args.modelValue = true;
resetBodyOverflow();
}
const slots = getSlotNames(UModal.__name);
return { args, slots, onClick, password };
},
template: `
<div>
<UModal v-bind="args" v-model="args.modelValue" size="sm">
${args.slotTemplate || getSlotsFragment(defaultTemplate)}
</UModal>
<UButton label="Show modal" @click="onClick" />
</div>
`,
});
const EnumTemplate: StoryFn<UModalArgs> = (args: UModalArgs, { argTypes }) => ({
components: {
UModal,
UButton,
URow,
UInput,
UCol,
UCheckbox,
UInputPassword,
UDivider,
},
setup: () => ({ args, argTypes, getArgs, password }),
template: `
<URow>
<UButton
v-for="option in argTypes?.[args.enum]?.options"
:key="option"
:label="option"
@click="args.modelValues[option] = !args.modelValues[option]"
/>
<UModal
v-for="option in argTypes?.[args.enum]?.options"
:key="option"
v-bind="getArgs(args, option)"
v-model="args.modelValues[option]"
>
${defaultTemplate}
</UModal>
</URow>
`,
});
export const Default = DefaultTemplate.bind({});
Default.args = {};
export const Description = DefaultTemplate.bind({});
Description.args = {
description: "Enter your email below to get started and create your account.",
};
export const NoCloseOnEscAndOverlay = DefaultTemplate.bind({});
NoCloseOnEscAndOverlay.args = {
closeOnEsc: false,
closeOnOverlay: false,
};
export const Inner: StoryFn<UModalArgs> = (args: UModalArgs) => ({
components: { UModal, UButton, UCol, UText, URow },
setup() {
const showMainModal = ref(true);
const showInnerModal = ref(true);
function openMainModal() {
showMainModal.value = true;
resetBodyOverflow();
}
function openInnerModal() {
showInnerModal.value = true;
resetBodyOverflow();
}
return { args, showMainModal, showInnerModal, openMainModal, openInnerModal };
},
template: `
<div>
<UModal title="Subscription canceling" v-model="showMainModal">
<UCol gap="sm">
<UText
label="
Are you sure you want to cancel your subscription?
This action will remove access to premium features and cannot be undone.
"
/>
<UButton
label="View Plan Details"
variant="outlined"
color="neutral"
size="sm"
@click="openInnerModal"
/>
</UCol>
<UModal
v-model="showInnerModal"
title="Current Plan Details"
description="
Your current plan includes unlimited access to premium content,
priority support, and exclusive features.
"
inner
>
<UText label="Consider downgrading instead of canceling." />
<template #footer-right>
<URow>
<UButton label="Downgrade" color="neutral" variant="subtle" />
</URow>
</template>
</UModal>
<template #footer-right>
<UButton label="Cancel subscription" color="error" variant="subtle" @click="showMainModal = false" />
</template>
</UModal>
<UButton label="Manage Subscription" @click="openMainModal"/>
</div>
`,
});
Inner.parameters = {
docs: {
description: {
story: "Add extra top margin for modal inside another modal.",
},
},
};
export const WithoutDivider = DefaultTemplate.bind({});
WithoutDivider.args = {
divided: false,
slotTemplate: `
${defaultTemplate}
<template #footer-left>
<UButton label="Back" color="neutral" variant="subtle" block />
</template>`,
};
WithoutDivider.parameters = {
docs: {
description: {
story: "Hide divider between content and footer.",
},
story: {
height: "550px",
},
},
};
export const Sizes = EnumTemplate.bind({});
Sizes.args = { enum: "size", modelValues: {} };
export const Variants = EnumTemplate.bind({});
Variants.args = { enum: "variant", modelValues: {} };
export const BackLink: StoryFn<UModalArgs> = (args: UModalArgs) => ({
components: { UModal, UButton, UCheckbox, UCol, URow, UDivider, UInput, UInputPassword },
setup() {
function onClick() {
args.modelValue = true;
}
const showAlert = () => alert("You clicked on a back link!");
return { args, onClick, password, showAlert };
},
template: `
<div>
<UModal
v-bind="args"
v-model="args.modelValue"
size="sm"
backLabel="Back"
:backTo="{ path: '/' }"
@back="showAlert"
>
${defaultTemplate}
</UModal>
<UButton label="Show modal" @click="onClick"/>
</div>
`,
});
BackLink.parameters = {
docs: {
description: {
story:
"Use `backTo` and `backLabel` props to add a route object and a label to the back link.",
},
},
};
export const BeforeTitleSlot = DefaultTemplate.bind({});
BeforeTitleSlot.args = {
slotTemplate: `
<template #before-title>
<UIcon name="account_circle" size="sm" color="primary" />
</template>
<template #default>
${defaultTemplate}
</template>
`,
};
export const TitleSlot = DefaultTemplate.bind({});
TitleSlot.args = {
slotTemplate: `
<template #title="{ title }">
<UHeader :label="title" color="primary" />
</template>
<template #default>
${defaultTemplate}
</template>
`,
};
export const AfterTitleSlot = DefaultTemplate.bind({});
AfterTitleSlot.args = {
slotTemplate: `
<template #after-title>
<UIcon name="verified" size="sm" color="primary" />
</template>
<template #default>
${defaultTemplate}
</template>
`,
};
export const ActionsSlot = DefaultTemplate.bind({});
ActionsSlot.args = {
config: { closeButton: "p-0" },
slotTemplate: `
<template #actions="{ close }">
<UButton
label="Close"
size="sm"
color="grayscale"
variant="subtle"
@click="close"
/>
</template>
<template #default>
${defaultTemplate}
</template>
`,
};
export const FooterLeftSlot = DefaultTemplate.bind({});
FooterLeftSlot.args = {
slotTemplate: `
<template #default>
${defaultTemplate}
</template>
<template #footer-left>
<UButton label="Back" variant="subtle" color="neutral" />
</template>
`,
};
FooterLeftSlot.parameters = {
docs: {
story: {
height: "600px",
},
},
};
export const FooterRightSlot = DefaultTemplate.bind({});
FooterRightSlot.args = {
slotTemplate: `
<template #default>
${defaultTemplate}
</template>
<template #footer-right>
<UButton label="Not now" variant="subtle" color="neutral" />
</template>
`,
};
FooterRightSlot.parameters = {
docs: {
story: {
height: "600px",
},
},
};