mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
fix: 🐛 Fix broken Note UIs
This commit is contained in:
parent
b6080eff60
commit
ac0a571ecc
|
|
@ -67,7 +67,6 @@
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<component
|
<component
|
||||||
:is="visibilities[state.visibility].icon"
|
:is="visibilities[state.visibility].icon"
|
||||||
class="!size-5"
|
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
|
||||||
15
components/notes/action-button.vue
Normal file
15
components/notes/action-button.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<Button variant="ghost" class="max-w-14 w-full">
|
||||||
|
<component :is="icon" class="size-4.5" />
|
||||||
|
<slot />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FunctionalComponent } from "vue";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
const { icon } = defineProps<{
|
||||||
|
icon: FunctionalComponent;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
@ -1,24 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row w-full gap-x-6 items-stretch justify-start text-sm *:max-w-14 *:w-full *:text-muted-foreground">
|
<div class="flex flex-row w-full gap-x-4 items-stretch justify-start">
|
||||||
<Button variant="ghost" @click="emit('reply')" :title="m.drab_tense_turtle_comfort()" :disabled="!identity">
|
<ActionButton :icon="Reply" @click="emit('reply')" :title="m.drab_tense_turtle_comfort()" :disabled="!identity">
|
||||||
<Reply class="size-5 text-primary" />
|
|
||||||
{{ numberFormat(replyCount) }}
|
{{ numberFormat(replyCount) }}
|
||||||
</Button>
|
</ActionButton>
|
||||||
<Button variant="ghost" @click="liked ? unlike() : like()" :title="liked ? m.vexed_fluffy_clownfish_dance() : m.royal_close_samuel_scold()" :disabled="!identity" :class="liked && '*:fill-red-600 *:text-red-600'">
|
<ActionButton :icon="Heart" @click="liked ? unlike() : like()" :title="liked ? m.vexed_fluffy_clownfish_dance() : m.royal_close_samuel_scold()" :disabled="!identity" :class="liked && '*:fill-red-600 *:text-red-600'">
|
||||||
<Heart class="size-5 text-primary" />
|
|
||||||
{{ numberFormat(likeCount) }}
|
{{ numberFormat(likeCount) }}
|
||||||
</Button>
|
</ActionButton>
|
||||||
<Button variant="ghost" @click="reblogged ? unreblog() : reblog()" :title="reblogged ? m.lime_neat_ox_stab() : m.aware_helpful_marlin_drop()" :disabled="!identity" :class="reblogged && '*:text-green-600'">
|
<ActionButton :icon="Repeat" @click="reblogged ? unreblog() : reblog()" :title="reblogged ? m.lime_neat_ox_stab() : m.aware_helpful_marlin_drop()" :disabled="!identity" :class="reblogged && '*:text-green-600'">
|
||||||
<Repeat class="size-5 text-primary" />
|
|
||||||
{{ numberFormat(reblogCount) }}
|
{{ numberFormat(reblogCount) }}
|
||||||
</Button>
|
</ActionButton>
|
||||||
<Button variant="ghost" @click="emit('quote')" :title="m.true_shy_jackal_drip()" :disabled="!identity">
|
<ActionButton :icon="Quote" @click="emit('quote')" :title="m.true_shy_jackal_drip()" :disabled="!identity" />
|
||||||
<Quote class="size-5 text-primary" />
|
|
||||||
</Button>
|
|
||||||
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')" :note-id="noteId" @delete="emit('delete')">
|
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')" :note-id="noteId" @delete="emit('delete')">
|
||||||
<Button variant="ghost" :title="m.busy_merry_cowfish_absorb()">
|
<ActionButton :icon="Ellipsis" :title="m.busy_merry_cowfish_absorb()" />
|
||||||
<Ellipsis class="size-5 text-primary" />
|
|
||||||
</Button>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -26,11 +19,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next";
|
import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next";
|
||||||
import { toast } from "vue-sonner";
|
import { toast } from "vue-sonner";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import * as m from "~/paraglide/messages.js";
|
import * as m from "~/paraglide/messages.js";
|
||||||
import { getLocale } from "~/paraglide/runtime";
|
import { getLocale } from "~/paraglide/runtime";
|
||||||
import { SettingIds } from "~/settings";
|
import { SettingIds } from "~/settings";
|
||||||
import { confirmModalService } from "../modals/composable";
|
import { confirmModalService } from "../modals/composable";
|
||||||
|
import ActionButton from "./action-button.vue";
|
||||||
import Menu from "./menu.vue";
|
import Menu from "./menu.vue";
|
||||||
|
|
||||||
const { noteId } = defineProps<{
|
const { noteId } = defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- [&:has(>:last-child:nth-child(1))] means "when this element has 1 child" -->
|
<!-- [&:has(>:last-child:nth-child(1))] means "when this element has 1 child" -->
|
||||||
<div class="mt-4 grid gap-4 grid-cols-2 *:max-h-56 [&:has(>:last-child:nth-child(1))]:grid-cols-1 sm:[&:has(>:last-child:nth-child(1))>*]:max-h-72">
|
<div class="grid gap-4 grid-cols-2 *:max-h-56 [&:has(>:last-child:nth-child(1))]:grid-cols-1 sm:[&:has(>:last-child:nth-child(1))>*]:max-h-72">
|
||||||
<Attachment v-for="attachment in attachments" :key="attachment.id" :attachment="attachment" />
|
<Attachment v-for="attachment in attachments" :key="attachment.id" :attachment="attachment" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
26
components/notes/content-warning.vue
Normal file
26
components/notes/content-warning.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<Alert layout="button">
|
||||||
|
<TriangleAlert />
|
||||||
|
<AlertTitle class="sr-only">{{ m.livid_tangy_lionfish_clasp() }}</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
{{ contentWarning || m.sour_seemly_bird_hike() }}
|
||||||
|
</AlertDescription>
|
||||||
|
<Button @click="blurred = !blurred" variant="outline" size="sm">{{ blurred ? m.bald_direct_turtle_win() :
|
||||||
|
m.known_flaky_cockroach_dash() }}</Button>
|
||||||
|
</Alert>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { TriangleAlert } from "lucide-vue-next";
|
||||||
|
import * as m from "~/paraglide/messages.js";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
const { contentWarning } = defineProps<{
|
||||||
|
contentWarning?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const blurred = defineModel<boolean>({
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,34 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<Alert variant="warning" v-if="(sensitive || contentWarning) && showCw.value"
|
<ContentWarning v-if="(sensitive || contentWarning) && showCw.value" :content-warning="contentWarning" v-model="blurred" />
|
||||||
class="mb-4 py-2 px-4 grid grid-cols-[auto,1fr,auto] gap-2 items-center [&>svg~*]:pl-0 [&>svg+div]:translate-y-0 [&>svg]:static">
|
|
||||||
<AlertTitle class="sr-only">{{ m.livid_tangy_lionfish_clasp() }}</AlertTitle>
|
|
||||||
<div>
|
|
||||||
<TriangleAlert class="size-4" />
|
|
||||||
</div>
|
|
||||||
<AlertDescription>
|
|
||||||
{{ contentWarning || m.sour_seemly_bird_hike() }}
|
|
||||||
</AlertDescription>
|
|
||||||
<Button @click="blurred = !blurred" variant="outline" size="sm">{{ blurred ? m.bald_direct_turtle_win() : m.known_flaky_cockroach_dash() }}</Button>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div ref="container" :class="cn('overflow-y-hidden relative duration-200', (blurred && showCw.value) && 'blur-md')" :style="{
|
<OverflowGuard :character-count="characterCount" :class="(blurred && showCw.value) && 'blur-md'">
|
||||||
maxHeight: collapsed ? '18rem' : `${container?.scrollHeight}px`,
|
<Prose v-html="content" v-render-emojis="emojis"></Prose>
|
||||||
}">
|
</OverflowGuard>
|
||||||
<div :class="[
|
|
||||||
'prose prose-sm block relative dark:prose-invert duration-200 !max-w-full break-words prose-a:no-underline hover:prose-a:underline',
|
|
||||||
$style.content,
|
|
||||||
]" v-html="content" v-render-emojis="emojis"></div>
|
|
||||||
<div v-if="isOverflowing && collapsed"
|
|
||||||
class="absolute inset-x-0 bottom-0 h-36 bg-gradient-to-t from-black/5 to-transparent rounded-b"></div>
|
|
||||||
<Button v-if="isOverflowing" @click="collapsed = !collapsed"
|
|
||||||
class="absolute bottom-2 right-1/2 translate-x-1/2">{{
|
|
||||||
collapsed
|
|
||||||
? `${m.lazy_honest_mammoth_bump()}${plainContent ? ` • ${m.dark_spare_goldfish_charm({
|
|
||||||
count: formattedCharacterCount ?? '0',
|
|
||||||
})}` : "" }`
|
|
||||||
: m.that_misty_mule_arrive()
|
|
||||||
}}</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Attachments v-if="attachments.length > 0" :attachments="attachments" :class="(blurred && showCw.value) && 'blur-xl'" />
|
<Attachments v-if="attachments.length > 0" :attachments="attachments" :class="(blurred && showCw.value) && 'blur-xl'" />
|
||||||
|
|
||||||
|
|
@ -38,16 +13,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import type { Attachment, Emoji, Status } from "@versia/client/types";
|
import type { Attachment, Emoji, Status } from "@versia/client/types";
|
||||||
import { TriangleAlert } from "lucide-vue-next";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import * as m from "~/paraglide/messages.js";
|
|
||||||
import { getLocale } from "~/paraglide/runtime";
|
|
||||||
import { type BooleanSetting, SettingIds } from "~/settings";
|
import { type BooleanSetting, SettingIds } from "~/settings";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
|
||||||
import Attachments from "./attachments.vue";
|
import Attachments from "./attachments.vue";
|
||||||
|
import ContentWarning from "./content-warning.vue";
|
||||||
import Note from "./note.vue";
|
import Note from "./note.vue";
|
||||||
|
import OverflowGuard from "./overflow-guard.vue";
|
||||||
|
import Prose from "./prose.vue";
|
||||||
|
|
||||||
const { content, plainContent, sensitive, contentWarning } = defineProps<{
|
const { content, plainContent, sensitive, contentWarning } = defineProps<{
|
||||||
plainContent?: string;
|
plainContent?: string;
|
||||||
|
|
@ -58,30 +30,9 @@ const { content, plainContent, sensitive, contentWarning } = defineProps<{
|
||||||
sensitive: boolean;
|
sensitive: boolean;
|
||||||
contentWarning?: string;
|
contentWarning?: string;
|
||||||
}>();
|
}>();
|
||||||
const container = ref<HTMLDivElement | null>(null);
|
|
||||||
const collapsed = ref(true);
|
|
||||||
const blurred = ref(sensitive || !!contentWarning);
|
const blurred = ref(sensitive || !!contentWarning);
|
||||||
const showCw = useSetting(SettingIds.ShowContentWarning) as Ref<BooleanSetting>;
|
const showCw = useSetting(SettingIds.ShowContentWarning) as Ref<BooleanSetting>;
|
||||||
|
|
||||||
// max-h-72 is 18rem
|
|
||||||
const remToPx = (rem: number) =>
|
|
||||||
rem *
|
|
||||||
Number.parseFloat(
|
|
||||||
getComputedStyle(document.documentElement).fontSize || "16px",
|
|
||||||
);
|
|
||||||
const isOverflowing = computed(() => {
|
|
||||||
if (!container.value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return container.value.scrollHeight > remToPx(18);
|
|
||||||
});
|
|
||||||
|
|
||||||
const characterCount = plainContent?.length;
|
const characterCount = plainContent?.length;
|
||||||
const formattedCharacterCount = characterCount
|
|
||||||
? new Intl.NumberFormat(getLocale()).format(characterCount)
|
|
||||||
: undefined;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module>
|
|
||||||
@import url("~/styles/content.css");
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,13 @@
|
||||||
author.display_name
|
author.display_name
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="truncate text-sm tracking-tight">
|
<span class="truncate text-sm tracking-tight">
|
||||||
<CopyableText :text="author.acct">
|
<span>
|
||||||
<span
|
<span
|
||||||
class="font-semibold bg-gradient-to-tr from-pink-700 dark:from-indigo-400 via-purple-700 dark:via-purple-400 to-indigo-700 dark:to-indigo-400 text-transparent bg-clip-text">
|
class="font-semibold bg-gradient-to-tr from-pink-700 dark:from-indigo-400 via-purple-700 dark:via-purple-400 to-indigo-700 dark:to-indigo-400 text-transparent bg-clip-text">
|
||||||
@{{ username }}
|
@{{ username }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-muted-foreground">{{ instance && "@" }}{{ instance }}</span>
|
<span class="text-muted-foreground">{{ instance && "@" }}{{ instance }}</span>
|
||||||
</CopyableText>
|
</span>
|
||||||
·
|
·
|
||||||
<span class="text-muted-foreground ml-auto tracking-normal" :title="fullTime">{{ timeAgo }}</span>
|
<span class="text-muted-foreground ml-auto tracking-normal" :title="fullTime">{{ timeAgo }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Card as="article" class="relative gap-4 items-stretch">
|
<Card as="article" class="relative gap-4 items-stretch">
|
||||||
<CardHeader as="header">
|
<CardHeader as="header" class="space-y-2">
|
||||||
<ReblogHeader
|
<ReblogHeader
|
||||||
v-if="note.reblog"
|
v-if="note.reblog"
|
||||||
:avatar="note.account.avatar"
|
:avatar="note.account.avatar"
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<!-- Simply offset by the size of avatar + 0.75rem (the gap) -->
|
<!-- Simply offset by the size of avatar + 0.75rem (the gap) -->
|
||||||
<CardContent
|
<CardContent
|
||||||
:class="
|
:class="
|
||||||
contentUnderUsername && (smallLayout ? 'ml-11' : 'ml-[4.25rem]')
|
['space-y-4', contentUnderUsername && (smallLayout ? 'ml-11' : 'ml-[4.25rem]')]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<Content
|
<Content
|
||||||
|
|
|
||||||
49
components/notes/overflow-guard.vue
Normal file
49
components/notes/overflow-guard.vue
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div ref="container" class="overflow-y-hidden relative duration-200" :style="{
|
||||||
|
maxHeight: collapsed ? '18rem' : `${container?.scrollHeight}px`,
|
||||||
|
}">
|
||||||
|
<slot />
|
||||||
|
<div v-if="isOverflowing && collapsed"
|
||||||
|
class="absolute inset-x-0 bottom-0 h-36 bg-gradient-to-t from-black/5 to-transparent rounded-b"></div>
|
||||||
|
<Button v-if="isOverflowing" @click="collapsed = !collapsed"
|
||||||
|
class="absolute bottom-2 right-1/2 translate-x-1/2">{{
|
||||||
|
collapsed
|
||||||
|
? `${m.lazy_honest_mammoth_bump()}${formattedCharacterCount ? ` • ${m.dark_spare_goldfish_charm({
|
||||||
|
count: formattedCharacterCount,
|
||||||
|
})}` : ""}`
|
||||||
|
: m.that_misty_mule_arrive()
|
||||||
|
}}</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as m from "~/paraglide/messages.js";
|
||||||
|
import { getLocale } from "~/paraglide/runtime";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
const { characterCount = 0 } = defineProps<{
|
||||||
|
characterCount?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const container = useTemplateRef<HTMLDivElement>("container");
|
||||||
|
const collapsed = ref(true);
|
||||||
|
|
||||||
|
// max-h-72 is 18rem
|
||||||
|
const remToPx = (rem: number) =>
|
||||||
|
rem *
|
||||||
|
Number.parseFloat(
|
||||||
|
getComputedStyle(document.documentElement).fontSize || "16px",
|
||||||
|
);
|
||||||
|
|
||||||
|
const isOverflowing = computed(() => {
|
||||||
|
if (!container.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return container.value.scrollHeight > remToPx(18);
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedCharacterCount =
|
||||||
|
characterCount > 0
|
||||||
|
? new Intl.NumberFormat(getLocale()).format(characterCount)
|
||||||
|
: undefined;
|
||||||
|
</script>
|
||||||
12
components/notes/prose.vue
Normal file
12
components/notes/prose.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<div :class="[
|
||||||
|
'prose prose-sm block relative dark:prose-invert duration-200 !max-w-full break-words prose-a:no-underline hover:prose-a:underline',
|
||||||
|
$style.content,
|
||||||
|
]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
@import "~/styles/content.css";
|
||||||
|
</style>
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink :href="urlAsPath" class="rounded border hover:bg-muted duration-100 text-sm flex flex-row items-center gap-2 px-2 py-1 mb-4">
|
<NuxtLink :href="urlAsPath">
|
||||||
<Repeat class="size-4 text-primary" />
|
<Card class="flex-row px-2 py-1 items-center gap-2 hover:bg-muted duration-100 text-sm">
|
||||||
<Avatar class="size-6 border" :src="avatar" :name="displayName" />
|
<Repeat class="size-4 text-primary" />
|
||||||
<span class="font-semibold" v-render-emojis="emojis">{{ displayName }}</span>
|
<Avatar class="size-6 border" :src="avatar" :name="displayName" />
|
||||||
{{ m.large_vivid_horse_catch() }}
|
<span class="font-semibold" v-render-emojis="emojis">{{ displayName }}</span>
|
||||||
|
{{ m.large_vivid_horse_catch() }}
|
||||||
|
</Card>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -12,6 +14,7 @@ import type { Emoji } from "@versia/client/types";
|
||||||
import { Repeat } from "lucide-vue-next";
|
import { Repeat } from "lucide-vue-next";
|
||||||
import * as m from "~/paraglide/messages.js";
|
import * as m from "~/paraglide/messages.js";
|
||||||
import Avatar from "../profiles/avatar.vue";
|
import Avatar from "../profiles/avatar.vue";
|
||||||
|
import { Card } from "../ui/card";
|
||||||
|
|
||||||
const { url } = defineProps<{
|
const { url } = defineProps<{
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|
@ -21,4 +24,4 @@ const { url } = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const urlAsPath = new URL(url).pathname;
|
const urlAsPath = new URL(url).pathname;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Avatar :shape="(shape.value as 'circle' | 'square')" :size="size">
|
<Avatar :class="shape.value === 'square' && 'rounded-md'" :size="size">
|
||||||
<AvatarFallback v-if="name">
|
<AvatarFallback v-if="name">
|
||||||
{{ getInitials(name) }}
|
{{ getInitials(name) }}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ import { type AlertVariants, alertVariants } from ".";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes["class"];
|
class?: HTMLAttributes["class"];
|
||||||
variant?: AlertVariants["variant"];
|
variant?: AlertVariants["variant"];
|
||||||
|
layout?: AlertVariants["layout"];
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
data-slot="alert"
|
data-slot="alert"
|
||||||
:class="cn(alertVariants({ variant }), props.class)"
|
:class="cn(alertVariants({ variant, layout }), props.class)"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
:class="cn('text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', props.class)"
|
:class="cn('text-muted-foreground text-sm [&_p]:leading-relaxed', props.class)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
:class="cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
|
:class="cn('line-clamp-1 min-h-4 font-medium tracking-tight', props.class)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export { default as AlertDescription } from "./AlertDescription.vue";
|
||||||
export { default as AlertTitle } from "./AlertTitle.vue";
|
export { default as AlertTitle } from "./AlertTitle.vue";
|
||||||
|
|
||||||
export const alertVariants = cva(
|
export const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
"relative w-full rounded-lg border px-4 py-3 grid text-sm [&>svg]:size-4 [&>svg]:text-current",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
@ -13,9 +13,15 @@ export const alertVariants = cva(
|
||||||
destructive:
|
destructive:
|
||||||
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||||
},
|
},
|
||||||
|
layout: {
|
||||||
|
default:
|
||||||
|
"has-[>svg]:grid-cols-[1fr_auto] grid-rows-2 gap-x-3 gap-y-1 items-start",
|
||||||
|
button: "grid-cols-[auto_1fr_auto] items-center gap-x-3 gap-y-0.5",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
|
layout: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div data-slot="card" :class="cn(
|
<div data-slot="card" :class="cn(
|
||||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
'bg-card text-card-foreground flex flex-col gap-6 rounded-md border py-6 shadow-sm',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
">
|
">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ import { type HTMLAttributes, computed } from "vue";
|
||||||
import DialogOverlay from "./DialogOverlay.vue";
|
import DialogOverlay from "./DialogOverlay.vue";
|
||||||
|
|
||||||
const props = defineProps<
|
const props = defineProps<
|
||||||
DialogContentProps & { class?: HTMLAttributes["class"] }
|
DialogContentProps & {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
hideClose?: boolean;
|
||||||
|
}
|
||||||
>();
|
>();
|
||||||
const emits = defineEmits<DialogContentEmits>();
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
|
|
@ -41,6 +44,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<DialogClose
|
<DialogClose
|
||||||
|
v-if="!hideClose"
|
||||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
>
|
>
|
||||||
<X />
|
<X />
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||||
:data-inset="inset ? '' : undefined"
|
:data-inset="inset ? '' : undefined"
|
||||||
:data-variant="variant"
|
:data-variant="variant"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn(`focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`, props.class)"
|
:class="cn(`focus:bg-accent w-full focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`, props.class)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
:bottom-avatar-bar="true"
|
:bottom-avatar-bar="true"
|
||||||
:content-under-username="true"
|
:content-under-username="true"
|
||||||
/>
|
/>
|
||||||
<Note v-if="note" :note="note" :top-avatar-bar="true" />
|
<Note v-if="note" :note="note" :top-avatar-bar="(context?.ancestors.length ?? 0) > 0" />
|
||||||
</div>
|
</div>
|
||||||
<Note v-for="note of context?.descendants" :note="note" />
|
<Note v-for="note of context?.descendants" :note="note" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue