@tekkare/romulus 0.1.88 → 0.1.89
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
|
@@ -5,6 +5,12 @@ import { PhMagnifyingGlass, PhX } from '@phosphor-icons/vue'
|
|
|
5
5
|
export interface SearchSuggestion {
|
|
6
6
|
label: string
|
|
7
7
|
field?: string
|
|
8
|
+
/**
|
|
9
|
+
* Icone Phosphor posee devant la suggestion, a la place de la loupe. Elle
|
|
10
|
+
* dit de quel champ la valeur vient (une marque, une molecule, un
|
|
11
|
+
* laboratoire) : sans elle, dix suggestions se ressemblent toutes.
|
|
12
|
+
*/
|
|
13
|
+
icon?: string
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
const model = defineModel<string>({ default: '' })
|
|
@@ -22,6 +28,22 @@ const props = withDefaults(defineProps<{
|
|
|
22
28
|
haveAdvanced?: boolean
|
|
23
29
|
/** Libelle du lien, pour les pages qui ne sont pas en anglais. */
|
|
24
30
|
advancedLabel?: string
|
|
31
|
+
/**
|
|
32
|
+
* Met en evidence la part saisie dans chaque suggestion. Le composant la
|
|
33
|
+
* decoupe lui-meme a partir du texte tape : la page n'a pas a fabriquer du
|
|
34
|
+
* HTML, et la couleur reste celle du theme.
|
|
35
|
+
*/
|
|
36
|
+
highlightMatch?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Les suggestions sont en cours de chargement. La liste s'ouvre sur une
|
|
39
|
+
* ligne d'attente plutot que de rester fermee : une recherche qui met une
|
|
40
|
+
* seconde a repondre ne doit pas se lire comme une recherche sans resultat.
|
|
41
|
+
*/
|
|
42
|
+
loading?: boolean
|
|
43
|
+
/** Ligne affichee pendant le chargement des suggestions. */
|
|
44
|
+
loadingText?: string
|
|
45
|
+
/** Ligne affichee quand la recherche ne ramene aucune suggestion. */
|
|
46
|
+
emptyText?: string
|
|
25
47
|
}>(), {
|
|
26
48
|
advancedActive: false,
|
|
27
49
|
advancedCount: 0,
|
|
@@ -29,6 +51,10 @@ const props = withDefaults(defineProps<{
|
|
|
29
51
|
placeholder: 'Search...',
|
|
30
52
|
haveAdvanced: true,
|
|
31
53
|
advancedLabel: 'Advanced search',
|
|
54
|
+
highlightMatch: false,
|
|
55
|
+
loading: false,
|
|
56
|
+
loadingText: 'Searching…',
|
|
57
|
+
emptyText: '',
|
|
32
58
|
})
|
|
33
59
|
|
|
34
60
|
const emit = defineEmits<{
|
|
@@ -72,6 +98,23 @@ function clearSearch() {
|
|
|
72
98
|
emit('search', '')
|
|
73
99
|
}
|
|
74
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Le libelle decoupe autour de la part saisie : avant, la correspondance,
|
|
103
|
+
* apres. Un `v-html` ferait le meme rendu, mais poserait du HTML fabrique par
|
|
104
|
+
* la page dans la liste ; trois morceaux de texte ne le posent pas.
|
|
105
|
+
*/
|
|
106
|
+
function parts(label: string) {
|
|
107
|
+
const query = model.value.trim()
|
|
108
|
+
if (!props.highlightMatch || !query) return [{ text: label, match: false }]
|
|
109
|
+
const at = label.toLowerCase().indexOf(query.toLowerCase())
|
|
110
|
+
if (at < 0) return [{ text: label, match: false }]
|
|
111
|
+
return [
|
|
112
|
+
{ text: label.slice(0, at), match: false },
|
|
113
|
+
{ text: label.slice(at, at + query.length), match: true },
|
|
114
|
+
{ text: label.slice(at + query.length), match: false },
|
|
115
|
+
].filter(part => part.text)
|
|
116
|
+
}
|
|
117
|
+
|
|
75
118
|
function onClickOutside(e: Event) {
|
|
76
119
|
if (barRef.value && !barRef.value.contains(e.target as Node)) {
|
|
77
120
|
showSuggestions.value = false
|
|
@@ -113,22 +156,39 @@ onUnmounted(() => document.removeEventListener('click', onClickOutside))
|
|
|
113
156
|
</div>
|
|
114
157
|
|
|
115
158
|
<Transition name="suggestions">
|
|
116
|
-
<div v-if="showSuggestions && suggestions.length > 0" class="search-bar__suggestions">
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
159
|
+
<div v-if="showSuggestions && (suggestions.length > 0 || loading || emptyText)" class="search-bar__suggestions">
|
|
160
|
+
<p v-if="loading" class="search-bar__suggestion-note">{{ loadingText }}</p>
|
|
161
|
+
<template v-else-if="suggestions.length > 0">
|
|
162
|
+
<button
|
|
163
|
+
v-for="(sugg, i) in suggestions"
|
|
164
|
+
:key="i"
|
|
165
|
+
class="search-bar__suggestion"
|
|
166
|
+
@click="selectSuggestion(sugg)"
|
|
167
|
+
>
|
|
168
|
+
<RmIcon
|
|
169
|
+
v-if="sugg.icon"
|
|
170
|
+
class="search-bar__suggestion-icon"
|
|
171
|
+
:name="sugg.icon"
|
|
172
|
+
:size="14"
|
|
173
|
+
color="var(--text-muted)"
|
|
174
|
+
/>
|
|
175
|
+
<PhMagnifyingGlass v-else :size="14" class="search-bar__suggestion-icon" />
|
|
176
|
+
<span class="search-bar__suggestion-label">
|
|
177
|
+
<span
|
|
178
|
+
v-for="(part, index) in parts(sugg.label)"
|
|
179
|
+
:key="index"
|
|
180
|
+
:class="{ 'search-bar__suggestion-match': part.match }"
|
|
181
|
+
>{{ part.text }}</span>
|
|
182
|
+
</span>
|
|
183
|
+
<span v-if="sugg.field" class="search-bar__suggestion-field">{{ sugg.field.split('.').pop() }}</span>
|
|
184
|
+
</button>
|
|
185
|
+
</template>
|
|
186
|
+
<p v-else class="search-bar__suggestion-note">{{ emptyText }}</p>
|
|
127
187
|
</div>
|
|
128
188
|
</Transition>
|
|
129
189
|
</div>
|
|
130
190
|
</template>
|
|
131
191
|
|
|
132
192
|
<style scoped>
|
|
133
|
-
.search-bar-wrapper{position:relative}.search-bar{align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-pill);display:inline-flex;gap:var(--space-8);height:32px;min-width:200px;padding:var(--space-4) var(--space-12);transition:border-color var(--duration-fast) var(--ease-in-out)}.search-bar:focus-within{border-color:var(--color-secondary);box-shadow:var(--shadow-focus)}.search-bar__icon{color:var(--text-muted);flex-shrink:0}.search-bar__input{background:transparent;border:none;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--font-14);outline:none;width:100%}.search-bar__input::-moz-placeholder{color:var(--text-muted)}.search-bar__input::placeholder{color:var(--text-muted)}.search-bar__clear{align-items:center;border:none;border-radius:50%;cursor:pointer;display:flex;flex-shrink:0;height:18px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:18px}.search-bar__clear,.search-bar__clear:hover{background:var(--border-default);color:var(--text-secondary)}.search-bar__advanced{background:none;border:none;border-left:1px solid var(--border-default);color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-family:var(--font-sans);font-size:var(--font-14);padding:0 0 0 var(--space-8);text-decoration:underline;transition:color var(--duration-fast) var(--ease-in-out);white-space:nowrap}.search-bar__advanced:hover{color:var(--color-healthcare)}.search-bar--advanced-active{border-color:var(--color-healthcare)}.search-bar__advanced--active{color:var(--color-healthcare);font-weight:500}.search-bar__advanced-badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;font-size:10px;font-weight:600;height:16px;justify-content:center;line-height:1;min-width:16px;padding:0 4px}.search-bar__suggestions{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08);left:0;max-height:280px;overflow-y:auto;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:50}.search-bar__suggestion{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:8px;padding:8px 10px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.search-bar__suggestion:hover{background-color:var(--bg-surface-2)}.search-bar__suggestion-icon{color:var(--text-muted);flex-shrink:0}.search-bar__suggestion-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-bar__suggestion-field{background:var(--bg-surface-2);border-radius:3px;color:var(--text-muted);flex-shrink:0;font-size:11px;padding:1px 6px}.suggestions-enter-active,.suggestions-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.suggestions-enter-from,.suggestions-leave-to{opacity:0;transform:translateY(-4px)}
|
|
193
|
+
.search-bar__suggestion-match{color:var(--color-secondary);font-weight:var(--weight-bold)}.search-bar__suggestion-note{color:var(--text-muted);font-size:var(--font-13);margin:0;padding:var(--space-8) var(--space-12)}.search-bar-wrapper{position:relative}.search-bar{align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-pill);display:inline-flex;gap:var(--space-8);height:32px;min-width:200px;padding:var(--space-4) var(--space-12);transition:border-color var(--duration-fast) var(--ease-in-out)}.search-bar:focus-within{border-color:var(--color-secondary);box-shadow:var(--shadow-focus)}.search-bar__icon{color:var(--text-muted);flex-shrink:0}.search-bar__input{background:transparent;border:none;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--font-14);outline:none;width:100%}.search-bar__input::-moz-placeholder{color:var(--text-muted)}.search-bar__input::placeholder{color:var(--text-muted)}.search-bar__clear{align-items:center;border:none;border-radius:50%;cursor:pointer;display:flex;flex-shrink:0;height:18px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:18px}.search-bar__clear,.search-bar__clear:hover{background:var(--border-default);color:var(--text-secondary)}.search-bar__advanced{background:none;border:none;border-left:1px solid var(--border-default);color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-family:var(--font-sans);font-size:var(--font-14);padding:0 0 0 var(--space-8);text-decoration:underline;transition:color var(--duration-fast) var(--ease-in-out);white-space:nowrap}.search-bar__advanced:hover{color:var(--color-healthcare)}.search-bar--advanced-active{border-color:var(--color-healthcare)}.search-bar__advanced--active{color:var(--color-healthcare);font-weight:500}.search-bar__advanced-badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;font-size:10px;font-weight:600;height:16px;justify-content:center;line-height:1;min-width:16px;padding:0 4px}.search-bar__suggestions{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:8px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08);left:0;max-height:280px;overflow-y:auto;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:50}.search-bar__suggestion{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:8px;padding:8px 10px;text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.search-bar__suggestion:hover{background-color:var(--bg-surface-2)}.search-bar__suggestion-icon{color:var(--text-muted);flex-shrink:0}.search-bar__suggestion-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-bar__suggestion-field{background:var(--bg-surface-2);border-radius:3px;color:var(--text-muted);flex-shrink:0;font-size:11px;padding:1px 6px}.suggestions-enter-active,.suggestions-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.suggestions-enter-from,.suggestions-leave-to{opacity:0;transform:translateY(-4px)}
|
|
134
194
|
</style>
|