init backend, added admin page in front
Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
parent
5c6e641fbd
commit
ddc6c64f0f
89 changed files with 5083 additions and 9 deletions
15
summer2024-frontend/src/components/ui/alert/Alert.vue
Normal file
15
summer2024-frontend/src/components/ui/alert/Alert.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import { alertVariants } from ".";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
variant: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('text-sm [&_p]:leading-relaxed', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/alert/AlertTitle.vue
Normal file
13
summer2024-frontend/src/components/ui/alert/AlertTitle.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</h5>
|
||||
</template>
|
||||
21
summer2024-frontend/src/components/ui/alert/index.js
Normal file
21
summer2024-frontend/src/components/ui/alert/index.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { cva } from "class-variance-authority";
|
||||
|
||||
export { default as Alert } from "./Alert.vue";
|
||||
export { default as AlertTitle } from "./AlertTitle.vue";
|
||||
export { default as AlertDescription } from "./AlertDescription.vue";
|
||||
|
||||
export const alertVariants = cva(
|
||||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
20
summer2024-frontend/src/components/ui/card/Card.vue
Normal file
20
summer2024-frontend/src/components/ui/card/Card.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'rounded-lg border bg-card text-card-foreground shadow-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/card/CardContent.vue
Normal file
13
summer2024-frontend/src/components/ui/card/CardContent.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('p-6 pt-0', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/card/CardFooter.vue
Normal file
13
summer2024-frontend/src/components/ui/card/CardFooter.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('flex items-center p-6 pt-0', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/card/CardHeader.vue
Normal file
13
summer2024-frontend/src/components/ui/card/CardHeader.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
17
summer2024-frontend/src/components/ui/card/CardTitle.vue
Normal file
17
summer2024-frontend/src/components/ui/card/CardTitle.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3
|
||||
:class="
|
||||
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
6
summer2024-frontend/src/components/ui/card/index.js
Normal file
6
summer2024-frontend/src/components/ui/card/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { default as Card } from "./Card.vue";
|
||||
export { default as CardHeader } from "./CardHeader.vue";
|
||||
export { default as CardTitle } from "./CardTitle.vue";
|
||||
export { default as CardDescription } from "./CardDescription.vue";
|
||||
export { default as CardContent } from "./CardContent.vue";
|
||||
export { default as CardFooter } from "./CardFooter.vue";
|
||||
46
summer2024-frontend/src/components/ui/carousel/Carousel.vue
Normal file
46
summer2024-frontend/src/components/ui/carousel/Carousel.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<script setup>
|
||||
import { useProvideCarousel } from "./useCarousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
opts: { type: null, required: false },
|
||||
plugins: { type: null, required: false },
|
||||
orientation: { type: String, required: false, default: "horizontal" },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["init-api"]);
|
||||
|
||||
const carouselArgs = useProvideCarousel(props, emits);
|
||||
|
||||
defineExpose(carouselArgs);
|
||||
|
||||
function onKeyDown(event) {
|
||||
const prevKey = props.orientation === "vertical" ? "ArrowUp" : "ArrowLeft";
|
||||
const nextKey = props.orientation === "vertical" ? "ArrowDown" : "ArrowRight";
|
||||
|
||||
if (event.key === prevKey) {
|
||||
event.preventDefault();
|
||||
carouselArgs.scrollPrev();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === nextKey) {
|
||||
event.preventDefault();
|
||||
carouselArgs.scrollNext();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="cn('relative', props.class)"
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
tabindex="0"
|
||||
@keydown="onKeyDown"
|
||||
>
|
||||
<slot v-bind="carouselArgs" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<script setup>
|
||||
import { useCarousel } from "./useCarousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { carouselRef, orientation } = useCarousel();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="carouselRef" class="overflow-hidden">
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex',
|
||||
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<script setup>
|
||||
import { useCarousel } from "./useCarousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { orientation } = useCarousel();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
:class="
|
||||
cn(
|
||||
'min-w-0 shrink-0 grow-0 basis-full',
|
||||
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
import { ArrowRight } from "lucide-vue-next";
|
||||
import { useCarousel } from "./useCarousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { orientation, canScrollNext, scrollNext } = useCarousel();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:disabled="!canScrollNext"
|
||||
:class="
|
||||
cn(
|
||||
'touch-manipulation absolute h-8 w-8 rounded-full p-0',
|
||||
orientation === 'horizontal'
|
||||
? '-right-12 top-1/2 -translate-y-1/2'
|
||||
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
variant="outline"
|
||||
@click="scrollNext"
|
||||
>
|
||||
<slot>
|
||||
<ArrowRight class="h-4 w-4 text-current" />
|
||||
<span class="sr-only">Next Slide</span>
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
import { ArrowLeft } from "lucide-vue-next";
|
||||
import { useCarousel } from "./useCarousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { orientation, canScrollPrev, scrollPrev } = useCarousel();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:disabled="!canScrollPrev"
|
||||
:class="
|
||||
cn(
|
||||
'touch-manipulation absolute h-8 w-8 rounded-full p-0',
|
||||
orientation === 'horizontal'
|
||||
? '-left-12 top-1/2 -translate-y-1/2'
|
||||
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
variant="outline"
|
||||
@click="scrollPrev"
|
||||
>
|
||||
<slot>
|
||||
<ArrowLeft class="h-4 w-4 text-current" />
|
||||
<span class="sr-only">Previous Slide</span>
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
||||
6
summer2024-frontend/src/components/ui/carousel/index.js
Normal file
6
summer2024-frontend/src/components/ui/carousel/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { default as Carousel } from "./Carousel.vue";
|
||||
export { default as CarouselContent } from "./CarouselContent.vue";
|
||||
export { default as CarouselItem } from "./CarouselItem.vue";
|
||||
export { default as CarouselPrevious } from "./CarouselPrevious.vue";
|
||||
export { default as CarouselNext } from "./CarouselNext.vue";
|
||||
export { useCarousel } from "./useCarousel";
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { createInjectionState } from "@vueuse/core";
|
||||
import emblaCarouselVue from "embla-carousel-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const [useProvideCarousel, useInjectCarousel] = createInjectionState(
|
||||
({ opts, orientation, plugins }, emits) => {
|
||||
const [emblaNode, emblaApi] = emblaCarouselVue(
|
||||
{
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins,
|
||||
);
|
||||
|
||||
function scrollPrev() {
|
||||
emblaApi.value?.scrollPrev();
|
||||
}
|
||||
function scrollNext() {
|
||||
emblaApi.value?.scrollNext();
|
||||
}
|
||||
|
||||
const canScrollNext = ref(false);
|
||||
const canScrollPrev = ref(false);
|
||||
|
||||
function onSelect(api) {
|
||||
canScrollNext.value = api?.canScrollNext() || false;
|
||||
canScrollPrev.value = api?.canScrollPrev() || false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!emblaApi.value) return;
|
||||
|
||||
emblaApi.value?.on("init", onSelect);
|
||||
emblaApi.value?.on("reInit", onSelect);
|
||||
emblaApi.value?.on("select", onSelect);
|
||||
|
||||
emits("init-api", emblaApi.value);
|
||||
});
|
||||
|
||||
return {
|
||||
carouselRef: emblaNode,
|
||||
carouselApi: emblaApi,
|
||||
canScrollPrev,
|
||||
canScrollNext,
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
orientation,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
function useCarousel() {
|
||||
const carouselState = useInjectCarousel();
|
||||
|
||||
if (!carouselState)
|
||||
throw new Error("useCarousel must be used within a <Carousel />");
|
||||
|
||||
return carouselState;
|
||||
}
|
||||
|
||||
export { useCarousel, useProvideCarousel };
|
||||
18
summer2024-frontend/src/components/ui/form/FormControl.vue
Normal file
18
summer2024-frontend/src/components/ui/form/FormControl.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script setup>
|
||||
import { Slot } from "radix-vue";
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot
|
||||
:id="formItemId"
|
||||
:aria-describedby="
|
||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||
"
|
||||
:aria-invalid="!!error"
|
||||
>
|
||||
<slot />
|
||||
</Slot>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
import { useFormField } from "./useFormField";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { formDescriptionId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p
|
||||
:id="formDescriptionId"
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
19
summer2024-frontend/src/components/ui/form/FormItem.vue
Normal file
19
summer2024-frontend/src/components/ui/form/FormItem.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
import { provide } from "vue";
|
||||
import { useId } from "radix-vue";
|
||||
import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const id = useId();
|
||||
provide(FORM_ITEM_INJECTION_KEY, id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('space-y-2', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
23
summer2024-frontend/src/components/ui/form/FormLabel.vue
Normal file
23
summer2024-frontend/src/components/ui/form/FormLabel.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
import { useFormField } from "./useFormField";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const props = defineProps({
|
||||
for: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { error, formItemId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
:class="cn(error && 'text-destructive', props.class)"
|
||||
:for="formItemId"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
16
summer2024-frontend/src/components/ui/form/FormMessage.vue
Normal file
16
summer2024-frontend/src/components/ui/form/FormMessage.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script setup>
|
||||
import { ErrorMessage } from "vee-validate";
|
||||
import { toValue } from "vue";
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const { name, formMessageId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorMessage
|
||||
:id="formMessageId"
|
||||
as="p"
|
||||
:name="toValue(name)"
|
||||
class="text-sm font-medium text-destructive"
|
||||
/>
|
||||
</template>
|
||||
7
summer2024-frontend/src/components/ui/form/index.js
Normal file
7
summer2024-frontend/src/components/ui/form/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export { Form, Field as FormField } from "vee-validate";
|
||||
export { default as FormItem } from "./FormItem.vue";
|
||||
export { default as FormLabel } from "./FormLabel.vue";
|
||||
export { default as FormControl } from "./FormControl.vue";
|
||||
export { default as FormMessage } from "./FormMessage.vue";
|
||||
export { default as FormDescription } from "./FormDescription.vue";
|
||||
export { FORM_ITEM_INJECTION_KEY } from "./injectionKeys";
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const FORM_ITEM_INJECTION_KEY = Symbol();
|
||||
36
summer2024-frontend/src/components/ui/form/useFormField.js
Normal file
36
summer2024-frontend/src/components/ui/form/useFormField.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
FieldContextKey,
|
||||
useFieldError,
|
||||
useIsFieldDirty,
|
||||
useIsFieldTouched,
|
||||
useIsFieldValid,
|
||||
} from "vee-validate";
|
||||
import { inject } from "vue";
|
||||
import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys";
|
||||
|
||||
export function useFormField() {
|
||||
const fieldContext = inject(FieldContextKey);
|
||||
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY);
|
||||
|
||||
if (!fieldContext)
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
|
||||
const { name } = fieldContext;
|
||||
const id = fieldItemContext;
|
||||
|
||||
const fieldState = {
|
||||
valid: useIsFieldValid(name),
|
||||
isDirty: useIsFieldDirty(name),
|
||||
isTouched: useIsFieldTouched(name),
|
||||
error: useFieldError(name),
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
}
|
||||
29
summer2024-frontend/src/components/ui/input/Input.vue
Normal file
29
summer2024-frontend/src/components/ui/input/Input.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
1
summer2024-frontend/src/components/ui/input/index.js
Normal file
1
summer2024-frontend/src/components/ui/input/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as Input } from "./Input.vue";
|
||||
32
summer2024-frontend/src/components/ui/label/Label.vue
Normal file
32
summer2024-frontend/src/components/ui/label/Label.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { Label } from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
for: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
1
summer2024-frontend/src/components/ui/label/index.js
Normal file
1
summer2024-frontend/src/components/ui/label/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as Label } from "./Label.vue";
|
||||
15
summer2024-frontend/src/components/ui/table/Table.vue
Normal file
15
summer2024-frontend/src/components/ui/table/Table.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full overflow-auto">
|
||||
<table :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/table/TableBody.vue
Normal file
13
summer2024-frontend/src/components/ui/table/TableBody.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
|
||||
<slot />
|
||||
</tbody>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/table/TableCaption.vue
Normal file
13
summer2024-frontend/src/components/ui/table/TableCaption.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</caption>
|
||||
</template>
|
||||
15
summer2024-frontend/src/components/ui/table/TableCell.vue
Normal file
15
summer2024-frontend/src/components/ui/table/TableCell.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td
|
||||
:class="cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
34
summer2024-frontend/src/components/ui/table/TableEmpty.vue
Normal file
34
summer2024-frontend/src/components/ui/table/TableEmpty.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import TableRow from "./TableRow.vue";
|
||||
import TableCell from "./TableCell.vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
colspan: { type: Number, required: false, default: 1 },
|
||||
});
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
:class="
|
||||
cn(
|
||||
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
|
||||
props.class
|
||||
)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<div class="flex items-center justify-center py-10">
|
||||
<slot />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
17
summer2024-frontend/src/components/ui/table/TableFooter.vue
Normal file
17
summer2024-frontend/src/components/ui/table/TableFooter.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tfoot
|
||||
:class="
|
||||
cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</tfoot>
|
||||
</template>
|
||||
20
summer2024-frontend/src/components/ui/table/TableHead.vue
Normal file
20
summer2024-frontend/src/components/ui/table/TableHead.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<th
|
||||
:class="
|
||||
cn(
|
||||
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</th>
|
||||
</template>
|
||||
13
summer2024-frontend/src/components/ui/table/TableHeader.vue
Normal file
13
summer2024-frontend/src/components/ui/table/TableHeader.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<thead :class="cn('[&_tr]:border-b', props.class)">
|
||||
<slot />
|
||||
</thead>
|
||||
</template>
|
||||
20
summer2024-frontend/src/components/ui/table/TableRow.vue
Normal file
20
summer2024-frontend/src/components/ui/table/TableRow.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
:class="
|
||||
cn(
|
||||
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
||||
9
summer2024-frontend/src/components/ui/table/index.js
Normal file
9
summer2024-frontend/src/components/ui/table/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export { default as Table } from "./Table.vue";
|
||||
export { default as TableBody } from "./TableBody.vue";
|
||||
export { default as TableCell } from "./TableCell.vue";
|
||||
export { default as TableHead } from "./TableHead.vue";
|
||||
export { default as TableHeader } from "./TableHeader.vue";
|
||||
export { default as TableFooter } from "./TableFooter.vue";
|
||||
export { default as TableRow } from "./TableRow.vue";
|
||||
export { default as TableCaption } from "./TableCaption.vue";
|
||||
export { default as TableEmpty } from "./TableEmpty.vue";
|
||||
29
summer2024-frontend/src/components/ui/textarea/Textarea.vue
Normal file
29
summer2024-frontend/src/components/ui/textarea/Textarea.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea
|
||||
v-model="modelValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
1
summer2024-frontend/src/components/ui/textarea/index.js
Normal file
1
summer2024-frontend/src/components/ui/textarea/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as Textarea } from "./Textarea.vue";
|
||||
Loading…
Add table
Add a link
Reference in a new issue