@tekkare/romulus 0.1.68 → 0.1.69

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
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.67",
7
+ "version": "0.1.68",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -0,0 +1,78 @@
1
+ <!--
2
+ RmToolbar - rail vertical d'icones, colle a droite de l'analyse.
3
+
4
+ Reprend `arg-toolbar` d'Auriga : une icone par volet (astuces, sources,
5
+ info), pastille rouge sur « Sources » quand la page en compte. Le clic ouvre
6
+ `RmToolbarContent` sur le volet demande.
7
+
8
+ Deux choses changent. Les icones sont des `<button>` porteurs de
9
+ `aria-label` : les `<div @click>` d'Auriga n'etaient ni atteignables au
10
+ clavier ni annonces, et un rail d'icones sans texte est deja le pire cas pour
11
+ un lecteur d'ecran.
12
+
13
+ La pastille de compte porte son nombre en toutes lettres dans l'intitule
14
+ (« Sources, 4 »), au lieu d'un chiffre rouge que rien n'explique.
15
+ -->
16
+ <script setup lang="ts">
17
+ export interface RmToolbarItem {
18
+ /** Nom du volet, qui sert aussi de cle de slot dans RmToolbarContent. */
19
+ name: string
20
+ /** Icone Phosphor sans le prefixe "Ph". */
21
+ icon: string
22
+ /** Volet a ouvrir si different de `name`. */
23
+ openSlot?: string
24
+ /** Compte affiche en pastille. */
25
+ size?: number
26
+ }
27
+
28
+ const props = withDefaults(defineProps<{
29
+ items?: RmToolbarItem[]
30
+ /** Nombre de sources de la page, en pastille sur le volet « Sources ». */
31
+ sourceAmount?: number
32
+ }>(), {
33
+ items: () => [
34
+ { name: 'Tips', icon: 'Lightbulb' },
35
+ { name: 'Sources', icon: 'Database' },
36
+ { name: 'Info', icon: 'Info' },
37
+ ],
38
+ sourceAmount: 0,
39
+ })
40
+
41
+ const emit = defineEmits<{ openToolbar: [payload: { open: boolean, menu: string }] }>()
42
+
43
+ function countOf(item: RmToolbarItem) {
44
+ return item.name === 'Sources' ? props.sourceAmount : (item.size ?? 0)
45
+ }
46
+
47
+ function open(item: RmToolbarItem) {
48
+ const wanted = item.openSlot ?? item.name
49
+ // Un `openSlot` qui ne designe aucun volet ouvrirait le tiroir sur du vide :
50
+ // on retombe alors sur le premier.
51
+ const exists = props.items.some(el => el.name === wanted)
52
+ emit('openToolbar', { open: true, menu: exists ? wanted : props.items[0]?.name })
53
+ }
54
+ </script>
55
+
56
+ <template>
57
+ <div class="rm-toolbar">
58
+ <button
59
+ v-for="(item, index) in items"
60
+ :key="index"
61
+ type="button"
62
+ class="rm-toolbar__item"
63
+ :aria-label="countOf(item) > 0 ? `${item.name}, ${countOf(item)}` : item.name"
64
+ @click="open(item)"
65
+ >
66
+ <RmTooltip :text="item.name" dir="left" class="rm-toolbar__mark">
67
+ <RmIcon :name="item.icon" :size="20" />
68
+ <span v-if="countOf(item) > 0" class="rm-toolbar__count" aria-hidden="true">
69
+ {{ countOf(item) }}
70
+ </span>
71
+ </RmTooltip>
72
+ </button>
73
+ </div>
74
+ </template>
75
+
76
+ <style scoped>
77
+ .rm-toolbar{align-items:flex-start;border:1px solid var(--border-default);border-radius:var(--radius-md,8px);display:inline-flex;flex-direction:column;gap:4px;padding:4px}.rm-toolbar__item{align-items:center;background:none;border:none;border-radius:var(--radius-sm,4px);cursor:pointer;display:flex;gap:8px;justify-content:flex-end;padding:10px}.rm-toolbar__item:hover{background:var(--bg-hover)}.rm-toolbar__item:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-toolbar__mark{display:flex;position:relative}.rm-toolbar__count{align-items:center;background:var(--color-danger);border-radius:var(--radius-pill,30px);color:var(--color-text-inverse);display:flex;font-size:var(--font-10,10px);font-weight:900;height:16px;justify-content:center;line-height:normal;position:absolute;right:-5px;top:-5px;width:16px}
78
+ </style>
@@ -0,0 +1,94 @@
1
+ <!--
2
+ RmToolbarContent - le tiroir qu'ouvre `RmToolbar`.
3
+
4
+ Reprend `arg-toolbar-content` d'Auriga : un tiroir a droite, coupe en deux,
5
+ la colonne des volets a gauche et le contenu du volet actif a droite. Un slot
6
+ par volet, nomme comme lui.
7
+
8
+ Trois choses changent. Le tiroir est `RmDrawer`, qui piege le focus et rend
9
+ Echap : le tiroir d'Auriga laissait le focus derriere lui, dans la page
10
+ masquee.
11
+
12
+ Les volets sont des `<button>` dans un `role="tablist"`, avec
13
+ `aria-selected` : les `<div @click>` d'Auriga n'annoncaient ni role ni etat
14
+ actif.
15
+
16
+ Les volets inactifs sont retires du DOM plutot que caches en `display: none`.
17
+ Chez Auriga, un lecteur d'ecran lisait les trois volets a la suite comme s'ils
18
+ etaient tous a l'ecran.
19
+ -->
20
+ <script setup lang="ts">
21
+ import { computed } from 'vue'
22
+ import type { RmToolbarItem } from './Toolbar.vue'
23
+
24
+ const props = withDefaults(defineProps<{
25
+ items?: RmToolbarItem[]
26
+ /** Nombre de sources de la page, en pastille sur le volet « Sources ». */
27
+ sourceAmount?: number
28
+ lang?: 'fr' | 'en'
29
+ }>(), {
30
+ items: () => [
31
+ { name: 'Tips', icon: 'Lightbulb' },
32
+ { name: 'Sources', icon: 'Database' },
33
+ { name: 'Info', icon: 'Info' },
34
+ ],
35
+ sourceAmount: 0,
36
+ lang: 'fr',
37
+ })
38
+
39
+ const open = defineModel<boolean>('open', { default: false })
40
+ /** Volet ouvert, par son `name`. */
41
+ const menu = defineModel<string>('menu', { default: '' })
42
+
43
+ const WORDS = { fr: { close: 'Fermer' }, en: { close: 'Close' } } as const
44
+ const t = computed(() => WORDS[props.lang])
45
+
46
+ const active = computed(() => menu.value || props.items[0]?.name || '')
47
+
48
+ function countOf(item: RmToolbarItem) {
49
+ return item.name === 'Sources' ? props.sourceAmount : (item.size ?? 0)
50
+ }
51
+
52
+ /** « SourcesDetail » se lit « Sources Detail ». */
53
+ function labelOf(name: string) {
54
+ return name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, s => s.toUpperCase())
55
+ }
56
+ </script>
57
+
58
+ <template>
59
+ <RmDrawer v-model="open" side="right" size="620px" :close-label="t.close">
60
+ <div class="rm-tbc">
61
+ <div class="rm-tbc__rail" role="tablist" aria-orientation="vertical">
62
+ <button type="button" class="rm-tbc__close" @click="open = false">
63
+ <RmIcon name="X" :size="16" />
64
+ <span>{{ t.close }}</span>
65
+ </button>
66
+
67
+ <button
68
+ v-for="(item, index) in items"
69
+ :key="index"
70
+ type="button"
71
+ role="tab"
72
+ class="rm-tbc__tab"
73
+ :class="{ 'is-active': active === item.name }"
74
+ :aria-selected="active === item.name"
75
+ @click="menu = item.name"
76
+ >
77
+ <span class="rm-tbc__tab-left">
78
+ <RmIcon :name="item.icon" :size="20" />
79
+ <span>{{ labelOf(item.name) }}</span>
80
+ </span>
81
+ <span v-if="countOf(item) > 0" class="rm-tbc__count">{{ countOf(item) }}</span>
82
+ </button>
83
+ </div>
84
+
85
+ <div class="rm-tbc__panel" role="tabpanel">
86
+ <slot :name="active" />
87
+ </div>
88
+ </div>
89
+ </RmDrawer>
90
+ </template>
91
+
92
+ <style scoped>
93
+ .rm-tbc{height:100%}.rm-tbc,.rm-tbc__rail{align-items:flex-start;display:flex}.rm-tbc__rail{align-self:stretch;background:var(--bg-surface-2);flex-direction:column;gap:16px;padding:24px 0;width:160px}.rm-tbc__close{align-items:center;background:var(--bg-surface-3);border:none;border-radius:var(--radius-md,8px);color:var(--text-secondary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);gap:6px;line-height:1.6;margin:0 16px;padding:6px 12px 6px 8px}.rm-tbc__close:hover{background:var(--bg-hover)}.rm-tbc__tab{align-items:center;align-self:stretch;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-secondary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);font-weight:500;gap:4px;justify-content:space-between;line-height:normal;padding:0 16px 4px}.rm-tbc__tab.is-active{border-bottom-color:var(--color-primary);color:var(--text-primary)}.rm-tbc__tab.is-active :deep(svg){color:var(--color-primary)}.rm-tbc__tab:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-tbc__tab-left{align-items:center;display:flex;gap:4px}.rm-tbc__count{align-items:center;background:var(--color-danger);border-radius:var(--radius-pill,30px);color:var(--color-text-inverse);display:flex;font-size:var(--font-10,10px);font-weight:900;height:16px;justify-content:center;line-height:normal;width:16px}.rm-tbc__panel{align-items:flex-start;align-self:stretch;background:var(--bg-surface);border-left:1px solid var(--border-default);display:flex;flex:1;flex-direction:column;gap:24px;min-width:0;overflow-y:auto;padding:24px;scrollbar-width:thin}.rm-tbc__panel::-webkit-scrollbar{height:5px;width:5px}.rm-tbc__panel::-webkit-scrollbar-thumb{background:var(--text-muted);border-radius:var(--radius-xl,12px)}
94
+ </style>
@@ -0,0 +1,70 @@
1
+ <!--
2
+ RmToolbarSources - le volet « Sources » du tiroir d'outils.
3
+
4
+ Reprend `arg-toolbar-sources` d'Auriga : la mention legale de licence Open
5
+ Data, puis la liste des sources posee par l'appelant dans le slot `sources`.
6
+
7
+ La mention n'est pas decorative. Les jeux ouverts que le portail rediffuse
8
+ imposent de citer la licence et de signaler les donnees retraitees : elle
9
+ reste donc dans le composant, pas dans chaque page, ou elle finirait par
10
+ manquer sur celles qu'on oublie.
11
+
12
+ Les libelles vivent dans le composant plutot que dans des JSON de locale
13
+ charges au montage, qui laissaient le volet vide le temps du premier rendu.
14
+ -->
15
+ <script setup lang="ts">
16
+ import { computed } from 'vue'
17
+
18
+ const props = withDefaults(defineProps<{
19
+ lang?: 'fr' | 'en'
20
+ /** Ajoute la mention sur les donnees retraitees. */
21
+ showCopy?: boolean
22
+ }>(), {
23
+ lang: 'fr',
24
+ showCopy: false,
25
+ })
26
+
27
+ const WORDS = {
28
+ fr: {
29
+ title: 'Sources',
30
+ comply: 'Les données rediffusées ici respectent la',
31
+ license: 'Licence Open Data 2.0',
32
+ list: 'et la liste des sources ci-dessous.',
33
+ copy: 'Certaines données ont été retraitées pour les besoins de l\'analyse.',
34
+ href: 'https://rcd-media.com/tekkare/documents/Licence+Open+Data+2.0.pdf',
35
+ },
36
+ en: {
37
+ title: 'Sources',
38
+ comply: 'The data redistributed here complies with the',
39
+ license: 'Open Data License 2.0',
40
+ list: 'and the list of sources below.',
41
+ copy: 'Some data has been reprocessed for the purposes of the analysis.',
42
+ href: 'https://rcd-media.com/tekkare/documents/Open+Licence+2.0.pdf',
43
+ },
44
+ } as const
45
+
46
+ const t = computed(() => WORDS[props.lang])
47
+ </script>
48
+
49
+ <template>
50
+ <div class="rm-tbs">
51
+ <p class="rm-tbs__title">{{ t.title }}</p>
52
+
53
+ <div class="rm-tbs__legal">
54
+ <p>
55
+ {{ t.comply }}
56
+ <a :href="t.href" target="_blank" rel="noopener" class="rm-tbs__link">{{ t.license }}</a>,
57
+ {{ t.list }}
58
+ </p>
59
+ <p v-if="showCopy">{{ t.copy }}</p>
60
+ </div>
61
+
62
+ <div class="rm-tbs__list">
63
+ <slot name="sources" />
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <style scoped>
69
+ .rm-tbs{align-self:stretch;display:flex;flex-direction:column;gap:16px}.rm-tbs__title{color:var(--text-primary);font-size:var(--font-18,18px);font-weight:800;margin:0}.rm-tbs__legal{color:var(--text-secondary);display:flex;flex-direction:column;font-size:var(--font-12,12px);gap:8px;line-height:1.6}.rm-tbs__legal p{margin:0}.rm-tbs__link{color:var(--color-primary);text-decoration:underline;text-underline-offset:3px}.rm-tbs__list{align-self:stretch;display:flex;flex-direction:column;gap:12px}
70
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.68",
3
+ "version": "0.1.69",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",