@tekkare/romulus 0.1.140 → 0.1.142
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
|
@@ -68,6 +68,22 @@ export interface RmSavedView {
|
|
|
68
68
|
/** La vue s'applique a l'ouverture de la page. */
|
|
69
69
|
isDefault?: boolean
|
|
70
70
|
starred?: boolean
|
|
71
|
+
/**
|
|
72
|
+
* La vue vient de quelqu'un d'autre : son nom. Le lecteur ne peut alors
|
|
73
|
+
* qu'activer la vue ou la dupliquer, le contenu reste a son auteur ;
|
|
74
|
+
* l'etoile et la vue par defaut, elles, restent a sa main.
|
|
75
|
+
*/
|
|
76
|
+
sharedBy?: string
|
|
77
|
+
/** Ma vue, ouverte a d'autres. Le bandeau le rappelle a son auteur. */
|
|
78
|
+
shared?: boolean
|
|
79
|
+
/**
|
|
80
|
+
* Menu propre a cette vue, a la place de celui de la liste. Une liste ou
|
|
81
|
+
* cohabitent mes vues et celles des autres n'a pas un seul menu : le
|
|
82
|
+
* lecteur d'une vue partagee ne peut ni la renommer ni la supprimer.
|
|
83
|
+
*/
|
|
84
|
+
menu?: RmSavedViewMenuItem[]
|
|
85
|
+
/** Renommage sur place, a la place du reglage de la liste. */
|
|
86
|
+
renamable?: boolean
|
|
71
87
|
config?: RmSavedViewConfigRow[]
|
|
72
88
|
/** Pied de carte : auteur, date, compteurs. */
|
|
73
89
|
meta?: RmSavedViewMeta[]
|
|
@@ -99,6 +115,11 @@ const props = withDefaults(
|
|
|
99
115
|
{ menu: () => [], renamable: false, starrable: false, lang: 'fr' },
|
|
100
116
|
)
|
|
101
117
|
|
|
118
|
+
/** Menu de cette carte : celui de la vue s'il y en a un, sinon celui de la liste. */
|
|
119
|
+
const menuItems = computed(() => props.view.menu ?? props.menu)
|
|
120
|
+
/** Idem pour le renommage sur place. */
|
|
121
|
+
const canRename = computed(() => props.view.renamable ?? props.renamable)
|
|
122
|
+
|
|
102
123
|
const emit = defineEmits<{
|
|
103
124
|
activate: [id: string]
|
|
104
125
|
star: [id: string]
|
|
@@ -117,6 +138,9 @@ const T = computed(() => ({
|
|
|
117
138
|
isDefault: en.value
|
|
118
139
|
? 'Default view, applied on opening'
|
|
119
140
|
: "Vue par défaut, s'active à l'ouverture",
|
|
141
|
+
shared: en.value ? 'Shared' : 'Partagée',
|
|
142
|
+
sharedBy: (name: string) =>
|
|
143
|
+
en.value ? `Shared by ${name}` : `Partagée par ${name}`,
|
|
120
144
|
cancel: en.value ? 'Cancel' : 'Annuler',
|
|
121
145
|
confirmPrefix: en.value ? 'Permanently delete' : 'Supprimer définitivement',
|
|
122
146
|
}))
|
|
@@ -140,7 +164,7 @@ function pick(item: RmSavedViewMenuItem) {
|
|
|
140
164
|
pending.value = item
|
|
141
165
|
return
|
|
142
166
|
}
|
|
143
|
-
if (item.key === 'rename' &&
|
|
167
|
+
if (item.key === 'rename' && canRename.value) {
|
|
144
168
|
startRename()
|
|
145
169
|
return
|
|
146
170
|
}
|
|
@@ -220,9 +244,15 @@ function onCardClick() {
|
|
|
220
244
|
:class="{ 'rm-view--active': view.active, 'rm-view--default': view.isDefault }"
|
|
221
245
|
@click="onCardClick"
|
|
222
246
|
>
|
|
223
|
-
<div v-if="view.isDefault" class="rm-view__strip">
|
|
224
|
-
<
|
|
225
|
-
|
|
247
|
+
<div v-if="view.isDefault || view.sharedBy || view.shared" class="rm-view__strip">
|
|
248
|
+
<span v-if="view.isDefault" class="rm-view__strip-mark">
|
|
249
|
+
<RmIcon name="PushPin" :size="14" weight="fill" />
|
|
250
|
+
{{ T.isDefault }}
|
|
251
|
+
</span>
|
|
252
|
+
<span v-if="view.sharedBy || view.shared" class="rm-view__strip-mark">
|
|
253
|
+
<RmIcon name="UsersThree" :size="14" weight="fill" />
|
|
254
|
+
{{ view.sharedBy ? T.sharedBy(view.sharedBy) : T.shared }}
|
|
255
|
+
</span>
|
|
226
256
|
</div>
|
|
227
257
|
|
|
228
258
|
<div class="rm-view__head">
|
|
@@ -258,7 +288,7 @@ function onCardClick() {
|
|
|
258
288
|
@keydown.esc.prevent="renaming = false"
|
|
259
289
|
@blur="commitRename"
|
|
260
290
|
>
|
|
261
|
-
<span v-else class="rm-view__title" @dblclick.stop="
|
|
291
|
+
<span v-else class="rm-view__title" @dblclick.stop="canRename && startRename()">
|
|
262
292
|
{{ view.title }}
|
|
263
293
|
</span>
|
|
264
294
|
|
|
@@ -276,7 +306,7 @@ function onCardClick() {
|
|
|
276
306
|
{{ view.actionLabel || T.activate }}
|
|
277
307
|
</RmButton>
|
|
278
308
|
|
|
279
|
-
<div v-if="
|
|
309
|
+
<div v-if="menuItems.length" class="rm-view__menu-wrap">
|
|
280
310
|
<button
|
|
281
311
|
type="button"
|
|
282
312
|
class="rm-view__more"
|
|
@@ -294,7 +324,7 @@ function onCardClick() {
|
|
|
294
324
|
<div class="rm-view__menu-shade" @click.stop="menuOpen = false" />
|
|
295
325
|
<div class="rm-view__menu" role="menu">
|
|
296
326
|
<button
|
|
297
|
-
v-for="item in
|
|
327
|
+
v-for="item in menuItems"
|
|
298
328
|
:key="item.key"
|
|
299
329
|
type="button"
|
|
300
330
|
class="rm-view__menu-item"
|
|
@@ -366,5 +396,5 @@ function onCardClick() {
|
|
|
366
396
|
</template>
|
|
367
397
|
|
|
368
398
|
<style scoped>
|
|
369
|
-
.rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-
|
|
399
|
+
.rm-view{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);padding:14px 18px;transition:border-color var(--duration-fast) var(--ease-out),box-shadow var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out)}.rm-view:not(.rm-view--active):hover{background:var(--bg-hover);border-color:var(--color-accent-soft)}.rm-view--active{border-color:var(--color-secondary);box-shadow:0 0 0 3px rgba(var(--brand-rgb),.08);cursor:default}.rm-view__strip{align-items:center;background:var(--bg-hover);border-bottom:1px solid var(--border-soft);border-radius:7px 7px 0 0;color:var(--color-secondary);display:flex;font-size:var(--font-11);font-weight:var(--weight-bold);gap:var(--space-8);letter-spacing:.06em;margin:-14px -18px var(--space-12);padding:var(--space-8) var(--space-16);text-transform:uppercase}.rm-view__strip-mark{align-items:center;display:inline-flex;gap:var(--space-6)}.rm-view__strip-mark+.rm-view__strip-mark:before{background:var(--border-soft);content:"";height:10px;margin-right:var(--space-8);width:1px}.rm-view__head{align-items:center;display:flex;gap:var(--space-10)}.rm-view__icon{align-items:center;border-radius:var(--radius-md);display:inline-flex;flex:0 0 auto;height:34px;justify-content:center;width:34px}.rm-view__icon--primary{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__icon--success{background:var(--color-success-bg);color:var(--color-success)}.rm-view__icon--warning{background:var(--color-warning-bg);color:var(--color-warning)}.rm-view__icon--danger{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__icon--info{background:var(--color-info-bg);color:var(--color-info)}.rm-view__star{align-items:center;background:transparent;border:1px solid transparent;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;flex:0 0 auto;height:28px;justify-content:center;padding:0;width:28px}.rm-view__star:hover{background:var(--bg-hover)}.rm-view__star--on,.rm-view__star:hover{color:var(--color-warning)}.rm-view__title,.rm-view__title-edit{flex:1;font-size:var(--font-16);font-weight:var(--weight-bold);min-width:0}.rm-view__title-edit{background:var(--bg-surface);border:1px solid var(--color-secondary);border-radius:var(--radius-md);color:var(--text-primary);font-family:inherit;outline:none;padding:var(--space-2) var(--space-8)}.rm-view__title-edit:focus{box-shadow:0 0 0 3px rgba(var(--brand-rgb),.12)}.rm-view__actions{align-items:center;display:flex;gap:var(--space-8)}.rm-view__badge{align-items:center;background:var(--color-success-bg);border-radius:var(--radius-pill);color:var(--color-success);display:inline-flex;font-size:var(--font-10);font-weight:var(--weight-bold);gap:var(--space-4);letter-spacing:.06em;padding:3px var(--space-8);text-transform:uppercase}.rm-view__menu-wrap{position:relative}.rm-view__more{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-muted);cursor:pointer;display:inline-flex;height:28px;justify-content:center;padding:0;width:28px}.rm-view__more:hover{background:var(--bg-hover);color:var(--text-primary)}.rm-view__menu-shade{inset:0;position:fixed;z-index:60}.rm-view__menu{background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;min-width:200px;padding:var(--space-4);position:absolute;right:0;top:calc(100% + 6px);z-index:61}.rm-view__menu-item{align-items:center;background:transparent;border:none;border-radius:var(--radius-md);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-13);gap:var(--space-8);padding:var(--space-8) var(--space-12);text-align:left;width:100%}.rm-view__menu-item:hover{background:var(--bg-hover);color:var(--color-secondary)}.rm-view__menu-item--danger{color:var(--color-danger)}.rm-view__menu-item--danger:hover{background:var(--color-danger-bg);color:var(--color-danger)}.rm-view__desc{color:var(--text-secondary);font-size:var(--font-13);margin:var(--space-8) 0 var(--space-12)}.rm-view__config{background:var(--bg-surface-2);border:1px solid var(--border-soft);border-radius:var(--radius-md);display:flex;flex-direction:column;gap:var(--space-6);margin:10px 0 var(--space-12);padding:var(--space-10) 14px}.rm-view__row{align-items:flex-start;display:flex;flex-wrap:wrap;font-size:var(--font-12);gap:14px}.rm-view__row-key{color:var(--text-muted);display:inline-flex;flex-shrink:0;font-size:var(--font-10);font-weight:var(--weight-semibold);letter-spacing:.04em;min-width:96px;padding-top:var(--space-4);text-transform:uppercase}.rm-view__row-key,.rm-view__row-val{align-items:center;gap:var(--space-6)}.rm-view__row-val{color:var(--text-primary);display:flex;flex:1 1 200px;flex-wrap:wrap;padding:var(--space-2) 0}.rm-view__pill{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);display:inline-flex;font-size:var(--font-12);gap:var(--space-4);padding:5px var(--space-10)}.rm-view__pill-k{color:var(--color-primary);font-weight:var(--weight-semibold)}.rm-view__pill-tip{min-width:0}.rm-view__pill-tip :deep(.rm-tooltip__bubble){max-width:320px}.rm-view__pill-v{display:-webkit-box;-webkit-box-orient:vertical;color:var(--text-primary);font-weight:var(--weight-medium);-webkit-line-clamp:3;overflow:hidden}.rm-view__foot{border-top:1px solid var(--border-soft);color:var(--text-muted);display:flex;flex-wrap:wrap;font-size:var(--font-11);gap:var(--space-16);padding-top:var(--space-10)}.rm-view__foot span{align-items:center;display:inline-flex;gap:var(--space-4)}.rm-view__confirm{align-items:center;background:var(--color-danger-bg);border:1px solid var(--color-danger);border-radius:var(--radius-md);color:var(--color-danger);display:flex;flex-wrap:wrap;font-size:var(--font-13);gap:var(--space-12);margin-top:var(--space-12);padding:var(--space-8) var(--space-12)}.rm-view__confirm>span{align-items:center;display:inline-flex;flex:1 1 220px;gap:var(--space-8)}.rm-view__confirm-actions{display:flex;flex-shrink:0;gap:var(--space-8)}
|
|
370
400
|
</style>
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmShareDialog - a qui une vue sauvegardee est ouverte.
|
|
3
|
+
|
|
4
|
+
Trois portees, exclusives : a personne, a des gens nommes, a tout l'espace
|
|
5
|
+
de travail. « Tout l'espace » n'est pas un raccourci pour « cocher tout le
|
|
6
|
+
monde » : c'est une regle, et elle vaut aussi pour les membres qui arrivent
|
|
7
|
+
apres. Deplier la case en une liste de noms au moment du partage laisserait
|
|
8
|
+
les arrivants dehors, sans que personne s'en apercoive.
|
|
9
|
+
|
|
10
|
+
La liste des noms reste montee quand la portee change : on revient de
|
|
11
|
+
« tout l'espace » a « des personnes » sans avoir reperdu sa selection.
|
|
12
|
+
|
|
13
|
+
Le dialogue ne parle pas a l'API. Il rend une portee et une liste d'ids ;
|
|
14
|
+
l'appelant enregistre. Le package ne sait pas ou vivent les vues.
|
|
15
|
+
-->
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { computed, ref, watch } from 'vue'
|
|
18
|
+
|
|
19
|
+
export interface RmShareMember {
|
|
20
|
+
userId: string
|
|
21
|
+
/** Nom affiche. A defaut, l'appelant y met l'adresse. */
|
|
22
|
+
name: string
|
|
23
|
+
email?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type RmShareScope = 'private' | 'people' | 'workspace'
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(
|
|
29
|
+
defineProps<{
|
|
30
|
+
modelValue: boolean
|
|
31
|
+
/** Nom de la vue partagee, rappele dans l'en-tete. */
|
|
32
|
+
title?: string
|
|
33
|
+
/** Membres de l'espace, sans son proprietaire : l'appelant le retire. */
|
|
34
|
+
members?: RmShareMember[]
|
|
35
|
+
/** Portee enregistree. `people` se deduit d'une liste non vide. */
|
|
36
|
+
visibility?: 'private' | 'workspace'
|
|
37
|
+
/** Ids deja destinataires. */
|
|
38
|
+
sharedWith?: string[]
|
|
39
|
+
loading?: boolean
|
|
40
|
+
lang?: 'fr' | 'en'
|
|
41
|
+
}>(),
|
|
42
|
+
{
|
|
43
|
+
title: '',
|
|
44
|
+
members: () => [],
|
|
45
|
+
visibility: 'private',
|
|
46
|
+
sharedWith: () => [],
|
|
47
|
+
loading: false,
|
|
48
|
+
lang: 'fr',
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const emit = defineEmits<{
|
|
53
|
+
'update:modelValue': [value: boolean]
|
|
54
|
+
save: [value: { visibility: 'private' | 'workspace'; sharedWith: string[] }]
|
|
55
|
+
}>()
|
|
56
|
+
|
|
57
|
+
const en = computed(() => props.lang === 'en')
|
|
58
|
+
|
|
59
|
+
const T = computed(() => ({
|
|
60
|
+
heading: en.value ? 'Share the view' : 'Partager la vue',
|
|
61
|
+
private: en.value ? 'Only me' : 'Moi seul',
|
|
62
|
+
privateHint: en.value
|
|
63
|
+
? 'Nobody else sees this view.'
|
|
64
|
+
: 'Personne d\'autre ne voit cette vue.',
|
|
65
|
+
people: en.value ? 'Chosen people' : 'Des personnes choisies',
|
|
66
|
+
peopleHint: en.value
|
|
67
|
+
? 'They can apply the view and mark it, not change it.'
|
|
68
|
+
: 'Elles peuvent activer la vue et la marquer, pas la modifier.',
|
|
69
|
+
workspace: en.value ? 'The whole workspace' : 'Tout l\'espace de travail',
|
|
70
|
+
workspaceHint: en.value
|
|
71
|
+
? 'Members who join later see it too.'
|
|
72
|
+
: 'Les membres qui arrivent ensuite la voient aussi.',
|
|
73
|
+
search: en.value ? 'Search for a person' : 'Chercher une personne',
|
|
74
|
+
empty: en.value ? 'No member to share with.' : 'Aucun membre a qui partager.',
|
|
75
|
+
noMatch: en.value ? 'No match.' : 'Aucun resultat.',
|
|
76
|
+
count: (n: number) =>
|
|
77
|
+
en.value
|
|
78
|
+
? `${n} ${n > 1 ? 'people' : 'person'} selected`
|
|
79
|
+
: `${n} ${n > 1 ? 'personnes' : 'personne'} selectionnee${n > 1 ? 's' : ''}`,
|
|
80
|
+
cancel: en.value ? 'Cancel' : 'Annuler',
|
|
81
|
+
save: en.value ? 'Share' : 'Partager',
|
|
82
|
+
}))
|
|
83
|
+
|
|
84
|
+
const scope = ref<RmShareScope>('private')
|
|
85
|
+
const picked = ref<string[]>([])
|
|
86
|
+
const search = ref('')
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* L'etat ouvert fait foi : le dialogue se recharge a chaque ouverture, jamais
|
|
90
|
+
* pendant, sinon une reponse de l'API en cours de saisie reposerait les cases.
|
|
91
|
+
*/
|
|
92
|
+
watch(
|
|
93
|
+
() => props.modelValue,
|
|
94
|
+
(open) => {
|
|
95
|
+
if (!open) return
|
|
96
|
+
picked.value = [...props.sharedWith]
|
|
97
|
+
scope.value =
|
|
98
|
+
props.visibility === 'workspace'
|
|
99
|
+
? 'workspace'
|
|
100
|
+
: props.sharedWith.length
|
|
101
|
+
? 'people'
|
|
102
|
+
: 'private'
|
|
103
|
+
search.value = ''
|
|
104
|
+
},
|
|
105
|
+
{ immediate: true },
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const matches = computed(() => {
|
|
109
|
+
const q = search.value.trim().toLowerCase()
|
|
110
|
+
if (!q) return props.members
|
|
111
|
+
return props.members.filter(
|
|
112
|
+
m =>
|
|
113
|
+
m.name.toLowerCase().includes(q) ||
|
|
114
|
+
(m.email ?? '').toLowerCase().includes(q),
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
function toggle(userId: string) {
|
|
119
|
+
const next = new Set(picked.value)
|
|
120
|
+
if (next.has(userId)) next.delete(userId)
|
|
121
|
+
else next.add(userId)
|
|
122
|
+
picked.value = [...next]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function close() {
|
|
126
|
+
emit('update:modelValue', false)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Enregistrer efface ce que la portee retenue ne porte pas : passer a « moi
|
|
131
|
+
* seul » retire les destinataires, sinon ils resteraient en base a attendre
|
|
132
|
+
* le prochain partage sans que rien ne le dise a l'ecran.
|
|
133
|
+
*/
|
|
134
|
+
function save() {
|
|
135
|
+
emit('save', {
|
|
136
|
+
visibility: scope.value === 'workspace' ? 'workspace' : 'private',
|
|
137
|
+
sharedWith: scope.value === 'people' ? picked.value : [],
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const scopes = computed<Array<{ key: RmShareScope; icon: string; label: string; hint: string }>>(
|
|
142
|
+
() => [
|
|
143
|
+
{ key: 'private', icon: 'Lock', label: T.value.private, hint: T.value.privateHint },
|
|
144
|
+
{ key: 'people', icon: 'UserPlus', label: T.value.people, hint: T.value.peopleHint },
|
|
145
|
+
{
|
|
146
|
+
key: 'workspace',
|
|
147
|
+
icon: 'UsersThree',
|
|
148
|
+
label: T.value.workspace,
|
|
149
|
+
hint: T.value.workspaceHint,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
)
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<RmModal
|
|
157
|
+
:model-value="modelValue"
|
|
158
|
+
size="sm"
|
|
159
|
+
mode="free"
|
|
160
|
+
:label="T.heading"
|
|
161
|
+
@update:model-value="close"
|
|
162
|
+
>
|
|
163
|
+
<div class="rm-share">
|
|
164
|
+
<header class="rm-share__head">
|
|
165
|
+
<h2 class="rm-share__title">{{ T.heading }}</h2>
|
|
166
|
+
<p v-if="title" class="rm-share__subject">{{ title }}</p>
|
|
167
|
+
</header>
|
|
168
|
+
|
|
169
|
+
<!--
|
|
170
|
+
Trois choix exclusifs : des boutons radio natifs, pas trois boutons.
|
|
171
|
+
Le groupe s'annonce alors comme un choix, se parcourt aux fleches et
|
|
172
|
+
dit lequel est retenu, sans qu'on ait a le recoder.
|
|
173
|
+
-->
|
|
174
|
+
<ul class="rm-share__scopes">
|
|
175
|
+
<li v-for="item in scopes" :key="item.key">
|
|
176
|
+
<label class="rm-share__scope" :class="{ 'is-on': scope === item.key }">
|
|
177
|
+
<input
|
|
178
|
+
v-model="scope"
|
|
179
|
+
type="radio"
|
|
180
|
+
name="rm-share-scope"
|
|
181
|
+
class="rm-share__radio"
|
|
182
|
+
:value="item.key"
|
|
183
|
+
:aria-label="item.label"
|
|
184
|
+
>
|
|
185
|
+
<RmIcon :name="item.icon" :size="18" />
|
|
186
|
+
<span class="rm-share__scope-text">
|
|
187
|
+
<span class="rm-share__scope-label">{{ item.label }}</span>
|
|
188
|
+
<span class="rm-share__scope-hint">{{ item.hint }}</span>
|
|
189
|
+
</span>
|
|
190
|
+
<RmIcon
|
|
191
|
+
v-if="scope === item.key"
|
|
192
|
+
name="CheckCircle"
|
|
193
|
+
:size="16"
|
|
194
|
+
weight="fill"
|
|
195
|
+
class="rm-share__scope-mark"
|
|
196
|
+
/>
|
|
197
|
+
</label>
|
|
198
|
+
</li>
|
|
199
|
+
</ul>
|
|
200
|
+
|
|
201
|
+
<div v-show="scope === 'people'" class="rm-share__people">
|
|
202
|
+
<label class="rm-share__search">
|
|
203
|
+
<RmIcon name="MagnifyingGlass" :size="16" />
|
|
204
|
+
<input v-model="search" type="search" :placeholder="T.search" :aria-label="T.search">
|
|
205
|
+
</label>
|
|
206
|
+
|
|
207
|
+
<p v-if="!members.length" class="rm-share__empty">{{ T.empty }}</p>
|
|
208
|
+
<p v-else-if="!matches.length" class="rm-share__empty">{{ T.noMatch }}</p>
|
|
209
|
+
|
|
210
|
+
<ul v-else class="rm-share__list">
|
|
211
|
+
<li v-for="member in matches" :key="member.userId">
|
|
212
|
+
<label class="rm-share__member">
|
|
213
|
+
<input
|
|
214
|
+
type="checkbox"
|
|
215
|
+
class="rm-share__check"
|
|
216
|
+
:checked="picked.includes(member.userId)"
|
|
217
|
+
:aria-label="member.name"
|
|
218
|
+
@change="toggle(member.userId)"
|
|
219
|
+
>
|
|
220
|
+
<RmIcon
|
|
221
|
+
:name="picked.includes(member.userId) ? 'CheckSquare' : 'Square'"
|
|
222
|
+
:weight="picked.includes(member.userId) ? 'fill' : 'regular'"
|
|
223
|
+
:size="18"
|
|
224
|
+
/>
|
|
225
|
+
<span class="rm-share__member-text">
|
|
226
|
+
<span class="rm-share__member-name">{{ member.name }}</span>
|
|
227
|
+
<span v-if="member.email" class="rm-share__member-mail">{{ member.email }}</span>
|
|
228
|
+
</span>
|
|
229
|
+
</label>
|
|
230
|
+
</li>
|
|
231
|
+
</ul>
|
|
232
|
+
|
|
233
|
+
<p v-if="picked.length" class="rm-share__count">{{ T.count(picked.length) }}</p>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<footer class="rm-share__foot">
|
|
237
|
+
<RmButton variant="ghost" size="sm" @click="close">
|
|
238
|
+
{{ T.cancel }}
|
|
239
|
+
</RmButton>
|
|
240
|
+
<RmButton variant="primary" size="sm" :disabled="loading" @click="save">
|
|
241
|
+
{{ T.save }}
|
|
242
|
+
</RmButton>
|
|
243
|
+
</footer>
|
|
244
|
+
</div>
|
|
245
|
+
</RmModal>
|
|
246
|
+
</template>
|
|
247
|
+
|
|
248
|
+
<style scoped>
|
|
249
|
+
.rm-share{gap:var(--space-16);padding:var(--space-4)}.rm-share,.rm-share__head{display:flex;flex-direction:column}.rm-share__head{gap:var(--space-4)}.rm-share__title{color:var(--color-primary);font-size:var(--font-16);font-weight:var(--weight-bold)}.rm-share__subject{color:var(--color-secondary);font-size:var(--font-13)}.rm-share__scopes{display:flex;flex-direction:column;gap:var(--space-8);list-style:none}.rm-share__scope{align-items:flex-start;background:var(--bg-surface);border:1px solid var(--border-soft);border-radius:var(--radius-lg);color:var(--color-primary);cursor:pointer;display:flex;gap:var(--space-10);padding:var(--space-10) var(--space-12);text-align:left;width:100%}.rm-share__scope:hover{background:var(--bg-hover)}.rm-share__check,.rm-share__radio{border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);white-space:nowrap}.rm-share__scope:has(.rm-share__radio:focus-visible){outline:2px solid var(--color-secondary);outline-offset:2px}.rm-share__scope.is-on{background:var(--bg-hover);border-color:var(--color-secondary)}.rm-share__scope-text{display:flex;flex:1;flex-direction:column;gap:2px}.rm-share__scope-label{font-size:var(--font-13);font-weight:var(--weight-medium)}.rm-share__scope-hint{font-size:var(--font-12)}.rm-share__scope-hint,.rm-share__scope-mark{color:var(--color-secondary)}.rm-share__people{display:flex;flex-direction:column;gap:var(--space-8)}.rm-share__search{align-items:center;background:var(--bg-surface);border:1px solid var(--border-soft);border-radius:var(--radius-pill);color:var(--color-secondary);display:flex;gap:var(--space-8);padding:var(--space-6) var(--space-10)}.rm-share__search input{background:none;border:none;color:var(--color-primary);flex:1;font-size:var(--font-13);outline:none}.rm-share__list{display:flex;flex-direction:column;gap:2px;list-style:none;max-height:220px;overflow-y:auto}.rm-share__member{align-items:center;border-radius:var(--radius-md);cursor:pointer;display:flex;gap:var(--space-8);padding:var(--space-6) var(--space-8)}.rm-share__member:hover{background:var(--bg-hover)}.rm-share__member:has(.rm-share__check:focus-visible){outline:2px solid var(--color-secondary);outline-offset:2px}.rm-share__member-text{display:flex;flex-direction:column}.rm-share__member-name{color:var(--color-primary);font-size:var(--font-13)}.rm-share__member-mail{color:var(--color-secondary);font-size:var(--font-11)}.rm-share__count,.rm-share__empty{color:var(--color-secondary);font-size:var(--font-12)}.rm-share__foot{display:flex;gap:var(--space-8);justify-content:flex-end}
|
|
250
|
+
</style>
|
|
@@ -94,6 +94,7 @@ export declare function rmBaseOption(theme: RmChartTheme, decal?: boolean): {
|
|
|
94
94
|
borderWidth: number;
|
|
95
95
|
};
|
|
96
96
|
};
|
|
97
|
+
export declare const rmTooltipTrigger: (count: number) => "axis" | "item";
|
|
97
98
|
export declare function rmLegend(theme: RmChartTheme, count: number, position?: 'top' | 'bottom'): {
|
|
98
99
|
pageIconSize?: number | undefined;
|
|
99
100
|
pageIconColor?: string | undefined;
|
|
@@ -177,6 +178,17 @@ export interface RmSeriesInput {
|
|
|
177
178
|
}
|
|
178
179
|
/** Bar chart vertical, une ou plusieurs series, empilable. */
|
|
179
180
|
export declare function rmBarVerticalOption(categories: (string | number)[], series: RmSeriesInput[], theme: RmChartTheme, palette: string[], decal?: boolean): {
|
|
181
|
+
tooltip: {
|
|
182
|
+
trigger: string;
|
|
183
|
+
backgroundColor: string;
|
|
184
|
+
textStyle: {
|
|
185
|
+
color: string;
|
|
186
|
+
fontFamily: string;
|
|
187
|
+
fontSize: number;
|
|
188
|
+
};
|
|
189
|
+
borderColor: string;
|
|
190
|
+
borderWidth: number;
|
|
191
|
+
};
|
|
180
192
|
legend: {
|
|
181
193
|
pageIconSize?: number | undefined;
|
|
182
194
|
pageIconColor?: string | undefined;
|
|
@@ -282,17 +294,6 @@ export declare function rmBarVerticalOption(categories: (string | number)[], ser
|
|
|
282
294
|
textStyle: {
|
|
283
295
|
fontFamily: string;
|
|
284
296
|
};
|
|
285
|
-
tooltip: {
|
|
286
|
-
trigger: string;
|
|
287
|
-
backgroundColor: string;
|
|
288
|
-
textStyle: {
|
|
289
|
-
color: string;
|
|
290
|
-
fontFamily: string;
|
|
291
|
-
fontSize: number;
|
|
292
|
-
};
|
|
293
|
-
borderColor: string;
|
|
294
|
-
borderWidth: number;
|
|
295
|
-
};
|
|
296
297
|
};
|
|
297
298
|
/** Bar chart horizontal, avec valeur affichee en bout de barre. */
|
|
298
299
|
export declare function rmBarHorizontalOption(labels: string[], values: (number | null)[], theme: RmChartTheme, palette: string[], decal?: boolean, formatter?: (value: number) => string): {
|
|
@@ -422,17 +423,7 @@ export declare function rmLineOption(categories: (string | number)[], series: Rm
|
|
|
422
423
|
valueFormatter?: (value: number) => string;
|
|
423
424
|
}): {
|
|
424
425
|
tooltip: {
|
|
425
|
-
|
|
426
|
-
backgroundColor: string;
|
|
427
|
-
textStyle: {
|
|
428
|
-
color: string;
|
|
429
|
-
fontFamily: string;
|
|
430
|
-
fontSize: number;
|
|
431
|
-
};
|
|
432
|
-
borderColor: string;
|
|
433
|
-
borderWidth: number;
|
|
434
|
-
} | {
|
|
435
|
-
valueFormatter: (value: unknown) => string;
|
|
426
|
+
valueFormatter?: ((value: unknown) => string) | undefined;
|
|
436
427
|
trigger: string;
|
|
437
428
|
backgroundColor: string;
|
|
438
429
|
textStyle: {
|
|
@@ -90,6 +90,8 @@ export function rmBaseOption(theme, decal = false) {
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
const TOOLTIP_AXIS_MIN_SERIES = 10;
|
|
94
|
+
export const rmTooltipTrigger = (count) => count < TOOLTIP_AXIS_MIN_SERIES ? "item" : "axis";
|
|
93
95
|
const LEGEND_MAX_INLINE = 6;
|
|
94
96
|
const LEGEND_NAME_MAX = 38;
|
|
95
97
|
const LEGEND_NAME_TAIL = 12;
|
|
@@ -141,6 +143,7 @@ const axisCommon = (theme) => ({
|
|
|
141
143
|
export function rmBarVerticalOption(categories, series, theme, palette, decal = false) {
|
|
142
144
|
return {
|
|
143
145
|
...rmBaseOption(theme, decal),
|
|
146
|
+
tooltip: { ...rmBaseOption(theme, decal).tooltip, trigger: rmTooltipTrigger(series.length) },
|
|
144
147
|
legend: series.length > 1 ? rmLegend(theme, series.length) : void 0,
|
|
145
148
|
// La legende passee sous le graphe prend sa place dans la gouttiere basse,
|
|
146
149
|
// sinon elle recouvre les libelles de l'axe des abscisses.
|
|
@@ -205,10 +208,11 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
|
|
|
205
208
|
const base = rmBaseOption(theme, decal);
|
|
206
209
|
return {
|
|
207
210
|
...base,
|
|
208
|
-
tooltip:
|
|
211
|
+
tooltip: {
|
|
209
212
|
...base.tooltip,
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
trigger: rmTooltipTrigger(series.length),
|
|
214
|
+
...valueFormatter ? { valueFormatter: (value) => typeof value === "number" ? format(value) : "" } : {}
|
|
215
|
+
},
|
|
212
216
|
// Legende obligatoire en double axe : sans elle, rien ne dit quelle courbe
|
|
213
217
|
// se lit sur quel axe.
|
|
214
218
|
legend: series.length > 1 || dual ? rmLegend(theme, series.length, legendPosition) : void 0,
|