chore: ad release label

This commit is contained in:
Bereket Engida
2024-09-27 13:36:20 +03:00
parent d49330e8a1
commit afd51708be
341 changed files with 4644 additions and 3805 deletions

View File

@@ -11,7 +11,7 @@
"editor.defaultFormatter": "biomejs.biome" "editor.defaultFormatter": "biomejs.biome"
}, },
"[vue]": { "[vue]": {
"editor.defaultFormatter": "Vue.volar" "editor.defaultFormatter": "biomejs.biome"
}, },
"[json]": { "[json]": {
"editor.defaultFormatter": "biomejs.biome" "editor.defaultFormatter": "biomejs.biome"

View File

@@ -20,10 +20,12 @@
· ·
<a href="https://github.com/better-auth/better-auth/issues">Issues</a> <a href="https://github.com/better-auth/better-auth/issues">Issues</a>
</p> </p>
</p>
[![npm](https://img.shields.io/npm/dm/better-auth)](https://www.npmjs.com/package/better-auth) [![npm](https://img.shields.io/npm/dm/better-auth)](https://www.npmjs.com/package/better-auth)
[![npm version](https://img.shields.io/npm/v/better-auth.svg)](https://www.npmjs.com/package/better-auth)
[![GitHub stars](https://img.shields.io/github/stars/better-auth/better-auth)](https://github.com/better-auth/better-auth/stargazers) [![GitHub stars](https://img.shields.io/github/stars/better-auth/better-auth)](https://github.com/better-auth/better-auth/stargazers)
</p>
> [!IMPORTANT] > [!IMPORTANT]
> **Note:** 🚧 This project is currently in beta. Features and APIs may change. > **Note:** 🚧 This project is currently in beta. Features and APIs may change.
@@ -52,10 +54,7 @@ Better Auth was built to tackle these frustrations. It offers a robust core auth
- **JWT-Based Authentication**: We wont support JWT-based auth unless provided by a third-party plugin. - **JWT-Based Authentication**: We wont support JWT-based auth unless provided by a third-party plugin.
- **Support for Non-Relational Databases**: No plans to support MongoDB or other non-relational databases. - **Support for Non-Relational Databases**: No plans to support MongoDB or other non-relational databases.
- **Deep Customization**: Our focus is on delivering opinionated, best-practice defaults, rather than enabling deep customization. - **Deep Customization**: Our focus is on delivering opinionated, best-practice defaults, rather than enabling deep customization.
- **Frontend UI Components**: We dont provide frontend UI components. However, feel free to use our examples for quick UI integration.
> Some of these non-goals might change after we hit v1
## Contribution ## Contribution
Better Auth is free and open source project licensed under the [MIT License](./LICENSE.md). You are free to do whatever you want with it. Better Auth is free and open source project licensed under the [MIT License](./LICENSE.md). You are free to do whatever you want with it.
@@ -66,6 +65,6 @@ You could help continuing its development by:
- [Suggest new features and report issues](https://github.com/better-auth/better-auth/issues) - [Suggest new features and report issues](https://github.com/better-auth/better-auth/issues)
## Security ## Security
If you discover a security vulnerability within Better AUth, please send an e-mail to support at better-auth.com. If you discover a security vulnerability within Better Auth, please send an e-mail to security@better-auth.com.
All reports will be promptly addressed, and you'll be credited accordingly. All reports will be promptly addressed, and you'll be credited accordingly.

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const router = useRouter() const router = useRouter();
</script> </script>
<template> <template>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
AccordionRoot, AccordionRoot,
type AccordionRootEmits, type AccordionRootEmits,
type AccordionRootProps, type AccordionRootProps,
useForwardPropsEmits, useForwardPropsEmits,
} from 'radix-vue' } from "radix-vue";
const props = defineProps<AccordionRootProps>() const props = defineProps<AccordionRootProps>();
const emits = defineEmits<AccordionRootEmits>() const emits = defineEmits<AccordionRootEmits>();
const forwarded = useForwardPropsEmits(props, emits) const forwarded = useForwardPropsEmits(props, emits);
</script> </script>
<template> <template>

View File

@@ -1,15 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { AccordionContent, type AccordionContentProps } from 'radix-vue' import { AccordionContent, type AccordionContentProps } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AccordionContentProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' AccordionItem,
type AccordionItemProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AccordionItemProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,20 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { import {
AccordionHeader, AccordionHeader,
AccordionTrigger, AccordionTrigger,
type AccordionTriggerProps, type AccordionTriggerProps,
} from 'radix-vue' } from "radix-vue";
import { ChevronDownIcon } from '@radix-icons/vue' import { ChevronDownIcon } from "@radix-icons/vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AccordionTriggerProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,4 +1,4 @@
export { default as Accordion } from './Accordion.vue' export { default as Accordion } from "./Accordion.vue";
export { default as AccordionContent } from './AccordionContent.vue' export { default as AccordionContent } from "./AccordionContent.vue";
export { default as AccordionItem } from './AccordionItem.vue' export { default as AccordionItem } from "./AccordionItem.vue";
export { default as AccordionTrigger } from './AccordionTrigger.vue' export { default as AccordionTrigger } from "./AccordionTrigger.vue";

View File

@@ -1,10 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { type AlertDialogEmits, type AlertDialogProps, AlertDialogRoot, useForwardPropsEmits } from 'radix-vue' import {
type AlertDialogEmits,
type AlertDialogProps,
AlertDialogRoot,
useForwardPropsEmits,
} from "radix-vue";
const props = defineProps<AlertDialogProps>() const props = defineProps<AlertDialogProps>();
const emits = defineEmits<AlertDialogEmits>() const emits = defineEmits<AlertDialogEmits>();
const forwarded = useForwardPropsEmits(props, emits) const forwarded = useForwardPropsEmits(props, emits);
</script> </script>
<template> <template>

View File

@@ -1,16 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue' import { AlertDialogAction, type AlertDialogActionProps } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AlertDialogActionProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,16 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue' import { AlertDialogCancel, type AlertDialogCancelProps } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AlertDialogCancelProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,25 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { import {
AlertDialogContent, AlertDialogContent,
type AlertDialogContentEmits, type AlertDialogContentEmits,
type AlertDialogContentProps, type AlertDialogContentProps,
AlertDialogOverlay, AlertDialogOverlay,
AlertDialogPortal, AlertDialogPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from 'radix-vue' } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
const emits = defineEmits<AlertDialogContentEmits>() AlertDialogContentProps & { class?: HTMLAttributes["class"] }
>();
const emits = defineEmits<AlertDialogContentEmits>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script> </script>
<template> <template>

View File

@@ -1,18 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { import {
AlertDialogDescription, AlertDialogDescription,
type AlertDialogDescriptionProps, type AlertDialogDescriptionProps,
} from 'radix-vue' } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AlertDialogDescriptionProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,15 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue' import { AlertDialogTitle, type AlertDialogTitleProps } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
AlertDialogTitleProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
</script> </script>
<template> <template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue' import { AlertDialogTrigger, type AlertDialogTriggerProps } from "radix-vue";
const props = defineProps<AlertDialogTriggerProps>() const props = defineProps<AlertDialogTriggerProps>();
</script> </script>
<template> <template>

View File

@@ -1,9 +1,9 @@
export { default as AlertDialog } from './AlertDialog.vue' export { default as AlertDialog } from "./AlertDialog.vue";
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue' export { default as AlertDialogTrigger } from "./AlertDialogTrigger.vue";
export { default as AlertDialogContent } from './AlertDialogContent.vue' export { default as AlertDialogContent } from "./AlertDialogContent.vue";
export { default as AlertDialogHeader } from './AlertDialogHeader.vue' export { default as AlertDialogHeader } from "./AlertDialogHeader.vue";
export { default as AlertDialogTitle } from './AlertDialogTitle.vue' export { default as AlertDialogTitle } from "./AlertDialogTitle.vue";
export { default as AlertDialogDescription } from './AlertDialogDescription.vue' export { default as AlertDialogDescription } from "./AlertDialogDescription.vue";
export { default as AlertDialogFooter } from './AlertDialogFooter.vue' export { default as AlertDialogFooter } from "./AlertDialogFooter.vue";
export { default as AlertDialogAction } from './AlertDialogAction.vue' export { default as AlertDialogAction } from "./AlertDialogAction.vue";
export { default as AlertDialogCancel } from './AlertDialogCancel.vue' export { default as AlertDialogCancel } from "./AlertDialogCancel.vue";

View File

@@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { type AlertVariants, alertVariants } from '.' import { type AlertVariants, alertVariants } from ".";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
variant?: AlertVariants['variant'] variant?: AlertVariants["variant"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,23 +1,23 @@
import { type VariantProps, cva } from 'class-variance-authority' import { type VariantProps, cva } from "class-variance-authority";
export { default as Alert } from './Alert.vue' export { default as Alert } from "./Alert.vue";
export { default as AlertTitle } from './AlertTitle.vue' export { default as AlertTitle } from "./AlertTitle.vue";
export { default as AlertDescription } from './AlertDescription.vue' export { default as AlertDescription } from "./AlertDescription.vue";
export const alertVariants = cva( export const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{ {
variants: { variants: {
variant: { variant: {
default: 'bg-background text-foreground', default: "bg-background text-foreground",
destructive: destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
}, },
}, },
) );
export type AlertVariants = VariantProps<typeof alertVariants> export type AlertVariants = VariantProps<typeof alertVariants>;

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { AspectRatio, type AspectRatioProps } from 'radix-vue' import { AspectRatio, type AspectRatioProps } from "radix-vue";
const props = defineProps<AspectRatioProps>() const props = defineProps<AspectRatioProps>();
</script> </script>
<template> <template>

View File

@@ -1 +1 @@
export { default as AspectRatio } from './AspectRatio.vue' export { default as AspectRatio } from "./AspectRatio.vue";

View File

@@ -1,81 +1,95 @@
<script setup lang="ts" generic="T extends ZodObjectOrWrapped"> <script setup lang="ts" generic="T extends ZodObjectOrWrapped">
import { computed, toRefs } from 'vue' import { computed, toRefs } from "vue";
import type { ZodAny, z } from 'zod' import type { ZodAny, z } from "zod";
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from "@vee-validate/zod";
import type { FormContext, GenericObject } from 'vee-validate' import type { FormContext, GenericObject } from "vee-validate";
import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils' import {
import type { Config, ConfigItem, Dependency, Shape } from './interface' type ZodObjectOrWrapped,
import AutoFormField from './AutoFormField.vue' getBaseSchema,
import { provideDependencies } from './dependencies' getBaseType,
import { Form } from '@/components/ui/form' getDefaultValueInZodStack,
getObjectFormSchema,
} from "./utils";
import type { Config, ConfigItem, Dependency, Shape } from "./interface";
import AutoFormField from "./AutoFormField.vue";
import { provideDependencies } from "./dependencies";
import { Form } from "@/components/ui/form";
const props = defineProps<{ const props = defineProps<{
schema: T schema: T;
form?: FormContext<GenericObject> form?: FormContext<GenericObject>;
fieldConfig?: Config<z.infer<T>> fieldConfig?: Config<z.infer<T>>;
dependencies?: Dependency<z.infer<T>>[] dependencies?: Dependency<z.infer<T>>[];
}>() }>();
const emits = defineEmits<{ const emits = defineEmits<{
submit: [event: z.infer<T>] submit: [event: z.infer<T>];
}>() }>();
const { dependencies } = toRefs(props) const { dependencies } = toRefs(props);
provideDependencies(dependencies) provideDependencies(dependencies);
const shapes = computed(() => { const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object // @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof T]: Shape } = {} const val: { [key in keyof T]: Shape } = {};
const baseSchema = getObjectFormSchema(props.schema) const baseSchema = getObjectFormSchema(props.schema);
const shape = baseSchema.shape const shape = baseSchema.shape;
Object.keys(shape).forEach((name) => { Object.keys(shape).forEach((name) => {
const item = shape[name] as ZodAny const item = shape[name] as ZodAny;
const baseItem = getBaseSchema(item) as ZodAny const baseItem = getBaseSchema(item) as ZodAny;
let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined let options =
if (!Array.isArray(options) && typeof options === 'object') baseItem && "values" in baseItem._def
options = Object.values(options) ? (baseItem._def.values as string[])
: undefined;
if (!Array.isArray(options) && typeof options === "object")
options = Object.values(options);
val[name as keyof T] = { val[name as keyof T] = {
type: getBaseType(item), type: getBaseType(item),
default: getDefaultValueInZodStack(item), default: getDefaultValueInZodStack(item),
options, options,
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName), required: !["ZodOptional", "ZodNullable"].includes(item._def.typeName),
schema: baseItem, schema: baseItem,
} };
}) });
return val return val;
}) });
const fields = computed(() => { const fields = computed(() => {
// @ts-expect-error ignore {} not assignable to object // @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {} const val: {
for (const key in shapes.value) { [key in keyof z.infer<T>]: {
const shape = shapes.value[key] shape: Shape;
val[key as keyof z.infer<T>] = { fieldName: string;
shape, config: ConfigItem;
config: props.fieldConfig?.[key] as ConfigItem, };
fieldName: key, } = {};
} for (const key in shapes.value) {
} const shape = shapes.value[key];
return val val[key as keyof z.infer<T>] = {
}) shape,
config: props.fieldConfig?.[key] as ConfigItem,
fieldName: key,
};
}
return val;
});
const formComponent = computed(() => props.form ? 'form' : Form) const formComponent = computed(() => (props.form ? "form" : Form));
const formComponentProps = computed(() => { const formComponentProps = computed(() => {
if (props.form) { if (props.form) {
return { return {
onSubmit: props.form.handleSubmit(val => emits('submit', val)), onSubmit: props.form.handleSubmit((val) => emits("submit", val)),
} };
} } else {
else { const formSchema = toTypedSchema(props.schema);
const formSchema = toTypedSchema(props.schema) return {
return { keepValues: true,
keepValues: true, validationSchema: formSchema,
validationSchema: formSchema, onSubmit: (val: GenericObject) => emits("submit", val),
onSubmit: (val: GenericObject) => emits('submit', val), };
} }
} });
})
</script> </script>
<template> <template>

View File

@@ -1,27 +1,29 @@
<script setup lang="ts" generic="U extends ZodAny"> <script setup lang="ts" generic="U extends ZodAny">
import type { ZodAny } from 'zod' import type { ZodAny } from "zod";
import { computed } from 'vue' import { computed } from "vue";
import type { Config, ConfigItem, Shape } from './interface' import type { Config, ConfigItem, Shape } from "./interface";
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant' import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from "./constant";
import useDependencies from './dependencies' import useDependencies from "./dependencies";
const props = defineProps<{ const props = defineProps<{
fieldName: string fieldName: string;
shape: Shape shape: Shape;
config?: ConfigItem | Config<U> config?: ConfigItem | Config<U>;
}>() }>();
function isValidConfig(config: any): config is ConfigItem { function isValidConfig(config: any): config is ConfigItem {
return !!config?.component return !!config?.component;
} }
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
if (['ZodObject', 'ZodArray'].includes(props.shape?.type)) if (["ZodObject", "ZodArray"].includes(props.shape?.type))
return { schema: props.shape?.schema } return { schema: props.shape?.schema };
return undefined return undefined;
}) });
const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.fieldName) const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(
props.fieldName,
);
</script> </script>
<template> <template>

View File

@@ -1,57 +1,61 @@
<script setup lang="ts" generic="T extends z.ZodAny"> <script setup lang="ts" generic="T extends z.ZodAny">
import * as z from 'zod' import * as z from "zod";
import { computed, provide } from 'vue' import { computed, provide } from "vue";
import { PlusIcon, TrashIcon } from 'lucide-vue-next' import { PlusIcon, TrashIcon } from "lucide-vue-next";
import { FieldArray, FieldContextKey, useField } from 'vee-validate' import { FieldArray, FieldContextKey, useField } from "vee-validate";
import type { Config, ConfigItem } from './interface' import type { Config, ConfigItem } from "./interface";
import { beautifyObjectName, getBaseType } from './utils' import { beautifyObjectName, getBaseType } from "./utils";
import AutoFormField from './AutoFormField.vue' import AutoFormField from "./AutoFormField.vue";
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' import {
import { Button } from '@/components/ui/button' Accordion,
import { Separator } from '@/components/ui/separator' AccordionContent,
import { FormItem, FormMessage } from '@/components/ui/form' AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { FormItem, FormMessage } from "@/components/ui/form";
const props = defineProps<{ const props = defineProps<{
fieldName: string fieldName: string;
required?: boolean required?: boolean;
config?: Config<T> config?: Config<T>;
schema?: z.ZodArray<T> schema?: z.ZodArray<T>;
disabled?: boolean disabled?: boolean;
}>() }>();
function isZodArray( function isZodArray(
item: z.ZodArray<any> | z.ZodDefault<any>, item: z.ZodArray<any> | z.ZodDefault<any>,
): item is z.ZodArray<any> { ): item is z.ZodArray<any> {
return item instanceof z.ZodArray return item instanceof z.ZodArray;
} }
function isZodDefault( function isZodDefault(
item: z.ZodArray<any> | z.ZodDefault<any>, item: z.ZodArray<any> | z.ZodDefault<any>,
): item is z.ZodDefault<any> { ): item is z.ZodDefault<any> {
return item instanceof z.ZodDefault return item instanceof z.ZodDefault;
} }
const itemShape = computed(() => { const itemShape = computed(() => {
if (!props.schema) if (!props.schema) return;
return
const schema: z.ZodAny = isZodArray(props.schema) const schema: z.ZodAny = isZodArray(props.schema)
? props.schema._def.type ? props.schema._def.type
: isZodDefault(props.schema) : isZodDefault(props.schema)
// @ts-expect-error missing schema ? // @ts-expect-error missing schema
? props.schema._def.innerType._def.type props.schema._def.innerType._def.type
: null : null;
return { return {
type: getBaseType(schema), type: getBaseType(schema),
schema, schema,
} };
}) });
const fieldContext = useField(props.fieldName) const fieldContext = useField(props.fieldName);
// @ts-expect-error ignore missing `id` // @ts-expect-error ignore missing `id`
provide(FieldContextKey, fieldContext) provide(FieldContextKey, fieldContext);
</script> </script>
<template> <template>

View File

@@ -1,15 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from "vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
import { Switch } from '@/components/ui/switch' FormControl,
import { Checkbox } from '@/components/ui/checkbox' FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox";
const props = defineProps<FieldProps>() const props = defineProps<FieldProps>();
const booleanComponent = computed(() => props.config?.component === 'switch' ? Switch : Checkbox) const booleanComponent = computed(() =>
props.config?.component === "switch" ? Switch : Checkbox,
);
</script> </script>
<template> <template>

View File

@@ -1,21 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import { DateFormatter, getLocalTimeZone } from '@internationalized/date' import { DateFormatter, getLocalTimeZone } from "@internationalized/date";
import { CalendarIcon } from '@radix-icons/vue' import { CalendarIcon } from "@radix-icons/vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Calendar } from '@/components/ui/calendar' import { Calendar } from "@/components/ui/calendar";
import { Button } from '@/components/ui/button' import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import {
import { cn } from '@/lib/utils' Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
defineProps<FieldProps>() defineProps<FieldProps>();
const df = new DateFormatter('en-US', { const df = new DateFormatter("en-US", {
dateStyle: 'long', dateStyle: "long",
}) });
</script> </script>
<template> <template>

View File

@@ -1,15 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' FormControl,
import { Label } from '@/components/ui/label' FormDescription,
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
defineProps<FieldProps & { defineProps<
options?: string[] FieldProps & {
}>() options?: string[];
}
>();
</script> </script>
<template> <template>

View File

@@ -1,29 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from "vue";
import { TrashIcon } from '@radix-icons/vue' import { TrashIcon } from "@radix-icons/vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
import { Input } from '@/components/ui/input' FormControl,
import { Button } from '@/components/ui/button' FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
defineProps<FieldProps>() defineProps<FieldProps>();
const inputFile = ref<File>() const inputFile = ref<File>();
async function parseFileAsString(file: File | undefined): Promise<string> { async function parseFileAsString(file: File | undefined): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (file) { if (file) {
const reader = new FileReader() const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
resolve(reader.result as string) resolve(reader.result as string);
} };
reader.onerror = (err) => { reader.onerror = (err) => {
reject(err) reject(err);
} };
reader.readAsDataURL(file) reader.readAsDataURL(file);
} }
}) });
} }
</script> </script>

View File

@@ -1,14 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from "vue";
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
import { Input } from '@/components/ui/input' FormControl,
import { Textarea } from '@/components/ui/textarea' FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
const props = defineProps<FieldProps>() const props = defineProps<FieldProps>();
const inputComponent = computed(() => props.config?.component === 'textarea' ? Textarea : Input) const inputComponent = computed(() =>
props.config?.component === "textarea" ? Textarea : Input,
);
</script> </script>
<template> <template>

View File

@@ -1,15 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import AutoFormLabel from './AutoFormLabel.vue' import AutoFormLabel from "./AutoFormLabel.vue";
import { beautifyObjectName } from './utils' import { beautifyObjectName } from "./utils";
import type { FieldProps } from './interface' import type { FieldProps } from "./interface";
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/components/ui/form' import {
import { Input } from '@/components/ui/input' FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) });
defineProps<FieldProps>() defineProps<FieldProps>();
</script> </script>
<template> <template>

View File

@@ -1,52 +1,63 @@
<script setup lang="ts" generic="T extends ZodRawShape"> <script setup lang="ts" generic="T extends ZodRawShape">
import type { ZodAny, ZodObject, ZodRawShape } from 'zod' import type { ZodAny, ZodObject, ZodRawShape } from "zod";
import { computed, provide } from 'vue' import { computed, provide } from "vue";
import { FieldContextKey, useField } from 'vee-validate' import { FieldContextKey, useField } from "vee-validate";
import AutoFormField from './AutoFormField.vue' import AutoFormField from "./AutoFormField.vue";
import type { Config, ConfigItem, Shape } from './interface' import type { Config, ConfigItem, Shape } from "./interface";
import { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils' import {
import AutoFormLabel from './AutoFormLabel.vue' beautifyObjectName,
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' getBaseSchema,
import { FormItem } from '@/components/ui/form' getBaseType,
getDefaultValueInZodStack,
} from "./utils";
import AutoFormLabel from "./AutoFormLabel.vue";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { FormItem } from "@/components/ui/form";
const props = defineProps<{ const props = defineProps<{
fieldName: string fieldName: string;
required?: boolean required?: boolean;
config?: Config<T> config?: Config<T>;
schema?: ZodObject<T> schema?: ZodObject<T>;
disabled?: boolean disabled?: boolean;
}>() }>();
const shapes = computed(() => { const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object // @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof T]: Shape } = {} const val: { [key in keyof T]: Shape } = {};
if (!props.schema) if (!props.schema) return;
return const shape = getBaseSchema(props.schema)?.shape;
const shape = getBaseSchema(props.schema)?.shape if (!shape) return;
if (!shape) Object.keys(shape).forEach((name) => {
return const item = shape[name] as ZodAny;
Object.keys(shape).forEach((name) => { const baseItem = getBaseSchema(item) as ZodAny;
const item = shape[name] as ZodAny let options =
const baseItem = getBaseSchema(item) as ZodAny baseItem && "values" in baseItem._def
let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined ? (baseItem._def.values as string[])
if (!Array.isArray(options) && typeof options === 'object') : undefined;
options = Object.values(options) if (!Array.isArray(options) && typeof options === "object")
options = Object.values(options);
val[name as keyof T] = { val[name as keyof T] = {
type: getBaseType(item), type: getBaseType(item),
default: getDefaultValueInZodStack(item), default: getDefaultValueInZodStack(item),
options, options,
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName), required: !["ZodOptional", "ZodNullable"].includes(item._def.typeName),
schema: item, schema: item,
} };
}) });
return val return val;
}) });
const fieldContext = useField(props.fieldName) const fieldContext = useField(props.fieldName);
// @ts-expect-error ignore missing `id` // @ts-expect-error ignore missing `id`
provide(FieldContextKey, fieldContext) provide(FieldContextKey, fieldContext);
</script> </script>
<template> <template>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { FormLabel } from '@/components/ui/form' import { FormLabel } from "@/components/ui/form";
defineProps<{ defineProps<{
required?: boolean required?: boolean;
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,39 +1,39 @@
import AutoFormFieldArray from './AutoFormFieldArray.vue' import AutoFormFieldArray from "./AutoFormFieldArray.vue";
import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue' import AutoFormFieldBoolean from "./AutoFormFieldBoolean.vue";
import AutoFormFieldDate from './AutoFormFieldDate.vue' import AutoFormFieldDate from "./AutoFormFieldDate.vue";
import AutoFormFieldEnum from './AutoFormFieldEnum.vue' import AutoFormFieldEnum from "./AutoFormFieldEnum.vue";
import AutoFormFieldFile from './AutoFormFieldFile.vue' import AutoFormFieldFile from "./AutoFormFieldFile.vue";
import AutoFormFieldInput from './AutoFormFieldInput.vue' import AutoFormFieldInput from "./AutoFormFieldInput.vue";
import AutoFormFieldNumber from './AutoFormFieldNumber.vue' import AutoFormFieldNumber from "./AutoFormFieldNumber.vue";
import AutoFormFieldObject from './AutoFormFieldObject.vue' import AutoFormFieldObject from "./AutoFormFieldObject.vue";
export const INPUT_COMPONENTS = { export const INPUT_COMPONENTS = {
date: AutoFormFieldDate, date: AutoFormFieldDate,
select: AutoFormFieldEnum, select: AutoFormFieldEnum,
radio: AutoFormFieldEnum, radio: AutoFormFieldEnum,
checkbox: AutoFormFieldBoolean, checkbox: AutoFormFieldBoolean,
switch: AutoFormFieldBoolean, switch: AutoFormFieldBoolean,
textarea: AutoFormFieldInput, textarea: AutoFormFieldInput,
number: AutoFormFieldNumber, number: AutoFormFieldNumber,
string: AutoFormFieldInput, string: AutoFormFieldInput,
file: AutoFormFieldFile, file: AutoFormFieldFile,
array: AutoFormFieldArray, array: AutoFormFieldArray,
object: AutoFormFieldObject, object: AutoFormFieldObject,
} };
/** /**
* Define handlers for specific Zod types. * Define handlers for specific Zod types.
* You can expand this object to support more types. * You can expand this object to support more types.
*/ */
export const DEFAULT_ZOD_HANDLERS: { export const DEFAULT_ZOD_HANDLERS: {
[key: string]: keyof typeof INPUT_COMPONENTS [key: string]: keyof typeof INPUT_COMPONENTS;
} = { } = {
ZodString: 'string', ZodString: "string",
ZodBoolean: 'checkbox', ZodBoolean: "checkbox",
ZodDate: 'date', ZodDate: "date",
ZodEnum: 'select', ZodEnum: "select",
ZodNativeEnum: 'select', ZodNativeEnum: "select",
ZodNumber: 'number', ZodNumber: "number",
ZodArray: 'array', ZodArray: "array",
ZodObject: 'object', ZodObject: "object",
} };

View File

@@ -1,92 +1,100 @@
import type * as z from 'zod' import type * as z from "zod";
import type { Ref } from 'vue' import type { Ref } from "vue";
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from "vue";
import { useFieldValue, useFormValues } from 'vee-validate' import { useFieldValue, useFormValues } from "vee-validate";
import { createContext } from 'radix-vue' import { createContext } from "radix-vue";
import { type Dependency, DependencyType, type EnumValues } from './interface' import { type Dependency, DependencyType, type EnumValues } from "./interface";
import { getFromPath, getIndexIfArray } from './utils' import { getFromPath, getIndexIfArray } from "./utils";
export const [injectDependencies, provideDependencies] = createContext<Ref<Dependency<z.infer<z.ZodObject<any>>>[] | undefined>>('AutoFormDependencies') export const [injectDependencies, provideDependencies] = createContext<
Ref<Dependency<z.infer<z.ZodObject<any>>>[] | undefined>
>("AutoFormDependencies");
export default function useDependencies( export default function useDependencies(fieldName: string) {
fieldName: string, const form = useFormValues();
) { // parsed test[0].age => test.age
const form = useFormValues() const currentFieldName = fieldName.replace(/\[\d+\]/g, "");
// parsed test[0].age => test.age const currentFieldValue = useFieldValue<any>(fieldName);
const currentFieldName = fieldName.replace(/\[\d+\]/g, '')
const currentFieldValue = useFieldValue<any>(fieldName)
if (!form) if (!form)
throw new Error('useDependencies should be used within <AutoForm>') throw new Error("useDependencies should be used within <AutoForm>");
const dependencies = injectDependencies() const dependencies = injectDependencies();
const isDisabled = ref(false) const isDisabled = ref(false);
const isHidden = ref(false) const isHidden = ref(false);
const isRequired = ref(false) const isRequired = ref(false);
const overrideOptions = ref<EnumValues | undefined>() const overrideOptions = ref<EnumValues | undefined>();
const currentFieldDependencies = computed(() => dependencies.value?.filter( const currentFieldDependencies = computed(() =>
dependency => dependency.targetField === currentFieldName, dependencies.value?.filter(
)) (dependency) => dependency.targetField === currentFieldName,
),
);
function getSourceValue(dep: Dependency<any>) { function getSourceValue(dep: Dependency<any>) {
const source = dep.sourceField as string const source = dep.sourceField as string;
const index = getIndexIfArray(fieldName) ?? -1 const index = getIndexIfArray(fieldName) ?? -1;
const [sourceLast, ...sourceInitial] = source.split('.').toReversed() const [sourceLast, ...sourceInitial] = source.split(".").toReversed();
const [_targetLast, ...targetInitial] = (dep.targetField as string).split('.').toReversed() const [_targetLast, ...targetInitial] = (dep.targetField as string)
.split(".")
.toReversed();
if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) { if (index >= 0 && sourceInitial.join(",") === targetInitial.join(",")) {
const [_currentLast, ...currentInitial] = fieldName.split('.').toReversed() const [_currentLast, ...currentInitial] = fieldName
return getFromPath(form.value, currentInitial.join('.') + sourceLast) .split(".")
} .toReversed();
return getFromPath(form.value, currentInitial.join(".") + sourceLast);
}
return getFromPath(form.value, source) return getFromPath(form.value, source);
} }
const sourceFieldValues = computed(() => currentFieldDependencies.value?.map(dep => getSourceValue(dep))) const sourceFieldValues = computed(() =>
currentFieldDependencies.value?.map((dep) => getSourceValue(dep)),
);
const resetConditionState = () => { const resetConditionState = () => {
isDisabled.value = false isDisabled.value = false;
isHidden.value = false isHidden.value = false;
isRequired.value = false isRequired.value = false;
overrideOptions.value = undefined overrideOptions.value = undefined;
} };
watch([sourceFieldValues, dependencies], () => { watch(
resetConditionState() [sourceFieldValues, dependencies],
currentFieldDependencies.value?.forEach((dep) => { () => {
const sourceValue = getSourceValue(dep) resetConditionState();
const conditionMet = dep.when(sourceValue, currentFieldValue.value) currentFieldDependencies.value?.forEach((dep) => {
const sourceValue = getSourceValue(dep);
const conditionMet = dep.when(sourceValue, currentFieldValue.value);
switch (dep.type) { switch (dep.type) {
case DependencyType.DISABLES: case DependencyType.DISABLES:
if (conditionMet) if (conditionMet) isDisabled.value = true;
isDisabled.value = true
break break;
case DependencyType.REQUIRES: case DependencyType.REQUIRES:
if (conditionMet) if (conditionMet) isRequired.value = true;
isRequired.value = true
break break;
case DependencyType.HIDES: case DependencyType.HIDES:
if (conditionMet) if (conditionMet) isHidden.value = true;
isHidden.value = true
break break;
case DependencyType.SETS_OPTIONS: case DependencyType.SETS_OPTIONS:
if (conditionMet) if (conditionMet) overrideOptions.value = dep.options;
overrideOptions.value = dep.options
break break;
} }
}) });
}, { immediate: true, deep: true }) },
{ immediate: true, deep: true },
);
return { return {
isDisabled, isDisabled,
isHidden, isHidden,
isRequired, isRequired,
overrideOptions, overrideOptions,
} };
} }

View File

@@ -1,15 +1,15 @@
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils' export { getObjectFormSchema, getBaseSchema, getBaseType } from "./utils";
export type { Config, ConfigItem, FieldProps } from './interface' export type { Config, ConfigItem, FieldProps } from "./interface";
export { default as AutoForm } from './AutoForm.vue' export { default as AutoForm } from "./AutoForm.vue";
export { default as AutoFormField } from './AutoFormField.vue' export { default as AutoFormField } from "./AutoFormField.vue";
export { default as AutoFormLabel } from './AutoFormLabel.vue' export { default as AutoFormLabel } from "./AutoFormLabel.vue";
export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue' export { default as AutoFormFieldArray } from "./AutoFormFieldArray.vue";
export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue' export { default as AutoFormFieldBoolean } from "./AutoFormFieldBoolean.vue";
export { default as AutoFormFieldDate } from './AutoFormFieldDate.vue' export { default as AutoFormFieldDate } from "./AutoFormFieldDate.vue";
export { default as AutoFormFieldEnum } from './AutoFormFieldEnum.vue' export { default as AutoFormFieldEnum } from "./AutoFormFieldEnum.vue";
export { default as AutoFormFieldFile } from './AutoFormFieldFile.vue' export { default as AutoFormFieldFile } from "./AutoFormFieldFile.vue";
export { default as AutoFormFieldInput } from './AutoFormFieldInput.vue' export { default as AutoFormFieldInput } from "./AutoFormFieldInput.vue";
export { default as AutoFormFieldNumber } from './AutoFormFieldNumber.vue' export { default as AutoFormFieldNumber } from "./AutoFormFieldNumber.vue";
export { default as AutoFormFieldObject } from './AutoFormFieldObject.vue' export { default as AutoFormFieldObject } from "./AutoFormFieldObject.vue";

View File

@@ -1,81 +1,80 @@
import type { Component, InputHTMLAttributes } from 'vue' import type { Component, InputHTMLAttributes } from "vue";
import type { ZodAny, z } from 'zod' import type { ZodAny, z } from "zod";
import type { INPUT_COMPONENTS } from './constant' import type { INPUT_COMPONENTS } from "./constant";
export interface FieldProps { export interface FieldProps {
fieldName: string fieldName: string;
label?: string label?: string;
required?: boolean required?: boolean;
config?: ConfigItem config?: ConfigItem;
disabled?: boolean disabled?: boolean;
} }
export interface Shape { export interface Shape {
type: string type: string;
default?: any default?: any;
required?: boolean required?: boolean;
options?: string[] options?: string[];
schema?: ZodAny schema?: ZodAny;
} }
export interface ConfigItem { export interface ConfigItem {
/** Value for the `FormLabel` */ /** Value for the `FormLabel` */
label?: string label?: string;
/** Value for the `FormDescription` */ /** Value for the `FormDescription` */
description?: string description?: string;
/** Pick which component to be rendered. */ /** Pick which component to be rendered. */
component?: keyof typeof INPUT_COMPONENTS | Component component?: keyof typeof INPUT_COMPONENTS | Component;
/** Hide `FormLabel`. */ /** Hide `FormLabel`. */
hideLabel?: boolean hideLabel?: boolean;
inputProps?: InputHTMLAttributes inputProps?: InputHTMLAttributes;
} }
// Define a type to unwrap an array // Define a type to unwrap an array
type UnwrapArray<T> = T extends (infer U)[] ? U : never type UnwrapArray<T> = T extends (infer U)[] ? U : never;
export type Config<SchemaType extends object> = { export type Config<SchemaType extends object> = {
// If SchemaType.key is an object, create a nested Config, otherwise ConfigItem // If SchemaType.key is an object, create a nested Config, otherwise ConfigItem
[Key in keyof SchemaType]?: [Key in keyof SchemaType]?: SchemaType[Key] extends any[]
SchemaType[Key] extends any[] ? UnwrapArray<Config<SchemaType[Key]>>
? UnwrapArray<Config<SchemaType[Key]>> : SchemaType[Key] extends object
: SchemaType[Key] extends object ? Config<SchemaType[Key]>
? Config<SchemaType[Key]> : ConfigItem;
: ConfigItem; };
}
export enum DependencyType { export enum DependencyType {
DISABLES, DISABLES,
REQUIRES, REQUIRES,
HIDES, HIDES,
SETS_OPTIONS, SETS_OPTIONS,
} }
interface BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> { interface BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> {
sourceField: keyof SchemaType sourceField: keyof SchemaType;
type: DependencyType type: DependencyType;
targetField: keyof SchemaType targetField: keyof SchemaType;
when: (sourceFieldValue: any, targetFieldValue: any) => boolean when: (sourceFieldValue: any, targetFieldValue: any) => boolean;
} }
export type ValueDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> = export type ValueDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
BaseDependency<SchemaType> & { BaseDependency<SchemaType> & {
type: type:
| DependencyType.DISABLES | DependencyType.DISABLES
| DependencyType.REQUIRES | DependencyType.REQUIRES
| DependencyType.HIDES | DependencyType.HIDES;
} };
export type EnumValues = readonly [string, ...string[]] export type EnumValues = readonly [string, ...string[]];
export type OptionsDependency< export type OptionsDependency<
SchemaType extends z.infer<z.ZodObject<any, any>>, SchemaType extends z.infer<z.ZodObject<any, any>>,
> = BaseDependency<SchemaType> & { > = BaseDependency<SchemaType> & {
type: DependencyType.SETS_OPTIONS type: DependencyType.SETS_OPTIONS;
// Partial array of values from sourceField that will trigger the dependency // Partial array of values from sourceField that will trigger the dependency
options: EnumValues options: EnumValues;
} };
export type Dependency<SchemaType extends z.infer<z.ZodObject<any, any>>> = export type Dependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
| ValueDependency<SchemaType> | ValueDependency<SchemaType>
| OptionsDependency<SchemaType> | OptionsDependency<SchemaType>;

View File

@@ -1,20 +1,20 @@
import type { z } from 'zod' import type { z } from "zod";
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions. // TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
export type ZodObjectOrWrapped = export type ZodObjectOrWrapped =
| z.ZodObject<any, any> | z.ZodObject<any, any>
| z.ZodEffects<z.ZodObject<any, any>> | z.ZodEffects<z.ZodObject<any, any>>;
/** /**
* Beautify a camelCase string. * Beautify a camelCase string.
* e.g. "myString" -> "My String" * e.g. "myString" -> "My String"
*/ */
export function beautifyObjectName(string: string) { export function beautifyObjectName(string: string) {
// Remove bracketed indices // Remove bracketed indices
// if numbers only return the string // if numbers only return the string
let output = string.replace(/\[\d+\]/g, '').replace(/([A-Z])/g, ' $1') let output = string.replace(/\[\d+\]/g, "").replace(/([A-Z])/g, " $1");
output = output.charAt(0).toUpperCase() + output.slice(1) output = output.charAt(0).toUpperCase() + output.slice(1);
return output return output;
} }
/** /**
@@ -23,12 +23,12 @@ export function beautifyObjectName(string: string) {
* @returns index or undefined * @returns index or undefined
*/ */
export function getIndexIfArray(string: string) { export function getIndexIfArray(string: string) {
const indexRegex = /\[(\d+)\]/ const indexRegex = /\[(\d+)\]/;
// Match the index // Match the index
const match = string.match(indexRegex) const match = string.match(indexRegex);
// Extract the index (number) // Extract the index (number)
const index = match ? Number.parseInt(match[1]) : undefined const index = match ? Number.parseInt(match[1]) : undefined;
return index return index;
} }
/** /**
@@ -36,17 +36,16 @@ export function getIndexIfArray(string: string) {
* This will unpack optionals, refinements, etc. * This will unpack optionals, refinements, etc.
*/ */
export function getBaseSchema< export function getBaseSchema<
ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny, ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,
>(schema: ChildType | z.ZodEffects<ChildType>): ChildType | null { >(schema: ChildType | z.ZodEffects<ChildType>): ChildType | null {
if (!schema) if (!schema) return null;
return null if ("innerType" in schema._def)
if ('innerType' in schema._def) return getBaseSchema(schema._def.innerType as ChildType);
return getBaseSchema(schema._def.innerType as ChildType)
if ('schema' in schema._def) if ("schema" in schema._def)
return getBaseSchema(schema._def.schema as ChildType) return getBaseSchema(schema._def.schema as ChildType);
return schema as ChildType return schema as ChildType;
} }
/** /**
@@ -54,118 +53,119 @@ export function getBaseSchema<
* This will unpack optionals, refinements, etc. * This will unpack optionals, refinements, etc.
*/ */
export function getBaseType(schema: z.ZodAny) { export function getBaseType(schema: z.ZodAny) {
const baseSchema = getBaseSchema(schema) const baseSchema = getBaseSchema(schema);
return baseSchema ? baseSchema._def.typeName : '' return baseSchema ? baseSchema._def.typeName : "";
} }
/** /**
* Search for a "ZodDefault" in the Zod stack and return its value. * Search for a "ZodDefault" in the Zod stack and return its value.
*/ */
export function getDefaultValueInZodStack(schema: z.ZodAny): any { export function getDefaultValueInZodStack(schema: z.ZodAny): any {
const typedSchema = schema as unknown as z.ZodDefault< const typedSchema = schema as unknown as z.ZodDefault<
z.ZodNumber | z.ZodString z.ZodNumber | z.ZodString
> >;
if (typedSchema._def.typeName === 'ZodDefault') if (typedSchema._def.typeName === "ZodDefault")
return typedSchema._def.defaultValue() return typedSchema._def.defaultValue();
if ('innerType' in typedSchema._def) { if ("innerType" in typedSchema._def) {
return getDefaultValueInZodStack( return getDefaultValueInZodStack(
typedSchema._def.innerType as unknown as z.ZodAny, typedSchema._def.innerType as unknown as z.ZodAny,
) );
} }
if ('schema' in typedSchema._def) { if ("schema" in typedSchema._def) {
return getDefaultValueInZodStack( return getDefaultValueInZodStack(
(typedSchema._def as any).schema as z.ZodAny, (typedSchema._def as any).schema as z.ZodAny,
) );
} }
return undefined return undefined;
} }
export function getObjectFormSchema( export function getObjectFormSchema(
schema: ZodObjectOrWrapped, schema: ZodObjectOrWrapped,
): z.ZodObject<any, any> { ): z.ZodObject<any, any> {
if (schema?._def.typeName === 'ZodEffects') { if (schema?._def.typeName === "ZodEffects") {
const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>> const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>;
return getObjectFormSchema(typedSchema._def.schema) return getObjectFormSchema(typedSchema._def.schema);
} }
return schema as z.ZodObject<any, any> return schema as z.ZodObject<any, any>;
} }
function isIndex(value: unknown): value is number { function isIndex(value: unknown): value is number {
return Number(value) >= 0 return Number(value) >= 0;
} }
/** /**
* Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax * Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax
*/ */
export function normalizeFormPath(path: string): string { export function normalizeFormPath(path: string): string {
const pathArr = path.split('.') const pathArr = path.split(".");
if (!pathArr.length) if (!pathArr.length) return "";
return ''
let fullPath = String(pathArr[0]) let fullPath = String(pathArr[0]);
for (let i = 1; i < pathArr.length; i++) { for (let i = 1; i < pathArr.length; i++) {
if (isIndex(pathArr[i])) { if (isIndex(pathArr[i])) {
fullPath += `[${pathArr[i]}]` fullPath += `[${pathArr[i]}]`;
continue continue;
} }
fullPath += `.${pathArr[i]}` fullPath += `.${pathArr[i]}`;
} }
return fullPath return fullPath;
} }
type NestedRecord = Record<string, unknown> | { [k: string]: NestedRecord } type NestedRecord = Record<string, unknown> | { [k: string]: NestedRecord };
/** /**
* Checks if the path opted out of nested fields using `[fieldName]` syntax * Checks if the path opted out of nested fields using `[fieldName]` syntax
*/ */
export function isNotNestedPath(path: string) { export function isNotNestedPath(path: string) {
return /^\[.+\]$/.test(path) return /^\[.+\]$/.test(path);
} }
function isObject(obj: unknown): obj is Record<string, unknown> { function isObject(obj: unknown): obj is Record<string, unknown> {
return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj) return (
obj !== null && !!obj && typeof obj === "object" && !Array.isArray(obj)
);
} }
function isContainerValue(value: unknown): value is Record<string, unknown> { function isContainerValue(value: unknown): value is Record<string, unknown> {
return isObject(value) || Array.isArray(value) return isObject(value) || Array.isArray(value);
} }
function cleanupNonNestedPath(path: string) { function cleanupNonNestedPath(path: string) {
if (isNotNestedPath(path)) if (isNotNestedPath(path)) return path.replace(/\[|\]/g, "");
return path.replace(/\[|\]/g, '')
return path return path;
} }
/** /**
* Gets a nested property value from an object * Gets a nested property value from an object
*/ */
export function getFromPath<TValue = unknown>(object: NestedRecord | undefined, path: string): TValue | undefined export function getFromPath<TValue = unknown>(
object: NestedRecord | undefined,
path: string,
): TValue | undefined;
export function getFromPath<TValue = unknown, TFallback = TValue>( export function getFromPath<TValue = unknown, TFallback = TValue>(
object: NestedRecord | undefined, object: NestedRecord | undefined,
path: string, path: string,
fallback?: TFallback, fallback?: TFallback,
): TValue | TFallback ): TValue | TFallback;
export function getFromPath<TValue = unknown, TFallback = TValue>( export function getFromPath<TValue = unknown, TFallback = TValue>(
object: NestedRecord | undefined, object: NestedRecord | undefined,
path: string, path: string,
fallback?: TFallback, fallback?: TFallback,
): TValue | TFallback | undefined { ): TValue | TFallback | undefined {
if (!object) if (!object) return fallback;
return fallback
if (isNotNestedPath(path)) if (isNotNestedPath(path))
return object[cleanupNonNestedPath(path)] as TValue | undefined return object[cleanupNonNestedPath(path)] as TValue | undefined;
const resolvedValue = (path || '') const resolvedValue = (path || "")
.split(/\.|\[(\d+)\]/) .split(/\.|\[(\d+)\]/)
.filter(Boolean) .filter(Boolean)
.reduce((acc, propKey) => { .reduce((acc, propKey) => {
if (isContainerValue(acc) && propKey in acc) if (isContainerValue(acc) && propKey in acc) return acc[propKey];
return acc[propKey]
return fallback return fallback;
}, object as unknown) }, object as unknown);
return resolvedValue as TValue | undefined return resolvedValue as TValue | undefined;
} }

View File

@@ -1,17 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { AvatarRoot } from 'radix-vue' import { AvatarRoot } from "radix-vue";
import { type AvatarVariants, avatarVariant } from '.' import { type AvatarVariants, avatarVariant } from ".";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<{ const props = withDefaults(
class?: HTMLAttributes['class'] defineProps<{
size?: AvatarVariants['size'] class?: HTMLAttributes["class"];
shape?: AvatarVariants['shape'] size?: AvatarVariants["size"];
}>(), { shape?: AvatarVariants["shape"];
size: 'sm', }>(),
shape: 'circle', {
}) size: "sm",
shape: "circle",
},
);
</script> </script>
<template> <template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue' import { AvatarFallback, type AvatarFallbackProps } from "radix-vue";
const props = defineProps<AvatarFallbackProps>() const props = defineProps<AvatarFallbackProps>();
</script> </script>
<template> <template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { AvatarImage, type AvatarImageProps } from 'radix-vue' import { AvatarImage, type AvatarImageProps } from "radix-vue";
const props = defineProps<AvatarImageProps>() const props = defineProps<AvatarImageProps>();
</script> </script>
<template> <template>

View File

@@ -1,24 +1,24 @@
import { type VariantProps, cva } from 'class-variance-authority' import { type VariantProps, cva } from "class-variance-authority";
export { default as Avatar } from './Avatar.vue' export { default as Avatar } from "./Avatar.vue";
export { default as AvatarImage } from './AvatarImage.vue' export { default as AvatarImage } from "./AvatarImage.vue";
export { default as AvatarFallback } from './AvatarFallback.vue' export { default as AvatarFallback } from "./AvatarFallback.vue";
export const avatarVariant = cva( export const avatarVariant = cva(
'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden', "inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden",
{ {
variants: { variants: {
size: { size: {
sm: 'h-10 w-10 text-xs', sm: "h-10 w-10 text-xs",
base: 'h-16 w-16 text-2xl', base: "h-16 w-16 text-2xl",
lg: 'h-32 w-32 text-5xl', lg: "h-32 w-32 text-5xl",
}, },
shape: { shape: {
circle: 'rounded-full', circle: "rounded-full",
square: 'rounded-md', square: "rounded-md",
}, },
}, },
}, },
) );
export type AvatarVariants = VariantProps<typeof avatarVariant> export type AvatarVariants = VariantProps<typeof avatarVariant>;

View File

@@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { type BadgeVariants, badgeVariants } from '.' import { type BadgeVariants, badgeVariants } from ".";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
variant?: BadgeVariants['variant'] variant?: BadgeVariants["variant"];
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,25 +1,25 @@
import { type VariantProps, cva } from 'class-variance-authority' import { type VariantProps, cva } from "class-variance-authority";
export { default as Badge } from './Badge.vue' export { default as Badge } from "./Badge.vue";
export const badgeVariants = cva( export const badgeVariants = cva(
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {
default: default:
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: destructive:
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: 'text-foreground', outline: "text-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
}, },
}, },
) );
export type BadgeVariants = VariantProps<typeof badgeVariants> export type BadgeVariants = VariantProps<typeof badgeVariants>;

View File

@@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { DotsHorizontalIcon } from '@radix-icons/vue' import { DotsHorizontalIcon } from "@radix-icons/vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,11 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { Primitive, type PrimitiveProps } from 'radix-vue' import { Primitive, type PrimitiveProps } from "radix-vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>(), { const props = withDefaults(
as: 'a', defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(),
}) {
as: "a",
},
);
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { ChevronRightIcon } from '@radix-icons/vue' import { ChevronRightIcon } from "@radix-icons/vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,7 +1,7 @@
export { default as Breadcrumb } from './Breadcrumb.vue' export { default as Breadcrumb } from "./Breadcrumb.vue";
export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue' export { default as BreadcrumbEllipsis } from "./BreadcrumbEllipsis.vue";
export { default as BreadcrumbItem } from './BreadcrumbItem.vue' export { default as BreadcrumbItem } from "./BreadcrumbItem.vue";
export { default as BreadcrumbLink } from './BreadcrumbLink.vue' export { default as BreadcrumbLink } from "./BreadcrumbLink.vue";
export { default as BreadcrumbList } from './BreadcrumbList.vue' export { default as BreadcrumbList } from "./BreadcrumbList.vue";
export { default as BreadcrumbPage } from './BreadcrumbPage.vue' export { default as BreadcrumbPage } from "./BreadcrumbPage.vue";
export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue' export { default as BreadcrumbSeparator } from "./BreadcrumbSeparator.vue";

View File

@@ -1,18 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { Primitive, type PrimitiveProps } from 'radix-vue' import { Primitive, type PrimitiveProps } from "radix-vue";
import { type ButtonVariants, buttonVariants } from '.' import { type ButtonVariants, buttonVariants } from ".";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant'] variant?: ButtonVariants["variant"];
size?: ButtonVariants['size'] size?: ButtonVariants["size"];
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
as: 'button', as: "button",
}) });
</script> </script>
<template> <template>

View File

@@ -1,35 +1,36 @@
import { type VariantProps, cva } from 'class-variance-authority' import { type VariantProps, cva } from "class-variance-authority";
export { default as Button } from './Button.vue' export { default as Button } from "./Button.vue";
export const buttonVariants = cva( export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{ {
variants: { variants: {
variant: { variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', default:
destructive: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', destructive:
outline: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', outline:
secondary: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', secondary:
ghost: 'hover:bg-accent hover:text-accent-foreground', "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
link: 'text-primary underline-offset-4 hover:underline', ghost: "hover:bg-accent hover:text-accent-foreground",
}, link: "text-primary underline-offset-4 hover:underline",
size: { },
default: 'h-9 px-4 py-2', size: {
xs: 'h-7 rounded px-2', default: "h-9 px-4 py-2",
sm: 'h-8 rounded-md px-3 text-xs', xs: "h-7 rounded px-2",
lg: 'h-10 rounded-md px-8', sm: "h-8 rounded-md px-3 text-xs",
icon: 'h-9 w-9', lg: "h-10 rounded-md px-8",
}, icon: "h-9 w-9",
}, },
defaultVariants: { },
variant: 'default', defaultVariants: {
size: 'default', variant: "default",
}, size: "default",
}, },
) },
);
export type ButtonVariants = VariantProps<typeof buttonVariants> export type ButtonVariants = VariantProps<typeof buttonVariants>;

View File

@@ -1,20 +1,39 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue' import {
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from '.' CalendarRoot,
import { cn } from '@/lib/utils' type CalendarRootEmits,
type CalendarRootProps,
useForwardPropsEmits,
} from "radix-vue";
import {
CalendarCell,
CalendarCellTrigger,
CalendarGrid,
CalendarGridBody,
CalendarGridHead,
CalendarGridRow,
CalendarHeadCell,
CalendarHeader,
CalendarHeading,
CalendarNextButton,
CalendarPrevButton,
} from ".";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarRootProps & { class?: HTMLAttributes["class"] }
>();
const emits = defineEmits<CalendarRootEmits>() const emits = defineEmits<CalendarRootEmits>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarCell,
type CalendarCellProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarCellProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,18 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue' import {
import { buttonVariants } from '@/components/ui/button' CalendarCellTrigger,
import { cn } from '@/lib/utils' type CalendarCellTriggerProps,
useForwardProps,
} from "radix-vue";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarCellTriggerProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarGrid, type CalendarGridProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarGrid,
type CalendarGridProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarGridProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { CalendarGridBody, type CalendarGridBodyProps } from 'radix-vue' import { CalendarGridBody, type CalendarGridBodyProps } from "radix-vue";
const props = defineProps<CalendarGridBodyProps>() const props = defineProps<CalendarGridBodyProps>();
</script> </script>
<template> <template>

View File

@@ -1,8 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { CalendarGridHead, type CalendarGridHeadProps } from 'radix-vue' import { CalendarGridHead, type CalendarGridHeadProps } from "radix-vue";
const props = defineProps<CalendarGridHeadProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarGridHeadProps & { class?: HTMLAttributes["class"] }
>();
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarGridRow, type CalendarGridRowProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarGridRow,
type CalendarGridRowProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarGridRowProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarHeadCell, type CalendarHeadCellProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarHeadCell,
type CalendarHeadCellProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarHeadCellProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarHeader, type CalendarHeaderProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarHeader,
type CalendarHeaderProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarHeaderProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarHeading, type CalendarHeadingProps, useForwardProps } from 'radix-vue' import {
import { cn } from '@/lib/utils' CalendarHeading,
type CalendarHeadingProps,
useForwardProps,
} from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarHeadingProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,19 +1,25 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarNext, type CalendarNextProps, useForwardProps } from 'radix-vue' import {
import { ChevronRightIcon } from '@radix-icons/vue' CalendarNext,
import { cn } from '@/lib/utils' type CalendarNextProps,
import { buttonVariants } from '@/components/ui/button' useForwardProps,
} from "radix-vue";
import { ChevronRightIcon } from "@radix-icons/vue";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const props = defineProps<CalendarNextProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarNextProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,19 +1,25 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import { CalendarPrev, type CalendarPrevProps, useForwardProps } from 'radix-vue' import {
import { ChevronLeftIcon } from '@radix-icons/vue' CalendarPrev,
import { cn } from '@/lib/utils' type CalendarPrevProps,
import { buttonVariants } from '@/components/ui/button' useForwardProps,
} from "radix-vue";
import { ChevronLeftIcon } from "@radix-icons/vue";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const props = defineProps<CalendarPrevProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
CalendarPrevProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>

View File

@@ -1,12 +1,12 @@
export { default as Calendar } from './Calendar.vue' export { default as Calendar } from "./Calendar.vue";
export { default as CalendarCell } from './CalendarCell.vue' export { default as CalendarCell } from "./CalendarCell.vue";
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue' export { default as CalendarCellTrigger } from "./CalendarCellTrigger.vue";
export { default as CalendarGrid } from './CalendarGrid.vue' export { default as CalendarGrid } from "./CalendarGrid.vue";
export { default as CalendarGridBody } from './CalendarGridBody.vue' export { default as CalendarGridBody } from "./CalendarGridBody.vue";
export { default as CalendarGridHead } from './CalendarGridHead.vue' export { default as CalendarGridHead } from "./CalendarGridHead.vue";
export { default as CalendarGridRow } from './CalendarGridRow.vue' export { default as CalendarGridRow } from "./CalendarGridRow.vue";
export { default as CalendarHeadCell } from './CalendarHeadCell.vue' export { default as CalendarHeadCell } from "./CalendarHeadCell.vue";
export { default as CalendarHeader } from './CalendarHeader.vue' export { default as CalendarHeader } from "./CalendarHeader.vue";
export { default as CalendarHeading } from './CalendarHeading.vue' export { default as CalendarHeading } from "./CalendarHeading.vue";
export { default as CalendarNextButton } from './CalendarNextButton.vue' export { default as CalendarNextButton } from "./CalendarNextButton.vue";
export { default as CalendarPrevButton } from './CalendarPrevButton.vue' export { default as CalendarPrevButton } from "./CalendarPrevButton.vue";

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from "vue";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,6 +1,6 @@
export { default as Card } from './Card.vue' export { default as Card } from "./Card.vue";
export { default as CardHeader } from './CardHeader.vue' export { default as CardHeader } from "./CardHeader.vue";
export { default as CardTitle } from './CardTitle.vue' export { default as CardTitle } from "./CardTitle.vue";
export { default as CardDescription } from './CardDescription.vue' export { default as CardDescription } from "./CardDescription.vue";
export { default as CardContent } from './CardContent.vue' export { default as CardContent } from "./CardContent.vue";
export { default as CardFooter } from './CardFooter.vue' export { default as CardFooter } from "./CardFooter.vue";

View File

@@ -1,41 +1,53 @@
<script setup lang="ts"> <script setup lang="ts">
import { useProvideCarousel } from './useCarousel' import { useProvideCarousel } from "./useCarousel";
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface' import type {
import { cn } from '@/lib/utils' CarouselEmits,
CarouselProps,
WithClassAsProps,
} from "./interface";
import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), { const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
orientation: 'horizontal', orientation: "horizontal",
}) });
const emits = defineEmits<CarouselEmits>() const emits = defineEmits<CarouselEmits>();
const { canScrollNext, canScrollPrev, carouselApi, carouselRef, orientation, scrollNext, scrollPrev } = useProvideCarousel(props, emits) const {
canScrollNext,
canScrollPrev,
carouselApi,
carouselRef,
orientation,
scrollNext,
scrollPrev,
} = useProvideCarousel(props, emits);
defineExpose({ defineExpose({
canScrollNext, canScrollNext,
canScrollPrev, canScrollPrev,
carouselApi, carouselApi,
carouselRef, carouselRef,
orientation, orientation,
scrollNext, scrollNext,
scrollPrev, scrollPrev,
}) });
function onKeyDown(event: KeyboardEvent) { function onKeyDown(event: KeyboardEvent) {
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft' const prevKey = props.orientation === "vertical" ? "ArrowUp" : "ArrowLeft";
const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight' const nextKey = props.orientation === "vertical" ? "ArrowDown" : "ArrowRight";
if (event.key === prevKey) { if (event.key === prevKey) {
event.preventDefault() event.preventDefault();
scrollPrev() scrollPrev();
return return;
} }
if (event.key === nextKey) { if (event.key === nextKey) {
event.preventDefault() event.preventDefault();
scrollNext() scrollNext();
} }
} }
</script> </script>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCarousel } from './useCarousel' import { useCarousel } from "./useCarousel";
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from "./interface";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) });
const props = defineProps<WithClassAsProps>() const props = defineProps<WithClassAsProps>();
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel();
</script> </script>
<template> <template>

View File

@@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCarousel } from './useCarousel' import { useCarousel } from "./useCarousel";
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from "./interface";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = defineProps<WithClassAsProps>() const props = defineProps<WithClassAsProps>();
const { orientation } = useCarousel() const { orientation } = useCarousel();
</script> </script>
<template> <template>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowRightIcon } from '@radix-icons/vue' import { ArrowRightIcon } from "@radix-icons/vue";
import { useCarousel } from './useCarousel' import { useCarousel } from "./useCarousel";
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from "./interface";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { Button } from '@/components/ui/button' import { Button } from "@/components/ui/button";
const props = defineProps<WithClassAsProps>() const props = defineProps<WithClassAsProps>();
const { orientation, canScrollNext, scrollNext } = useCarousel() const { orientation, canScrollNext, scrollNext } = useCarousel();
</script> </script>
<template> <template>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowLeftIcon } from '@radix-icons/vue' import { ArrowLeftIcon } from "@radix-icons/vue";
import { useCarousel } from './useCarousel' import { useCarousel } from "./useCarousel";
import type { WithClassAsProps } from './interface' import type { WithClassAsProps } from "./interface";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { Button } from '@/components/ui/button' import { Button } from "@/components/ui/button";
const props = defineProps<WithClassAsProps>() const props = defineProps<WithClassAsProps>();
const { orientation, canScrollPrev, scrollPrev } = useCarousel() const { orientation, canScrollPrev, scrollPrev } = useCarousel();
</script> </script>
<template> <template>

View File

@@ -1,10 +1,8 @@
export { default as Carousel } from './Carousel.vue' export { default as Carousel } from "./Carousel.vue";
export { default as CarouselContent } from './CarouselContent.vue' export { default as CarouselContent } from "./CarouselContent.vue";
export { default as CarouselItem } from './CarouselItem.vue' export { default as CarouselItem } from "./CarouselItem.vue";
export { default as CarouselPrevious } from './CarouselPrevious.vue' export { default as CarouselPrevious } from "./CarouselPrevious.vue";
export { default as CarouselNext } from './CarouselNext.vue' export { default as CarouselNext } from "./CarouselNext.vue";
export { useCarousel } from './useCarousel' export { useCarousel } from "./useCarousel";
export type { export type { UnwrapRefCarouselApi as CarouselApi } from "./interface";
UnwrapRefCarouselApi as CarouselApi,
} from './interface'

View File

@@ -1,26 +1,24 @@
import type { HTMLAttributes, UnwrapRef } from 'vue' import type { HTMLAttributes, UnwrapRef } from "vue";
import type useEmblaCarousel from 'embla-carousel-vue' import type useEmblaCarousel from "embla-carousel-vue";
import type { import type { EmblaCarouselVueType } from "embla-carousel-vue";
EmblaCarouselVueType,
} from 'embla-carousel-vue'
type CarouselApi = EmblaCarouselVueType[1] type CarouselApi = EmblaCarouselVueType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel> type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0] type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1] type CarouselPlugin = UseCarouselParameters[1];
export type UnwrapRefCarouselApi = UnwrapRef<CarouselApi> export type UnwrapRefCarouselApi = UnwrapRef<CarouselApi>;
export interface CarouselProps { export interface CarouselProps {
opts?: CarouselOptions opts?: CarouselOptions;
plugins?: CarouselPlugin plugins?: CarouselPlugin;
orientation?: 'horizontal' | 'vertical' orientation?: "horizontal" | "vertical";
} }
export interface CarouselEmits { export interface CarouselEmits {
(e: 'init-api', payload: UnwrapRefCarouselApi): void (e: "init-api", payload: UnwrapRefCarouselApi): void;
} }
export interface WithClassAsProps { export interface WithClassAsProps {
class?: HTMLAttributes['class'] class?: HTMLAttributes["class"];
} }

View File

@@ -1,56 +1,66 @@
import { createInjectionState } from '@vueuse/core' import { createInjectionState } from "@vueuse/core";
import emblaCarouselVue from 'embla-carousel-vue' import emblaCarouselVue from "embla-carousel-vue";
import { onMounted, ref } from 'vue' import { onMounted, ref } from "vue";
import type { UnwrapRefCarouselApi as CarouselApi, CarouselEmits, CarouselProps } from './interface' import type {
UnwrapRefCarouselApi as CarouselApi,
CarouselEmits,
CarouselProps,
} from "./interface";
const [useProvideCarousel, useInjectCarousel] = createInjectionState( const [useProvideCarousel, useInjectCarousel] = createInjectionState(
({ ({ opts, orientation, plugins }: CarouselProps, emits: CarouselEmits) => {
opts, const [emblaNode, emblaApi] = emblaCarouselVue(
orientation, {
plugins, ...opts,
}: CarouselProps, emits: CarouselEmits) => { axis: orientation === "horizontal" ? "x" : "y",
const [emblaNode, emblaApi] = emblaCarouselVue({ },
...opts, plugins,
axis: orientation === 'horizontal' ? 'x' : 'y', );
}, plugins)
function scrollPrev() { function scrollPrev() {
emblaApi.value?.scrollPrev() emblaApi.value?.scrollPrev();
} }
function scrollNext() { function scrollNext() {
emblaApi.value?.scrollNext() emblaApi.value?.scrollNext();
} }
const canScrollNext = ref(false) const canScrollNext = ref(false);
const canScrollPrev = ref(false) const canScrollPrev = ref(false);
function onSelect(api: CarouselApi) { function onSelect(api: CarouselApi) {
canScrollNext.value = api?.canScrollNext() || false canScrollNext.value = api?.canScrollNext() || false;
canScrollPrev.value = api?.canScrollPrev() || false canScrollPrev.value = api?.canScrollPrev() || false;
} }
onMounted(() => { onMounted(() => {
if (!emblaApi.value) if (!emblaApi.value) return;
return
emblaApi.value?.on('init', onSelect) emblaApi.value?.on("init", onSelect);
emblaApi.value?.on('reInit', onSelect) emblaApi.value?.on("reInit", onSelect);
emblaApi.value?.on('select', onSelect) emblaApi.value?.on("select", onSelect);
emits('init-api', emblaApi.value) emits("init-api", emblaApi.value);
}) });
return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation } return {
}, carouselRef: emblaNode,
) carouselApi: emblaApi,
canScrollPrev,
canScrollNext,
scrollPrev,
scrollNext,
orientation,
};
},
);
function useCarousel() { function useCarousel() {
const carouselState = useInjectCarousel() const carouselState = useInjectCarousel();
if (!carouselState) if (!carouselState)
throw new Error('useCarousel must be used within a <Carousel />') throw new Error("useCarousel must be used within a <Carousel />");
return carouselState return carouselState;
} }
export { useCarousel, useProvideCarousel } export { useCarousel, useProvideCarousel };

View File

@@ -1,62 +1,75 @@
<script setup lang="ts" generic="T extends Record<string, any>"> <script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts' import { type BulletLegendItemInterface, CurveType } from "@unovis/ts";
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue' import { VisArea, VisAxis, VisLine, VisXYContainer } from "@unovis/vue";
import { Area, Axis, Line } from '@unovis/ts' import { Area, Axis, Line } from "@unovis/ts";
import { type Component, computed, ref } from 'vue' import { type Component, computed, ref } from "vue";
import { useMounted } from '@vueuse/core' import { useMounted } from "@vueuse/core";
import { useId } from 'radix-vue' import { useId } from "radix-vue";
import type { BaseChartProps } from '.' import type { BaseChartProps } from ".";
import { ChartCrosshair, ChartLegend, defaultColors } from '@/components/ui/chart' import {
import { cn } from '@/lib/utils' ChartCrosshair,
ChartLegend,
defaultColors,
} from "@/components/ui/chart";
import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<BaseChartProps<T> & { const props = withDefaults(
/** defineProps<
* Render custom tooltip component. BaseChartProps<T> & {
*/ /**
customTooltip?: Component * Render custom tooltip component.
/** */
* Type of curve customTooltip?: Component;
*/ /**
curveType?: CurveType * Type of curve
/** */
* Controls the visibility of gradient. curveType?: CurveType;
* @default true /**
*/ * Controls the visibility of gradient.
showGradiant?: boolean * @default true
}>(), { */
curveType: CurveType.MonotoneX, showGradiant?: boolean;
filterOpacity: 0.2, }
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), >(),
showXAxis: true, {
showYAxis: true, curveType: CurveType.MonotoneX,
showTooltip: true, filterOpacity: 0.2,
showLegend: true, margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showGridLine: true, showXAxis: true,
showGradiant: true, showYAxis: true,
}) showTooltip: true,
showLegend: true,
showGridLine: true,
showGradiant: true,
},
);
const emits = defineEmits<{ const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number] legendItemClick: [d: BulletLegendItemInterface, i: number];
}>() }>();
type KeyOfT = Extract<keyof T, string> type KeyOfT = Extract<keyof T, string>;
type Data = typeof props.data[number] type Data = (typeof props.data)[number];
const chartRef = useId() const chartRef = useId();
const index = computed(() => props.index as KeyOfT) const index = computed(() => props.index as KeyOfT);
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length)) const colors = computed(() =>
props.colors?.length ? props.colors : defaultColors(props.categories.length),
);
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({ const legendItems = ref<BulletLegendItemInterface[]>(
name: category, props.categories.map((category, i) => ({
color: colors.value[i], name: category,
inactive: false, color: colors.value[i],
}))) inactive: false,
})),
);
const isMounted = useMounted() const isMounted = useMounted();
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) { function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i) emits("legendItemClick", d, i);
} }
</script> </script>

View File

@@ -1,66 +1,74 @@
export { default as AreaChart } from './AreaChart.vue' export { default as AreaChart } from "./AreaChart.vue";
import type { Spacing } from '@unovis/ts' import type { Spacing } from "@unovis/ts";
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string> type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>;
export interface BaseChartProps<T extends Record<string, any>> { export interface BaseChartProps<T extends Record<string, any>> {
/** /**
* The source data, in which each entry is a dictionary. * The source data, in which each entry is a dictionary.
*/ */
data: T[] data: T[];
/** /**
* Select the categories from your data. Used to populate the legend and toolip. * Select the categories from your data. Used to populate the legend and toolip.
*/ */
categories: KeyOf<T>[] categories: KeyOf<T>[];
/** /**
* Sets the key to map the data to the axis. * Sets the key to map the data to the axis.
*/ */
index: KeyOf<T> index: KeyOf<T>;
/** /**
* Change the default colors. * Change the default colors.
*/ */
colors?: string[] colors?: string[];
/** /**
* Margin of each the container * Margin of each the container
*/ */
margin?: Spacing margin?: Spacing;
/** /**
* Change the opacity of the non-selected field * Change the opacity of the non-selected field
* @default 0.2 * @default 0.2
*/ */
filterOpacity?: number filterOpacity?: number;
/** /**
* Function to format X label * Function to format X label
*/ */
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string xFormatter?: (
/** tick: number | Date,
* Function to format Y label i: number,
*/ ticks: number[] | Date[],
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string ) => string;
/** /**
* Controls the visibility of the X axis. * Function to format Y label
* @default true */
*/ yFormatter?: (
showXAxis?: boolean tick: number | Date,
/** i: number,
* Controls the visibility of the Y axis. ticks: number[] | Date[],
* @default true ) => string;
*/ /**
showYAxis?: boolean * Controls the visibility of the X axis.
/** * @default true
* Controls the visibility of tooltip. */
* @default true showXAxis?: boolean;
*/ /**
showTooltip?: boolean * Controls the visibility of the Y axis.
/** * @default true
* Controls the visibility of legend. */
* @default true showYAxis?: boolean;
*/ /**
showLegend?: boolean * Controls the visibility of tooltip.
/** * @default true
* Controls the visibility of gridline. */
* @default true showTooltip?: boolean;
*/ /**
showGridLine?: boolean * Controls the visibility of legend.
* @default true
*/
showLegend?: boolean;
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean;
} }

View File

@@ -1,62 +1,86 @@
<script setup lang="ts" generic="T extends Record<string, any>"> <script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface } from '@unovis/ts' import type { BulletLegendItemInterface } from "@unovis/ts";
import { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue' import {
import { Axis, GroupedBar, StackedBar } from '@unovis/ts' VisAxis,
import { type Component, computed, ref } from 'vue' VisGroupedBar,
import { useMounted } from '@vueuse/core' VisStackedBar,
import type { BaseChartProps } from '.' VisXYContainer,
import { ChartCrosshair, ChartLegend, defaultColors } from '@/components/ui/chart' } from "@unovis/vue";
import { cn } from '@/lib/utils' import { Axis, GroupedBar, StackedBar } from "@unovis/ts";
import { type Component, computed, ref } from "vue";
import { useMounted } from "@vueuse/core";
import type { BaseChartProps } from ".";
import {
ChartCrosshair,
ChartLegend,
defaultColors,
} from "@/components/ui/chart";
import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<BaseChartProps<T> & { const props = withDefaults(
/** defineProps<
* Render custom tooltip component. BaseChartProps<T> & {
*/ /**
customTooltip?: Component * Render custom tooltip component.
/** */
* Change the type of the chart customTooltip?: Component;
* @default "grouped" /**
*/ * Change the type of the chart
type?: 'stacked' | 'grouped' * @default "grouped"
/** */
* Rounded bar corners type?: "stacked" | "grouped";
* @default 0 /**
*/ * Rounded bar corners
roundedCorners?: number * @default 0
}>(), { */
type: 'grouped', roundedCorners?: number;
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), }
filterOpacity: 0.2, >(),
roundedCorners: 0, {
showXAxis: true, type: "grouped",
showYAxis: true, margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showTooltip: true, filterOpacity: 0.2,
showLegend: true, roundedCorners: 0,
showGridLine: true, showXAxis: true,
}) showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
},
);
const emits = defineEmits<{ const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number] legendItemClick: [d: BulletLegendItemInterface, i: number];
}>() }>();
type KeyOfT = Extract<keyof T, string> type KeyOfT = Extract<keyof T, string>;
type Data = typeof props.data[number] type Data = (typeof props.data)[number];
const index = computed(() => props.index as KeyOfT) const index = computed(() => props.index as KeyOfT);
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length)) const colors = computed(() =>
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({ props.colors?.length ? props.colors : defaultColors(props.categories.length),
name: category, );
color: colors.value[i], const legendItems = ref<BulletLegendItemInterface[]>(
inactive: false, props.categories.map((category, i) => ({
}))) name: category,
color: colors.value[i],
inactive: false,
})),
);
const isMounted = useMounted() const isMounted = useMounted();
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) { function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i) emits("legendItemClick", d, i);
} }
const VisBarComponent = computed(() => props.type === 'grouped' ? VisGroupedBar : VisStackedBar) const VisBarComponent = computed(() =>
const selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.selectors.bar : StackedBar.selectors.bar) props.type === "grouped" ? VisGroupedBar : VisStackedBar,
);
const selectorsBar = computed(() =>
props.type === "grouped"
? GroupedBar.selectors.bar
: StackedBar.selectors.bar,
);
</script> </script>
<template> <template>

View File

@@ -1,66 +1,74 @@
export { default as BarChart } from './BarChart.vue' export { default as BarChart } from "./BarChart.vue";
import type { Spacing } from '@unovis/ts' import type { Spacing } from "@unovis/ts";
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string> type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>;
export interface BaseChartProps<T extends Record<string, any>> { export interface BaseChartProps<T extends Record<string, any>> {
/** /**
* The source data, in which each entry is a dictionary. * The source data, in which each entry is a dictionary.
*/ */
data: T[] data: T[];
/** /**
* Select the categories from your data. Used to populate the legend and toolip. * Select the categories from your data. Used to populate the legend and toolip.
*/ */
categories: KeyOf<T>[] categories: KeyOf<T>[];
/** /**
* Sets the key to map the data to the axis. * Sets the key to map the data to the axis.
*/ */
index: KeyOf<T> index: KeyOf<T>;
/** /**
* Change the default colors. * Change the default colors.
*/ */
colors?: string[] colors?: string[];
/** /**
* Margin of each the container * Margin of each the container
*/ */
margin?: Spacing margin?: Spacing;
/** /**
* Change the opacity of the non-selected field * Change the opacity of the non-selected field
* @default 0.2 * @default 0.2
*/ */
filterOpacity?: number filterOpacity?: number;
/** /**
* Function to format X label * Function to format X label
*/ */
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string xFormatter?: (
/** tick: number | Date,
* Function to format Y label i: number,
*/ ticks: number[] | Date[],
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string ) => string;
/** /**
* Controls the visibility of the X axis. * Function to format Y label
* @default true */
*/ yFormatter?: (
showXAxis?: boolean tick: number | Date,
/** i: number,
* Controls the visibility of the Y axis. ticks: number[] | Date[],
* @default true ) => string;
*/ /**
showYAxis?: boolean * Controls the visibility of the X axis.
/** * @default true
* Controls the visibility of tooltip. */
* @default true showXAxis?: boolean;
*/ /**
showTooltip?: boolean * Controls the visibility of the Y axis.
/** * @default true
* Controls the visibility of legend. */
* @default true showYAxis?: boolean;
*/ /**
showLegend?: boolean * Controls the visibility of tooltip.
/** * @default true
* Controls the visibility of gridline. */
* @default true showTooltip?: boolean;
*/ /**
showGridLine?: boolean * Controls the visibility of legend.
* @default true
*/
showLegend?: boolean;
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean;
} }

View File

@@ -1,62 +1,86 @@
<script setup lang="ts" generic="T extends Record<string, any>"> <script setup lang="ts" generic="T extends Record<string, any>">
import { VisDonut, VisSingleContainer } from '@unovis/vue' import { VisDonut, VisSingleContainer } from "@unovis/vue";
import { Donut } from '@unovis/ts' import { Donut } from "@unovis/ts";
import { type Component, computed, ref } from 'vue' import { type Component, computed, ref } from "vue";
import { useMounted } from '@vueuse/core' import { useMounted } from "@vueuse/core";
import type { BaseChartProps } from '.' import type { BaseChartProps } from ".";
import { ChartSingleTooltip, defaultColors } from '@/components/ui/chart' import { ChartSingleTooltip, defaultColors } from "@/components/ui/chart";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<Pick<BaseChartProps<T>, 'data' | 'colors' | 'index' | 'margin' | 'showLegend' | 'showTooltip' | 'filterOpacity'> & { const props = withDefaults(
/** defineProps<
* Sets the name of the key containing the quantitative chart values. Pick<
*/ BaseChartProps<T>,
category: KeyOfT | "data"
/** | "colors"
* Change the type of the chart | "index"
* @default "donut" | "margin"
*/ | "showLegend"
type?: 'donut' | 'pie' | "showTooltip"
/** | "filterOpacity"
* Function to sort the segment > & {
*/ /**
sortFunction?: (a: any, b: any) => number | undefined * Sets the name of the key containing the quantitative chart values.
/** */
* Controls the formatting for the label. category: KeyOfT;
*/ /**
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string * Change the type of the chart
/** * @default "donut"
* Render custom tooltip component. */
*/ type?: "donut" | "pie";
customTooltip?: Component /**
}>(), { * Function to sort the segment
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), */
sortFunction: () => undefined, sortFunction?: (a: any, b: any) => number | undefined;
valueFormatter: (tick: number) => `${tick}`, /**
type: 'donut', * Controls the formatting for the label.
filterOpacity: 0.2, */
showTooltip: true, valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string;
showLegend: true, /**
}) * Render custom tooltip component.
*/
customTooltip?: Component;
}
>(),
{
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
valueFormatter: (tick: number) => `${tick}`,
type: "donut",
filterOpacity: 0.2,
showTooltip: true,
showLegend: true,
},
);
type KeyOfT = Extract<keyof T, string> type KeyOfT = Extract<keyof T, string>;
type Data = typeof props.data[number] type Data = (typeof props.data)[number];
const category = computed(() => props.category as KeyOfT) const category = computed(() => props.category as KeyOfT);
const index = computed(() => props.index as KeyOfT) const index = computed(() => props.index as KeyOfT);
const isMounted = useMounted() const isMounted = useMounted();
const activeSegmentKey = ref<string>() const activeSegmentKey = ref<string>();
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.data.filter(d => d[props.category]).filter(Boolean).length)) const colors = computed(() =>
const legendItems = computed(() => props.data.map((item, i) => ({ props.colors?.length
name: item[props.index], ? props.colors
color: colors.value[i], : defaultColors(
inactive: false, props.data.filter((d) => d[props.category]).filter(Boolean).length,
}))) ),
);
const legendItems = computed(() =>
props.data.map((item, i) => ({
name: item[props.index],
color: colors.value[i],
inactive: false,
})),
);
const totalValue = computed(() => props.data.reduce((prev, curr) => { const totalValue = computed(() =>
return prev + curr[props.category] props.data.reduce((prev, curr) => {
}, 0)) return prev + curr[props.category];
}, 0),
);
</script> </script>
<template> <template>

View File

@@ -1,39 +1,39 @@
export { default as DonutChart } from './DonutChart.vue' export { default as DonutChart } from "./DonutChart.vue";
import type { Spacing } from '@unovis/ts' import type { Spacing } from "@unovis/ts";
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string> type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>;
export interface BaseChartProps<T extends Record<string, any>> { export interface BaseChartProps<T extends Record<string, any>> {
/** /**
* The source data, in which each entry is a dictionary. * The source data, in which each entry is a dictionary.
*/ */
data: T[] data: T[];
/** /**
* Sets the key to map the data to the axis. * Sets the key to map the data to the axis.
*/ */
index: KeyOf<T> index: KeyOf<T>;
/** /**
* Change the default colors. * Change the default colors.
*/ */
colors?: string[] colors?: string[];
/** /**
* Margin of each the container * Margin of each the container
*/ */
margin?: Spacing margin?: Spacing;
/** /**
* Change the opacity of the non-selected field * Change the opacity of the non-selected field
* @default 0.2 * @default 0.2
*/ */
filterOpacity?: number filterOpacity?: number;
/** /**
* Controls the visibility of tooltip. * Controls the visibility of tooltip.
* @default true * @default true
*/ */
showTooltip?: boolean showTooltip?: boolean;
/** /**
* Controls the visibility of legend. * Controls the visibility of legend.
* @default true * @default true
*/ */
showLegend?: boolean showLegend?: boolean;
} }

View File

@@ -1,53 +1,66 @@
<script setup lang="ts" generic="T extends Record<string, any>"> <script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts' import { type BulletLegendItemInterface, CurveType } from "@unovis/ts";
import { VisAxis, VisLine, VisXYContainer } from '@unovis/vue' import { VisAxis, VisLine, VisXYContainer } from "@unovis/vue";
import { Axis, Line } from '@unovis/ts' import { Axis, Line } from "@unovis/ts";
import { type Component, computed, ref } from 'vue' import { type Component, computed, ref } from "vue";
import { useMounted } from '@vueuse/core' import { useMounted } from "@vueuse/core";
import type { BaseChartProps } from '.' import type { BaseChartProps } from ".";
import { ChartCrosshair, ChartLegend, defaultColors } from '@/components/ui/chart' import {
import { cn } from '@/lib/utils' ChartCrosshair,
ChartLegend,
defaultColors,
} from "@/components/ui/chart";
import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<BaseChartProps<T> & { const props = withDefaults(
/** defineProps<
* Render custom tooltip component. BaseChartProps<T> & {
*/ /**
customTooltip?: Component * Render custom tooltip component.
/** */
* Type of curve customTooltip?: Component;
*/ /**
curveType?: CurveType * Type of curve
}>(), { */
curveType: CurveType.MonotoneX, curveType?: CurveType;
filterOpacity: 0.2, }
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), >(),
showXAxis: true, {
showYAxis: true, curveType: CurveType.MonotoneX,
showTooltip: true, filterOpacity: 0.2,
showLegend: true, margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showGridLine: true, showXAxis: true,
}) showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
},
);
const emits = defineEmits<{ const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number] legendItemClick: [d: BulletLegendItemInterface, i: number];
}>() }>();
type KeyOfT = Extract<keyof T, string> type KeyOfT = Extract<keyof T, string>;
type Data = typeof props.data[number] type Data = (typeof props.data)[number];
const index = computed(() => props.index as KeyOfT) const index = computed(() => props.index as KeyOfT);
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length)) const colors = computed(() =>
props.colors?.length ? props.colors : defaultColors(props.categories.length),
);
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({ const legendItems = ref<BulletLegendItemInterface[]>(
name: category, props.categories.map((category, i) => ({
color: colors.value[i], name: category,
inactive: false, color: colors.value[i],
}))) inactive: false,
})),
);
const isMounted = useMounted() const isMounted = useMounted();
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) { function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i) emits("legendItemClick", d, i);
} }
</script> </script>

View File

@@ -1,66 +1,74 @@
export { default as LineChart } from './LineChart.vue' export { default as LineChart } from "./LineChart.vue";
import type { Spacing } from '@unovis/ts' import type { Spacing } from "@unovis/ts";
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string> type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>;
export interface BaseChartProps<T extends Record<string, any>> { export interface BaseChartProps<T extends Record<string, any>> {
/** /**
* The source data, in which each entry is a dictionary. * The source data, in which each entry is a dictionary.
*/ */
data: T[] data: T[];
/** /**
* Select the categories from your data. Used to populate the legend and toolip. * Select the categories from your data. Used to populate the legend and toolip.
*/ */
categories: KeyOf<T>[] categories: KeyOf<T>[];
/** /**
* Sets the key to map the data to the axis. * Sets the key to map the data to the axis.
*/ */
index: KeyOf<T> index: KeyOf<T>;
/** /**
* Change the default colors. * Change the default colors.
*/ */
colors?: string[] colors?: string[];
/** /**
* Margin of each the container * Margin of each the container
*/ */
margin?: Spacing margin?: Spacing;
/** /**
* Change the opacity of the non-selected field * Change the opacity of the non-selected field
* @default 0.2 * @default 0.2
*/ */
filterOpacity?: number filterOpacity?: number;
/** /**
* Function to format X label * Function to format X label
*/ */
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string xFormatter?: (
/** tick: number | Date,
* Function to format Y label i: number,
*/ ticks: number[] | Date[],
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string ) => string;
/** /**
* Controls the visibility of the X axis. * Function to format Y label
* @default true */
*/ yFormatter?: (
showXAxis?: boolean tick: number | Date,
/** i: number,
* Controls the visibility of the Y axis. ticks: number[] | Date[],
* @default true ) => string;
*/ /**
showYAxis?: boolean * Controls the visibility of the X axis.
/** * @default true
* Controls the visibility of tooltip. */
* @default true showXAxis?: boolean;
*/ /**
showTooltip?: boolean * Controls the visibility of the Y axis.
/** * @default true
* Controls the visibility of legend. */
* @default true showYAxis?: boolean;
*/ /**
showLegend?: boolean * Controls the visibility of tooltip.
/** * @default true
* Controls the visibility of gridline. */
* @default true showTooltip?: boolean;
*/ /**
showGridLine?: boolean * Controls the visibility of legend.
* @default true
*/
showLegend?: boolean;
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean;
} }

View File

@@ -1,40 +1,47 @@
<script setup lang="ts"> <script setup lang="ts">
import { VisCrosshair, VisTooltip } from '@unovis/vue' import { VisCrosshair, VisTooltip } from "@unovis/vue";
import type { BulletLegendItemInterface } from '@unovis/ts' import type { BulletLegendItemInterface } from "@unovis/ts";
import { omit } from '@unovis/ts' import { omit } from "@unovis/ts";
import { type Component, createApp } from 'vue' import { type Component, createApp } from "vue";
import { ChartTooltip } from '.' import { ChartTooltip } from ".";
const props = withDefaults(defineProps<{ const props = withDefaults(
colors: string[] defineProps<{
index: string colors: string[];
items: BulletLegendItemInterface[] index: string;
customTooltip?: Component items: BulletLegendItemInterface[];
}>(), { customTooltip?: Component;
colors: () => [], }>(),
}) {
colors: () => [],
},
);
// Use weakmap to store reference to each datapoint for Tooltip // Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap() const wm = new WeakMap();
function template(d: any) { function template(d: any) {
if (wm.has(d)) { if (wm.has(d)) {
return wm.get(d) return wm.get(d);
} } else {
else { const componentDiv = document.createElement("div");
const componentDiv = document.createElement('div') const omittedData = Object.entries(omit(d, [props.index])).map(
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => { ([key, value]) => {
const legendReference = props.items.find(i => i.name === key) const legendReference = props.items.find((i) => i.name === key);
return { ...legendReference, value } return { ...legendReference, value };
}) },
const TooltipComponent = props.customTooltip ?? ChartTooltip );
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv) const TooltipComponent = props.customTooltip ?? ChartTooltip;
wm.set(d, componentDiv.innerHTML) createApp(TooltipComponent, {
return componentDiv.innerHTML title: d[props.index].toString(),
} data: omittedData,
}).mount(componentDiv);
wm.set(d, componentDiv.innerHTML);
return componentDiv.innerHTML;
}
} }
function color(d: unknown, i: number) { function color(d: unknown, i: number) {
return props.colors[i] ?? 'transparent' return props.colors[i] ?? "transparent";
} }
</script> </script>

View File

@@ -1,43 +1,57 @@
<script setup lang="ts"> <script setup lang="ts">
import { VisBulletLegend } from '@unovis/vue' import { VisBulletLegend } from "@unovis/vue";
import type { BulletLegendItemInterface } from '@unovis/ts' import type { BulletLegendItemInterface } from "@unovis/ts";
import { BulletLegend } from '@unovis/ts' import { BulletLegend } from "@unovis/ts";
import { nextTick, onMounted, ref } from 'vue' import { nextTick, onMounted, ref } from "vue";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
const props = withDefaults(defineProps<{ items: BulletLegendItemInterface[] }>(), { const props = withDefaults(
items: () => [], defineProps<{ items: BulletLegendItemInterface[] }>(),
}) {
items: () => [],
},
);
const emits = defineEmits<{ const emits = defineEmits<{
'legendItemClick': [d: BulletLegendItemInterface, i: number] legendItemClick: [d: BulletLegendItemInterface, i: number];
'update:items': [payload: BulletLegendItemInterface[]] "update:items": [payload: BulletLegendItemInterface[]];
}>() }>();
const elRef = ref<HTMLElement>() const elRef = ref<HTMLElement>();
onMounted(() => { onMounted(() => {
const selector = `.${BulletLegend.selectors.item}` const selector = `.${BulletLegend.selectors.item}`;
nextTick(() => { nextTick(() => {
const elements = elRef.value?.querySelectorAll(selector) const elements = elRef.value?.querySelectorAll(selector);
const classes = buttonVariants({ variant: 'ghost', size: 'xs' }).split(' ') const classes = buttonVariants({ variant: "ghost", size: "xs" }).split(" ");
elements?.forEach(el => el.classList.add(...classes, '!inline-flex', '!mr-2')) elements?.forEach((el) =>
}) el.classList.add(...classes, "!inline-flex", "!mr-2"),
}) );
});
});
function onLegendItemClick(d: BulletLegendItemInterface, i: number) { function onLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i) emits("legendItemClick", d, i);
const isBulletActive = !props.items[i].inactive const isBulletActive = !props.items[i].inactive;
const isFilterApplied = props.items.some(i => i.inactive) const isFilterApplied = props.items.some((i) => i.inactive);
if (isFilterApplied && isBulletActive) { if (isFilterApplied && isBulletActive) {
// reset filter // reset filter
emits('update:items', props.items.map(item => ({ ...item, inactive: false }))) emits(
} "update:items",
else { props.items.map((item) => ({ ...item, inactive: false })),
// apply selection, set other item as inactive );
emits('update:items', props.items.map(item => item.name === d.name ? ({ ...d, inactive: false }) : { ...item, inactive: true })) } else {
} // apply selection, set other item as inactive
emits(
"update:items",
props.items.map((item) =>
item.name === d.name
? { ...d, inactive: false }
: { ...item, inactive: true },
),
);
}
} }
</script> </script>

View File

@@ -1,56 +1,69 @@
<script setup lang="ts"> <script setup lang="ts">
import { VisTooltip } from '@unovis/vue' import { VisTooltip } from "@unovis/vue";
import type { BulletLegendItemInterface } from '@unovis/ts' import type { BulletLegendItemInterface } from "@unovis/ts";
import { omit } from '@unovis/ts' import { omit } from "@unovis/ts";
import { type Component, createApp } from 'vue' import { type Component, createApp } from "vue";
import { ChartTooltip } from '.' import { ChartTooltip } from ".";
const props = withDefaults(defineProps<{ const props = withDefaults(
selector: string defineProps<{
index: string selector: string;
items?: BulletLegendItemInterface[] index: string;
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string items?: BulletLegendItemInterface[];
customTooltip?: Component valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string;
}>(), { customTooltip?: Component;
valueFormatter: (tick: number) => `${tick}`, }>(),
}) {
valueFormatter: (tick: number) => `${tick}`,
},
);
// Use weakmap to store reference to each datapoint for Tooltip // Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap() const wm = new WeakMap();
function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) { function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
if (props.index in d) { if (props.index in d) {
if (wm.has(d)) { if (wm.has(d)) {
return wm.get(d) return wm.get(d);
} } else {
else { const componentDiv = document.createElement("div");
const componentDiv = document.createElement('div') const omittedData = Object.entries(omit(d, [props.index])).map(
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => { ([key, value]) => {
const legendReference = props.items?.find(i => i.name === key) const legendReference = props.items?.find((i) => i.name === key);
return { ...legendReference, value: props.valueFormatter(value) } return { ...legendReference, value: props.valueFormatter(value) };
}) },
const TooltipComponent = props.customTooltip ?? ChartTooltip );
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv) const TooltipComponent = props.customTooltip ?? ChartTooltip;
wm.set(d, componentDiv.innerHTML) createApp(TooltipComponent, {
return componentDiv.innerHTML title: d[props.index],
} data: omittedData,
} }).mount(componentDiv);
wm.set(d, componentDiv.innerHTML);
return componentDiv.innerHTML;
}
} else {
const data = d.data;
else { if (wm.has(data)) {
const data = d.data return wm.get(data);
} else {
if (wm.has(data)) { const style = getComputedStyle(elements[i]);
return wm.get(data) const omittedData = [
} {
else { name: data.name,
const style = getComputedStyle(elements[i]) value: props.valueFormatter(data[props.index]),
const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }] color: style.fill,
const componentDiv = document.createElement('div') },
const TooltipComponent = props.customTooltip ?? ChartTooltip ];
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv) const componentDiv = document.createElement("div");
wm.set(d, componentDiv.innerHTML) const TooltipComponent = props.customTooltip ?? ChartTooltip;
return componentDiv.innerHTML createApp(TooltipComponent, {
} title: d[props.index],
} data: omittedData,
}).mount(componentDiv);
wm.set(d, componentDiv.innerHTML);
return componentDiv.innerHTML;
}
}
} }
</script> </script>

View File

@@ -1,14 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
defineProps<{ defineProps<{
title?: string title?: string;
data: { data: {
name: string name: string;
color: string color: string;
value: any value: any;
}[] }[];
}>() }>();
</script> </script>
<template> <template>

View File

@@ -1,18 +1,23 @@
export { default as ChartTooltip } from './ChartTooltip.vue' export { default as ChartTooltip } from "./ChartTooltip.vue";
export { default as ChartSingleTooltip } from './ChartSingleTooltip.vue' export { default as ChartSingleTooltip } from "./ChartSingleTooltip.vue";
export { default as ChartLegend } from './ChartLegend.vue' export { default as ChartLegend } from "./ChartLegend.vue";
export { default as ChartCrosshair } from './ChartCrosshair.vue' export { default as ChartCrosshair } from "./ChartCrosshair.vue";
export function defaultColors(count: number = 3) { export function defaultColors(count: number = 3) {
const quotient = Math.floor(count / 2) const quotient = Math.floor(count / 2);
const remainder = count % 2 const remainder = count % 2;
const primaryCount = quotient + remainder const primaryCount = quotient + remainder;
const secondaryCount = quotient const secondaryCount = quotient;
return [ return [
...Array.from(Array(primaryCount).keys()).map(i => `hsl(var(--vis-primary-color) / ${1 - (1 / primaryCount) * i})`), ...Array.from(Array(primaryCount).keys()).map(
...Array.from(Array(secondaryCount).keys()).map(i => `hsl(var(--vis-secondary-color) / ${1 - (1 / secondaryCount) * i})`), (i) => `hsl(var(--vis-primary-color) / ${1 - (1 / primaryCount) * i})`,
] ),
...Array.from(Array(secondaryCount).keys()).map(
(i) =>
`hsl(var(--vis-secondary-color) / ${1 - (1 / secondaryCount) * i})`,
),
];
} }
export * from './interface' export * from "./interface";

View File

@@ -1,64 +1,72 @@
import type { Spacing } from '@unovis/ts' import type { Spacing } from "@unovis/ts";
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string> type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>;
export interface BaseChartProps<T extends Record<string, any>> { export interface BaseChartProps<T extends Record<string, any>> {
/** /**
* The source data, in which each entry is a dictionary. * The source data, in which each entry is a dictionary.
*/ */
data: T[] data: T[];
/** /**
* Select the categories from your data. Used to populate the legend and toolip. * Select the categories from your data. Used to populate the legend and toolip.
*/ */
categories: KeyOf<T>[] categories: KeyOf<T>[];
/** /**
* Sets the key to map the data to the axis. * Sets the key to map the data to the axis.
*/ */
index: KeyOf<T> index: KeyOf<T>;
/** /**
* Change the default colors. * Change the default colors.
*/ */
colors?: string[] colors?: string[];
/** /**
* Margin of each the container * Margin of each the container
*/ */
margin?: Spacing margin?: Spacing;
/** /**
* Change the opacity of the non-selected field * Change the opacity of the non-selected field
* @default 0.2 * @default 0.2
*/ */
filterOpacity?: number filterOpacity?: number;
/** /**
* Function to format X label * Function to format X label
*/ */
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string xFormatter?: (
/** tick: number | Date,
* Function to format Y label i: number,
*/ ticks: number[] | Date[],
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string ) => string;
/** /**
* Controls the visibility of the X axis. * Function to format Y label
* @default true */
*/ yFormatter?: (
showXAxis?: boolean tick: number | Date,
/** i: number,
* Controls the visibility of the Y axis. ticks: number[] | Date[],
* @default true ) => string;
*/ /**
showYAxis?: boolean * Controls the visibility of the X axis.
/** * @default true
* Controls the visibility of tooltip. */
* @default true showXAxis?: boolean;
*/ /**
showTooltip?: boolean * Controls the visibility of the Y axis.
/** * @default true
* Controls the visibility of legend. */
* @default true showYAxis?: boolean;
*/ /**
showLegend?: boolean * Controls the visibility of tooltip.
/** * @default true
* Controls the visibility of gridline. */
* @default true showTooltip?: boolean;
*/ /**
showGridLine?: boolean * Controls the visibility of legend.
* @default true
*/
showLegend?: boolean;
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean;
} }

View File

@@ -1,20 +1,26 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue' import { type HTMLAttributes, computed } from "vue";
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue' import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue";
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue' import {
import { CheckIcon } from '@radix-icons/vue' CheckboxIndicator,
import { cn } from '@/lib/utils' CheckboxRoot,
useForwardPropsEmits,
} from "radix-vue";
import { CheckIcon } from "@radix-icons/vue";
import { cn } from "@/lib/utils";
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>() const props = defineProps<
const emits = defineEmits<CheckboxRootEmits>() CheckboxRootProps & { class?: HTMLAttributes["class"] }
>();
const emits = defineEmits<CheckboxRootEmits>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props const { class: _, ...delegated } = props;
return delegated return delegated;
}) });
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script> </script>
<template> <template>

View File

@@ -1 +1 @@
export { default as Checkbox } from './Checkbox.vue' export { default as Checkbox } from "./Checkbox.vue";

Some files were not shown because too many files have changed in this diff Show More