@tekkare/romulus 0.1.138 → 0.1.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmClampText - un texte long coupe a N lignes, avec « Lire plus ».
|
|
3
|
+
|
|
4
|
+
Les fiches produit portent des paragraphes de longueur imprevisible : une
|
|
5
|
+
indication tient parfois en deux lignes, parfois en quarante. Les laisser
|
|
6
|
+
couler pousse tout le reste de la carte hors de l'ecran ; les couper sans
|
|
7
|
+
rien dire fait perdre le texte.
|
|
8
|
+
|
|
9
|
+
Le lien n'apparait que si le texte deborde vraiment. La mesure se fait dans
|
|
10
|
+
l'etat coupe (`scrollHeight > clientHeight`) et se refait au
|
|
11
|
+
redimensionnement : une carte elargie peut faire tenir le texte, le lien
|
|
12
|
+
doit alors disparaitre. Une fois deplie, on ne remesure pas — l'element
|
|
13
|
+
n'est plus coupe, la comparaison rendrait toujours faux et le lien
|
|
14
|
+
« Reduire » s'effacerait sous le doigt.
|
|
15
|
+
-->
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<{
|
|
20
|
+
/** Le texte a afficher. Vide, le composant ne rend rien. */
|
|
21
|
+
text?: string | null
|
|
22
|
+
/** Nombre de lignes visibles avant la coupe. */
|
|
23
|
+
lines?: number
|
|
24
|
+
lang?: 'fr' | 'en'
|
|
25
|
+
}>(), {
|
|
26
|
+
text: '',
|
|
27
|
+
lines: 6,
|
|
28
|
+
lang: 'fr',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const WORDS = {
|
|
32
|
+
fr: { more: 'Lire plus', less: 'Réduire' },
|
|
33
|
+
en: { more: 'Read more', less: 'Show less' },
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
const words = computed(() => WORDS[props.lang])
|
|
37
|
+
|
|
38
|
+
const bodyRef = ref<HTMLElement>()
|
|
39
|
+
const expanded = ref(false)
|
|
40
|
+
const overflows = ref(false)
|
|
41
|
+
|
|
42
|
+
async function measure() {
|
|
43
|
+
if (expanded.value) return
|
|
44
|
+
await nextTick()
|
|
45
|
+
const el = bodyRef.value
|
|
46
|
+
if (!el) return
|
|
47
|
+
overflows.value = el.scrollHeight > el.clientHeight
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let observer: ResizeObserver | undefined
|
|
51
|
+
|
|
52
|
+
onMounted(() => {
|
|
53
|
+
measure()
|
|
54
|
+
if (typeof ResizeObserver !== 'undefined' && bodyRef.value) {
|
|
55
|
+
observer = new ResizeObserver(() => measure())
|
|
56
|
+
observer.observe(bodyRef.value)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
onBeforeUnmount(() => observer?.disconnect())
|
|
60
|
+
|
|
61
|
+
// Un changement de texte remet la coupe : le paragraphe suivant n'a aucune
|
|
62
|
+
// raison d'heriter du « deplie » demande sur le precedent.
|
|
63
|
+
watch(() => props.text, () => {
|
|
64
|
+
expanded.value = false
|
|
65
|
+
measure()
|
|
66
|
+
})
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<template>
|
|
70
|
+
<div v-if="text" class="rm-clamp">
|
|
71
|
+
<p
|
|
72
|
+
ref="bodyRef"
|
|
73
|
+
class="rm-clamp__body"
|
|
74
|
+
:class="{ 'rm-clamp__body--cut': !expanded }"
|
|
75
|
+
:style="{ '--rm-clamp-lines': lines }"
|
|
76
|
+
>
|
|
77
|
+
{{ text }}
|
|
78
|
+
</p>
|
|
79
|
+
<button
|
|
80
|
+
v-if="overflows"
|
|
81
|
+
type="button"
|
|
82
|
+
class="rm-clamp__toggle"
|
|
83
|
+
:aria-expanded="expanded"
|
|
84
|
+
@click="expanded = !expanded"
|
|
85
|
+
>
|
|
86
|
+
{{ expanded ? words.less : words.more }}
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<style scoped>
|
|
92
|
+
.rm-clamp{max-width:100%;min-width:0;overflow:hidden}.rm-clamp__body{color:var(--text-secondary);font-size:var(--font-14);line-height:1.6;overflow-wrap:break-word;word-break:break-word}.rm-clamp__body--cut{display:-webkit-box;-webkit-line-clamp:var(--rm-clamp-lines);line-clamp:var(--rm-clamp-lines);-webkit-box-orient:vertical;overflow:hidden}.rm-clamp__toggle{background:none;border:none;color:var(--color-healthcare);cursor:pointer;display:inline-block;font-family:inherit;font-size:var(--font-13);font-weight:var(--weight-semibold);margin-top:var(--space-4);padding:0}.rm-clamp__toggle:hover{text-decoration:underline}
|
|
93
|
+
</style>
|
|
@@ -67,5 +67,5 @@ withDefaults(defineProps<{
|
|
|
67
67
|
</template>
|
|
68
68
|
|
|
69
69
|
<style scoped>
|
|
70
|
-
.rm-product-card{align-self:stretch;background-color:var(--bg-surface);background-position:50%;background-repeat:no-repeat;background-size:cover;border-radius:var(--radius-xl,12px);box-shadow:0 6px 8px 0 rgba(var(--shadow-rgb,30 41 59),.1);display:flex;flex-direction:column;position:relative}.rm-product-card--padded{padding:var(--space-40)}.rm-product-card--healthcare{border:1px solid rgba(var(--healthcare-rgb),.1)}.rm-product-card--discovery{border:1px solid rgba(var(--discovery-rgb),.1)}.rm-product-card--databank{border:1px solid rgba(var(--databank-rgb),.1)}.rm-product-card--pharma{border:1px solid rgba(var(--pharma-rgb),.1)}.rm-product-card__inner{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--space-24)}.rm-product-card__inner--overlay{backdrop-filter:blur(5px);background:rgba(var(--surface-rgb),.7);border-radius:var(--radius-md,8px);gap:var(--space-16);margin:24px;padding:var(--space-24)
|
|
70
|
+
.rm-product-card{align-self:stretch;background-color:var(--bg-surface);background-position:50%;background-repeat:no-repeat;background-size:cover;border-radius:var(--radius-xl,12px);box-shadow:0 6px 8px 0 rgba(var(--shadow-rgb,30 41 59),.1);display:flex;flex-direction:column;position:relative}.rm-product-card--padded{padding:var(--space-40)}.rm-product-card--healthcare{border:1px solid rgba(var(--healthcare-rgb),.1)}.rm-product-card--discovery{border:1px solid rgba(var(--discovery-rgb),.1)}.rm-product-card--databank{border:1px solid rgba(var(--databank-rgb),.1)}.rm-product-card--pharma{border:1px solid rgba(var(--pharma-rgb),.1)}.rm-product-card__inner{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--space-24)}.rm-product-card__inner--overlay{backdrop-filter:blur(5px);background:rgba(var(--surface-rgb),.7);border-radius:var(--radius-md,8px);gap:var(--space-16);margin:24px;padding:var(--space-24)}.rm-product-card__title{font-size:26px;font-weight:900;line-height:normal}.rm-product-card--healthcare .rm-product-card__title{color:var(--color-primary)}.rm-product-card--discovery .rm-product-card__title{color:var(--color-discovery)}.rm-product-card--databank .rm-product-card__title{color:var(--color-databank)}.rm-product-card--pharma .rm-product-card__title{color:var(--color-pharma)}.rm-product-card__text{color:var(--text-primary);max-width:550px}.rm-product-card__actions{align-items:center;display:flex;gap:var(--space-16)}
|
|
71
71
|
</style>
|