1<template>
2 <Section :label="__('Feedback')" v-model:opened="opened">
3 <!-- Comment reads as a quote, the preset splits into pills on commas -->
4 <div class="flex flex-col items-start gap-3 pb-4 pt-0.5">
5 <StarRating :rating="rating" :aria-label="`${rating * 5} out of 5`" />
6 <div v-if="comment">
7 <p
8 ref="commentRef"
9 class="relative whitespace-pre-line pl-[0.4em] text-p-base text-ink-gray-7"
10 :class="!showFullComment && 'line-clamp-3'"
11 >
12 <span class="absolute left-0 text-ink-gray-4">“</span
13 >{{ comment }}<span class="text-ink-gray-4">”</span>
14 </p>
15 <button
16 v-if="isCommentClamped || showFullComment"
17 class="mt-0.5 pl-[0.4em] text-base text-ink-gray-5 hover:text-ink-gray-7"
18 @click="showFullComment = !showFullComment"
19 >
20 {{ showFullComment ? __("Show less") : __("Show more") }}
21 </button>
22 </div>
23 <div v-if="presetChips.length" class="flex flex-wrap gap-1.5">
24 <span
25 v-for="chip in presetChips"
26 :key="chip"
27 class="rounded-full border px-2.5 py-1 text-base leading-4 text-ink-gray-7"
28 >
29 {{ chip }}
30 </span>
31 </div>
32 </div>
33 </Section>
34</template>
35
36<script setup lang="ts">
37import { __ } from "@/translation";
38import { useResizeObserver } from "@vueuse/core";
39import { computed, ref } from "vue";
40import Section from "../Section.vue";
41import StarRating from "../StarRating.vue";
42
43const props = defineProps<{
44 rating: number;
45 feedback?: string;
46 comment?: string;
47}>();
48
49const opened = defineModel<boolean>("opened", { default: true });
50
51// the preset reads as tags ("Adequate help, bit slow"), so split it on commas
52const presetChips = computed(() =>
53 (props.feedback ?? "")
54 .split(",")
55 .map((part: string) => part.trim())
56 .filter(Boolean)
57 .map((part: string) => part.charAt(0).toUpperCase() + part.slice(1))
58);
59
60const commentRef = ref<HTMLElement | null>(null);
61const showFullComment = ref(false);
62const isCommentClamped = ref(false);
63
64// The sidebar is resizable, so how many lines the comment takes changes with
65// the column width, not just with the text.
66useResizeObserver(commentRef, () => {
67 const element = commentRef.value;
68 if (!element || showFullComment.value) return;
69 isCommentClamped.value = element.scrollHeight > element.clientHeight + 1;
70});
71</script>
72