feat: add attachment components including Attachment, AttachmentAction, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, and AttachmentTrigger with associated variants
This commit is contained in:
parent
1e0b0c28f9
commit
d689fbe6b1
31
app/components/ui/attachment/Attachment.vue
Normal file
31
app/components/ui/attachment/Attachment.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { AttachmentVariants } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { attachmentVariants } from "."
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: AttachmentVariants["size"]
|
||||||
|
orientation?: AttachmentVariants["orientation"]
|
||||||
|
state?: "idle" | "uploading" | "processing" | "error" | "done"
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
state: "done",
|
||||||
|
size: "default",
|
||||||
|
orientation: "horizontal",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="attachment"
|
||||||
|
:data-state="state"
|
||||||
|
:data-size="size"
|
||||||
|
:data-orientation="orientation"
|
||||||
|
:class="cn(attachmentVariants({ size, orientation }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
27
app/components/ui/attachment/AttachmentAction.vue
Normal file
27
app/components/ui/attachment/AttachmentAction.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { ButtonVariants } from "../button"
|
||||||
|
import Button from "../button/Button.vue"
|
||||||
|
|
||||||
|
interface Props extends PrimitiveProps {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
variant?: ButtonVariants["variant"]
|
||||||
|
size?: ButtonVariants["size"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
variant: "ghost",
|
||||||
|
size: "icon-xs",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
data-slot="attachment-action"
|
||||||
|
:variant="variant"
|
||||||
|
:size="size"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
20
app/components/ui/attachment/AttachmentActions.vue
Normal file
20
app/components/ui/attachment/AttachmentActions.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="attachment-actions"
|
||||||
|
:class="cn(
|
||||||
|
'group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 relative z-20 group-data-[orientation=vertical]/attachment:gap-1 flex shrink-0 items-center',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
app/components/ui/attachment/AttachmentContent.vue
Normal file
21
app/components/ui/attachment/AttachmentContent.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="attachment-content"
|
||||||
|
:class="cn(
|
||||||
|
'leading-tight group-data-[orientation=vertical]/attachment:px-1 max-w-full min-w-0 flex-1',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
app/components/ui/attachment/AttachmentDescription.vue
Normal file
21
app/components/ui/attachment/AttachmentDescription.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
data-slot="attachment-description"
|
||||||
|
:class="cn(
|
||||||
|
'mt-0.5 text-xs block min-w-0 truncate text-muted-foreground group-data-[state=error]/attachment:text-destructive/80',
|
||||||
|
'max-w-full',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
20
app/components/ui/attachment/AttachmentGroup.vue
Normal file
20
app/components/ui/attachment/AttachmentGroup.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="attachment-group"
|
||||||
|
:class="cn(
|
||||||
|
'gap-3 scroll-px-1 py-1 flex min-w-0 scroll-fade-x snap-x snap-mandatory scrollbar-none overflow-x-auto overscroll-x-contain *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
app/components/ui/attachment/AttachmentMedia.vue
Normal file
21
app/components/ui/attachment/AttachmentMedia.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { AttachmentMediaVariants } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { attachmentMediaVariants } from "."
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
variant?: AttachmentMediaVariants["variant"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="attachment-media"
|
||||||
|
:data-variant="variant"
|
||||||
|
:class="cn(attachmentMediaVariants({ variant }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
20
app/components/ui/attachment/AttachmentTitle.vue
Normal file
20
app/components/ui/attachment/AttachmentTitle.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
data-slot="attachment-title"
|
||||||
|
:class="cn(
|
||||||
|
'font-medium block max-w-full min-w-0 truncate group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
23
app/components/ui/attachment/AttachmentTrigger.vue
Normal file
23
app/components/ui/attachment/AttachmentTrigger.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
interface Props extends PrimitiveProps {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
as: "button",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Primitive
|
||||||
|
data-slot="attachment-trigger"
|
||||||
|
:as="as"
|
||||||
|
:as-child="asChild"
|
||||||
|
:class="cn('absolute inset-0 z-10 outline-none', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
|
</template>
|
||||||
49
app/components/ui/attachment/index.ts
Normal file
49
app/components/ui/attachment/index.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as Attachment } from "./Attachment.vue"
|
||||||
|
export { default as AttachmentAction } from "./AttachmentAction.vue"
|
||||||
|
export { default as AttachmentActions } from "./AttachmentActions.vue"
|
||||||
|
export { default as AttachmentContent } from "./AttachmentContent.vue"
|
||||||
|
export { default as AttachmentDescription } from "./AttachmentDescription.vue"
|
||||||
|
export { default as AttachmentGroup } from "./AttachmentGroup.vue"
|
||||||
|
export { default as AttachmentMedia } from "./AttachmentMedia.vue"
|
||||||
|
export { default as AttachmentTitle } from "./AttachmentTitle.vue"
|
||||||
|
export { default as AttachmentTrigger } from "./AttachmentTrigger.vue"
|
||||||
|
|
||||||
|
export const attachmentVariants = cva(
|
||||||
|
"rounded-xl w-fit focus-within:ring-1 focus-within:ring-ring/50 group/attachment relative flex max-w-full min-w-0 shrink-0 flex-wrap border bg-card text-card-foreground transition-colors has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
default: "gap-2 has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2 text-sm",
|
||||||
|
sm: "gap-2.5 has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5 text-xs",
|
||||||
|
xs: "gap-1.5 has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1 text-xs rounded-lg",
|
||||||
|
},
|
||||||
|
orientation: {
|
||||||
|
horizontal: "min-w-40 items-center",
|
||||||
|
vertical: "w-24 has-data-[slot=attachment-content]:w-30 flex-col",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AttachmentVariants = VariantProps<typeof attachmentVariants>
|
||||||
|
|
||||||
|
export const attachmentMediaVariants = cva(
|
||||||
|
"bg-muted text-foreground w-10 rounded-lg group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md [&_svg:not([class*='size-'])]:size-4 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5 group-data-[orientation=vertical]/attachment:w-full group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! relative flex aspect-square shrink-0 items-center justify-center overflow-hidden group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive [&_svg]:pointer-events-none",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
icon: "",
|
||||||
|
image:
|
||||||
|
"opacity-60 group-data-[state=idle]/attachment:opacity-100 group-data-[state=done]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "icon",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AttachmentMediaVariants = VariantProps<typeof attachmentMediaVariants>
|
||||||
Loading…
Reference in New Issue
Block a user