Skip to content

Commit

Permalink
chore: update components
Browse files Browse the repository at this point in the history
  • Loading branch information
BayBreezy committed Dec 29, 2024
1 parent 88ed23c commit 57df50d
Show file tree
Hide file tree
Showing 21 changed files with 621 additions and 70 deletions.
92 changes: 77 additions & 15 deletions app/components/Ui/Alert/Alert.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
<template>
<div v-if="shown" :class="styles({ variant: variant, class: props.class })">
<div v-if="shown" :class="styles().base({ variant, filled, class: props.class })">
<slot :props="props" name="icon">
<Icon v-if="icon" :name="icon" class="h-4 w-4" />
<Icon
v-if="icon"
:name="icon"
:class="styles().icon({ variant, filled, class: props.iconClass })"
/>
</slot>
<div>
<div :class="styles().content({ variant, filled })">
<slot :props="props">
<UiAlertTitle v-if="title" :title="title" />
<UiAlertDescription v-if="description" :description="description" />
<slot name="title">
<UiAlertTitle v-if="title" :title="title" />
</slot>
<slot name="description">
<UiAlertDescription v-if="description" :description="description" />
</slot>
</slot>
</div>
</div>
Expand All @@ -17,6 +25,10 @@
defineProps<{
/** Custom class to add to the `Alert` parent */
class?: any;
/** Classes to add to the icon */
iconClass?: any;
/** Whether the alert should have a filled/colored background */
filled?: boolean;
/**
* Whether or not the `Alert` is shown.
* @default true
Expand All @@ -34,27 +46,77 @@
{
modelValue: true,
variant: "default",
title: undefined,
description: undefined,
icon: undefined,
class: undefined,
}
);
const emit = defineEmits(["update:modelValue"]);
const shown = useVModel(props, "modelValue", emit, { defaultValue: true });
const shown = defineModel<boolean>({ default: true });
const styles = tv({
base: "relative flex w-full gap-3 rounded-lg border p-4",
slots: {
base: "relative flex w-full gap-3 rounded-lg border p-4",
icon: "size-4 shrink-0",
content: "grow",
},
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
default: {
base: "bg-background text-foreground",
icon: "text-foreground",
},
destructive: {
base: "border-destructive/50 text-destructive dark:border-destructive",
icon: "text-destructive",
},
info: {
base: "border-blue-500/50 text-blue-600",
icon: "text-blue-600",
},
success: {
base: "border-emerald-500/50 text-emerald-600",
icon: "text-emerald-500",
},
warning: {
base: "border-amber-500/50 text-amber-600",
icon: "text-amber-600",
},
},
filled: {
true: {},
},
},
defaultVariants: {
variant: "default",
filled: false,
},
compoundVariants: [
{
filled: true,
variant: "default",
class: { base: "bg-muted/50 text-foreground", icon: "text-foreground" },
},
{
filled: true,
variant: "destructive",
class: {
base: "bg-destructive text-destructive-foreground",
icon: "text-destructive-foreground",
},
},
{
filled: true,
variant: "info",
class: { base: "bg-blue-500 text-blue-50", icon: "text-blue-50" },
},
{
filled: true,
variant: "success",
class: { base: "bg-emerald-500 text-emerald-50", icon: "text-emerald-50" },
},
{
filled: true,
variant: "warning",
class: { base: "bg-amber-500 text-amber-50", icon: "text-amber-50" },
},
],
});
</script>
64 changes: 36 additions & 28 deletions app/components/Ui/Breadcrumbs.vue
Original file line number Diff line number Diff line change
@@ -1,44 +1,52 @@
<template>
<div :class="styles({ class: props.class })">
<template v-for="(item, i) in items" :key="i">
<div class="flex items-center gap-3">
<div class="flex cursor-pointer items-center gap-2">
<slot name="crumbIcon" :item="item" :index="i">
<Icon
v-if="item.icon"
:name="item.icon"
:class="[!isNotLastItem(i) && 'text-primary']"
/>
</slot>
<slot :item="item" :is-not-last-item="isNotLastItem" :index="i" name="link">
<NuxtLink
v-if="item.label"
:to="!item?.disabled ? item.link : ''"
:class="[
isNotLastItem(i)
? 'text-muted-foreground hover:underline'
: 'font-semibold text-primary',
]"
class="text-sm text-foreground"
@click="item?.click?.()"
>{{ item.label }}</NuxtLink
>
</slot>
<slot :name="item.slot || 'default'">
<div class="flex items-center gap-3">
<div class="group flex items-center gap-2">
<slot name="crumbIcon" :item="item" :index="i">
<Icon
v-if="item.icon"
:name="item.icon"
:class="[
isNotLastItem(i)
? 'text-muted-foreground group-hover:text-foreground'
: 'text-primary',
]"
/>
</slot>
<slot :item="item" :is-not-last-item="isNotLastItem" :index="i" name="link">
<NuxtLink
v-if="item.label"
:to="!item?.disabled ? item.link : ''"
:class="[
item.link && !item.disabled && 'underline-offset-2 group-hover:underline',
isNotLastItem(i)
? 'text-muted-foreground group-hover:text-foreground'
: 'font-semibold text-primary',
]"
class="text-sm text-foreground transition-colors"
@click="item?.click?.()"
>{{ item.label }}</NuxtLink
>
</slot>
</div>
</div>
<slot name="separator" :item="item" :index="i">
<Icon v-if="isNotLastItem(i)" :name="separator" class="h-3 w-3 text-muted-foreground" />
</slot>
</div>
</slot>
<slot name="separator" :item="item" :index="i">
<Icon v-if="isNotLastItem(i)" :name="separator" class="h-3 w-3 text-muted-foreground" />
</slot>
</template>
</div>
</template>

<script setup lang="ts">
export interface Crumbs {
label: string;
label?: string;
icon?: string;
link?: string;
disabled?: boolean;
slot?: string;
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
click?: Function;
}
Expand Down
13 changes: 11 additions & 2 deletions app/components/Ui/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
<Icon :name="icon" class="size-5" />
</div>
</slot>
<slot name="loading">
<Icon v-if="loading" class="size-4 shrink-0" :name="loadingIcon" />
</slot>
<slot>
<span v-if="text">{{ text }}</span>
</slot>
Expand All @@ -44,7 +47,7 @@
const props = withDefaults(
defineProps<
NuxtLinkProps & {
/** The type fro the button */
/** The type for the button */
type?: "button" | "submit" | "reset";
/** Whether the button is disabled */
disabled?: boolean;
Expand All @@ -66,10 +69,15 @@
iconPlacement?: "left" | "right";
/** The icon to display in the button */
icon?: string;
/** The icon to display when the button is loading */
loadingIcon?: string;
}
>(),
{
type: "button",
loadingIcon: "line-md:loading-loop",
iconPlacement: "left",
loading: false,
}
);
Expand All @@ -90,7 +98,8 @@
"variant",
"as",
"loading",
"disabled"
"disabled",
"loadingIcon"
)
);
</script>
67 changes: 67 additions & 0 deletions app/components/Ui/Carousel/Carousel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<div
:class="styles({ class: props.class })"
role="region"
aria-roledescription="carousel"
tabindex="0"
@keydown="onKeyDown"
>
<slot
:can-scroll-next
:can-scroll-prev
:carousel-api
:carousel-ref
:orientation
:scroll-next
:scroll-prev
/>
</div>
</template>

<script setup lang="ts">
import type { CarouselEmits, CarouselProps, WithClassAsProps } from "~/composables/useCarousel";
const styles = tv({
base: "relative focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
});
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
orientation: "horizontal",
});
const emits = defineEmits<CarouselEmits>();
const {
canScrollNext,
canScrollPrev,
carouselApi,
carouselRef,
orientation,
scrollNext,
scrollPrev,
} = useProvideCarousel(props, emits);
defineExpose({
canScrollNext,
canScrollPrev,
carouselApi,
carouselRef,
orientation: props.orientation,
scrollNext,
scrollPrev,
});
function onKeyDown(event: KeyboardEvent) {
const prevKey = props.orientation === "vertical" ? "ArrowUp" : "ArrowLeft";
const nextKey = props.orientation === "vertical" ? "ArrowDown" : "ArrowRight";
if (event.key === prevKey) {
event.preventDefault();
scrollPrev();
return;
}
if (event.key === nextKey) {
event.preventDefault();
scrollNext();
}
}
</script>
28 changes: 28 additions & 0 deletions app/components/Ui/Carousel/Content.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<div ref="carouselRef" :class="styles().base({ orientation })">
<div :class="styles().content({ orientation, class: props.class })" v-bind="$attrs">
<slot />
</div>
</div>
</template>

<script setup lang="ts">
import type { WithClassAsProps } from "~/composables/useCarousel";
defineOptions({ inheritAttrs: false });
const props = defineProps<WithClassAsProps>();
const { carouselRef, orientation } = useCarousel();
const styles = tv({
slots: {
base: "overflow-hidden",
content: "flex",
},
variants: {
orientation: {
horizontal: { content: "-ml-4" },
vertical: { content: "-mt-4 flex-col" },
},
},
});
</script>
45 changes: 45 additions & 0 deletions app/components/Ui/Carousel/Item.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<div
role="group"
aria-roledescription="slide"
:class="styles({ orientation, class: `${props.class} ${grabbingClass}` })"
@mousedown="isGrabbing = true"
@mouseup="isGrabbing = false"
@mouseleave="isGrabbing = false"
>
<slot />
</div>
</template>

<script setup lang="ts">
import type { WithClassAsProps } from "~/composables/useCarousel";
const props = defineProps<
WithClassAsProps & {
/**
* Whether to show the grab cursor when hovering over the item.
* @default false
*/
grabCursor?: boolean;
}
>();
const { orientation } = useCarousel();
const styles = tv({
base: "min-w-0 shrink-0 grow-0 basis-full",
variants: {
orientation: {
horizontal: "pl-4",
vertical: "pt-4",
},
},
});
const isGrabbing = ref(false);
const grabbingClass = computed(() => {
if (!props.grabCursor) return "";
return isGrabbing.value ? "cursor-grabbing" : "cursor-grab";
});
</script>
Loading

0 comments on commit 57df50d

Please sign in to comment.