@tekkare/romulus 0.1.67 → 0.1.68

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.66",
7
+ "version": "0.1.67",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -0,0 +1,126 @@
1
+ <!--
2
+ RmAppSidebar - barre laterale repliable, composee en slots.
3
+
4
+ Reprend `arg-sidebar` d'Auriga : 200px depliee, 54px repliee, bouton retour
5
+ a l'accueil, titre de l'app, corps defilant (slot `menu`) et pied colle en bas
6
+ (slot `footer`). Se replie seule sous 1000px de large.
7
+
8
+ Laquelle des barres du paquet prendre :
9
+ - celle-ci quand la page compose son menu en balises (`RmMenuItem`,
10
+ `RmSubMenu`) et doit glisser ses propres blocs entre les entrees ;
11
+ - `RmSidebar` quand le menu tient dans une simple liste d'entrees ;
12
+ - `RmSidebarLeft` pour le chrome du portail, decrit par une prop `sections` ;
13
+ - `RmSidebarRight` pour le rail d'icones.
14
+
15
+ Trois choses changent par rapport a Auriga. Les libelles du chrome sont dans
16
+ le composant plutot que charges depuis des JSON de locale : un composant qui
17
+ va chercher un fichier au montage affiche une bulle vide le temps du premier
18
+ rendu.
19
+
20
+ Le retour a l'accueil est un vrai `<a href>` : le `window.location.href`
21
+ d'Auriga privait du clic milieu et du survol qui montre la cible.
22
+
23
+ L'etat replie est un `v-model`, si bien qu'une page peut le poser elle-meme.
24
+ -->
25
+ <script setup lang="ts">
26
+ import { computed, onMounted, onUnmounted } from 'vue'
27
+
28
+ const props = withDefaults(defineProps<{
29
+ /** Nom de l'app, en titre. Absent, le slot `title` prend la place. */
30
+ name?: string
31
+ /** Code ISO 3166-1 alpha-2 pose devant le nom. */
32
+ appIso?: string
33
+ /** Cible du bouton accueil. `null` retire le bouton. */
34
+ homeLink?: string | null
35
+ lang?: 'fr' | 'en'
36
+ /** Largeur en dessous de laquelle la barre se replie seule. */
37
+ breakpoint?: number
38
+ }>(), {
39
+ name: '',
40
+ appIso: undefined,
41
+ homeLink: '/',
42
+ lang: 'fr',
43
+ breakpoint: 1000,
44
+ })
45
+
46
+ const emit = defineEmits<{ sidebarChange: [open: boolean] }>()
47
+
48
+ const open = defineModel<boolean>('open', { default: true })
49
+
50
+ const WORDS = {
51
+ fr: { home: 'Accueil', toggle: 'Replier ou déplier la barre' },
52
+ en: { home: 'Home', toggle: 'Collapse or expand the sidebar' },
53
+ } as const
54
+
55
+ const t = computed(() => WORDS[props.lang])
56
+
57
+ /**
58
+ * Sous le seuil, la barre depliee mange la moitie de l'ecran : elle se replie
59
+ * seule, et se laisse rouvrir a la main ensuite.
60
+ */
61
+ function handleResize() {
62
+ const next = window.innerWidth >= props.breakpoint
63
+ if (next === open.value) return
64
+ open.value = next
65
+ emit('sidebarChange', next)
66
+ }
67
+
68
+ function toggle() {
69
+ open.value = !open.value
70
+ emit('sidebarChange', open.value)
71
+ }
72
+
73
+ onMounted(() => {
74
+ window.addEventListener('resize', handleResize)
75
+ handleResize()
76
+ })
77
+ onUnmounted(() => window.removeEventListener('resize', handleResize))
78
+ </script>
79
+
80
+ <template>
81
+ <div id="rm-app-sidebar" class="rm-asb" :class="{ 'rm-asb--open': open }">
82
+ <div
83
+ class="rm-asb__head"
84
+ :class="[
85
+ open ? 'rm-asb__head--full' : 'rm-asb__head--small',
86
+ { 'rm-asb__head--end': open && homeLink === null },
87
+ ]"
88
+ >
89
+ <a v-if="homeLink !== null" :href="homeLink" class="rm-asb__home">
90
+ <RmTooltip :text="t.home" :disabled="open" dir="right" class="rm-asb__mark">
91
+ <RmIcon name="House" :size="20" color="var(--color-primary)" />
92
+ </RmTooltip>
93
+ <span v-if="open">{{ t.home }}</span>
94
+ </a>
95
+
96
+ <button type="button" class="rm-asb__toggle" :aria-expanded="open" @click="toggle">
97
+ <RmTooltip :text="t.toggle" :disabled="open" dir="right" class="rm-asb__mark">
98
+ <RmIcon name="Sidebar" :size="20" color="var(--color-primary)" />
99
+ </RmTooltip>
100
+ <span class="rm-sr-only">{{ t.toggle }}</span>
101
+ </button>
102
+ </div>
103
+
104
+ <h1 v-show="open" class="rm-asb__title">
105
+ <slot v-if="!name" name="title" />
106
+ <span v-else class="rm-asb__name">
107
+ <RmFlag v-if="appIso" :code="appIso" :height="16" />
108
+ <span>{{ name }}</span>
109
+ </span>
110
+ </h1>
111
+
112
+ <div class="rm-asb__body">
113
+ <div class="rm-asb__content">
114
+ <slot name="menu" />
115
+ </div>
116
+ </div>
117
+
118
+ <div class="rm-asb__footer">
119
+ <slot name="footer" />
120
+ </div>
121
+ </div>
122
+ </template>
123
+
124
+ <style scoped>
125
+ .rm-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}.rm-asb{align-items:flex-start;background:var(--bg-surface-2);border-right:1px solid var(--border-default);box-sizing:border-box;display:flex;flex-direction:column;gap:16px;height:100vh;left:0;padding:16px 8px;position:fixed;top:0;transition:width .3s ease;width:54px;z-index:98}.rm-asb--open{width:200px}.rm-asb__head{align-items:center;display:flex}.rm-asb__head--full{align-self:stretch;justify-content:space-between}.rm-asb__head--end{justify-content:flex-end}.rm-asb__head--small{flex-direction:column;gap:4px}.rm-asb__home,.rm-asb__toggle{align-items:center;background:transparent;border:none;border-radius:var(--radius-sm,4px);color:var(--color-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);gap:6px;height:32px;line-height:1.6;padding:6px;text-decoration:none}.rm-asb__toggle{align-self:flex-end;justify-content:center}.rm-asb__home:hover,.rm-asb__toggle:hover{background:var(--bg-hover)}.rm-asb__home:focus-visible,.rm-asb__toggle:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.rm-asb__mark{display:flex}.rm-asb__title{align-self:stretch;font-size:20px;font-weight:900;line-height:normal;margin:0 0 16px;padding:0 8px}.rm-asb__name,.rm-asb__title{align-items:center;display:flex;gap:8px}.rm-asb__body{gap:16px;overflow:hidden;width:100%}.rm-asb__body,.rm-asb__content{display:flex;flex:1;flex-direction:column;min-height:0}.rm-asb__content{gap:8px;overflow-y:auto;scrollbar-width:thin}.rm-asb__content::-webkit-scrollbar{height:5px;width:5px}.rm-asb__content::-webkit-scrollbar-thumb{background:var(--text-muted);border-radius:var(--radius-xl,12px)}.rm-asb__footer{width:100%}
126
+ </style>
@@ -0,0 +1,78 @@
1
+ <!--
2
+ RmMenuItem - entree de navigation d'une barre laterale.
3
+
4
+ Reprend `arg-menu-item` d'Auriga : icone ou drapeau, intitule qui disparait
5
+ quand la barre est repliee, bulle a droite qui prend le relais dans ce cas,
6
+ et squelette scintillant pendant le chargement.
7
+
8
+ A poser dans `RmSidebar`, qui transmet son etat replie. Pour un menu a
9
+ sections depliables, c'est `RmAccordionMenu`.
10
+
11
+ Deux choses changent. L'entree en chargement porte `aria-busy` et un intitule
12
+ masque : chez Auriga, c'etait une barre grise muette, que le lecteur d'ecran
13
+ sautait sans rien dire.
14
+
15
+ Le drapeau passe par `RmFlag` plutot que par l'emoji, qui ne se ligature pas
16
+ sous Windows.
17
+ -->
18
+ <script setup lang="ts">
19
+ withDefaults(defineProps<{
20
+ to: string
21
+ label: string
22
+ /** Icone Phosphor sans le prefixe "Ph". A defaut, `countryIso`. */
23
+ icon?: string
24
+ /** Code ISO 3166-1 alpha-2, pour une entree qui designe un pays. */
25
+ countryIso?: string
26
+ /** Barre depliee : l'intitule sort et la bulle se tait. */
27
+ sidebarOpen?: boolean
28
+ newTab?: boolean
29
+ /** Entree pas encore prete : squelette a la place du contenu. */
30
+ loading?: boolean
31
+ }>(), {
32
+ icon: undefined,
33
+ countryIso: undefined,
34
+ sidebarOpen: true,
35
+ newTab: false,
36
+ loading: false,
37
+ })
38
+
39
+ const emit = defineEmits<{ clickLink: [payload: { label: string, to: string }] }>()
40
+ </script>
41
+
42
+ <template>
43
+ <NuxtLink
44
+ :to="to"
45
+ class="rm-menu-item"
46
+ :class="{ 'rm-menu-item--loading': loading }"
47
+ active-class="is-active"
48
+ :target="newTab ? '_blank' : '_self'"
49
+ :aria-busy="loading ? 'true' : undefined"
50
+ @click="emit('clickLink', { label, to })"
51
+ >
52
+ <template v-if="loading">
53
+ <span class="rm-menu-item__skeleton" aria-hidden="true" />
54
+ <span class="rm-sr-only">{{ label }}</span>
55
+ </template>
56
+
57
+ <template v-else>
58
+ <!-- Repliee, la barre ne montre que l'icone : la bulle porte alors le
59
+ nom, sinon l'entree n'est identifiable qu'a l'essai. -->
60
+ <RmTooltip
61
+ :text="label"
62
+ :disabled="sidebarOpen"
63
+ dir="right"
64
+ class="rm-menu-item__mark"
65
+ :class="{ 'rm-menu-item__mark--grow': !sidebarOpen }"
66
+ >
67
+ <RmIcon v-if="icon" :name="icon" :size="20" />
68
+ <RmFlag v-else-if="countryIso" :code="countryIso" :height="16" />
69
+ </RmTooltip>
70
+
71
+ <span v-show="sidebarOpen" class="rm-menu-item__label">{{ label }}</span>
72
+ </template>
73
+ </NuxtLink>
74
+ </template>
75
+
76
+ <style scoped>
77
+ .rm-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;white-space:nowrap}.rm-menu-item{align-items:center;align-self:stretch;border-radius:var(--radius-sm,4px);color:var(--text-primary);display:flex;font-size:var(--font-14,14px);gap:8px;line-height:1.6;padding:8px;text-decoration:none}.rm-menu-item--loading{height:36px;padding:0}.rm-menu-item :deep(svg){color:var(--text-secondary)}.rm-menu-item:hover:not(.is-active){background:var(--bg-hover)}.rm-menu-item.is-active{background:rgba(var(--brand-rgb),.1)}.rm-menu-item.is-active :deep(svg),.rm-menu-item:hover:not(.is-active) :deep(svg){color:var(--color-primary)}.rm-menu-item:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-menu-item__mark{display:flex}.rm-menu-item__mark--grow{flex:1 1 100%}.rm-menu-item__label{min-width:0}.rm-menu-item__skeleton{background:var(--bg-surface-2);border-radius:var(--radius-sm,4px);height:100%;overflow:hidden;position:relative;width:100%}.rm-menu-item__skeleton:after{animation:rm-menu-item-shimmer 1.5s infinite;background:linear-gradient(90deg,rgba(var(--brand-rgb),0) 0,rgba(var(--brand-rgb),.08) 50%,rgba(var(--brand-rgb),0) 100%);content:"";height:100%;left:-100%;position:absolute;top:0;width:100%}@keyframes rm-menu-item-shimmer{to{left:100%}}@media (prefers-reduced-motion:reduce){.rm-menu-item__skeleton:after{animation:none}}
78
+ </style>
@@ -0,0 +1,68 @@
1
+ <!--
2
+ RmMenuLink - lien secondaire d'une barre laterale, ou action.
3
+
4
+ Reprend `arg-menu-link` d'Auriga : meme rangee que `RmMenuItem` mais sans
5
+ etat actif, pour ce qui vit au pied de la barre (aide, contact, deconnexion).
6
+ `action` en fait un bouton qui emet `click` au lieu de naviguer.
7
+
8
+ Une chose change : la branche action est un vrai `<button>`. Le `<div @click>`
9
+ d'Auriga n'etait ni atteignable au clavier ni annonce comme actionnable, ce
10
+ qui coupait la deconnexion a qui navigue sans souris.
11
+ -->
12
+ <script setup lang="ts">
13
+ withDefaults(defineProps<{
14
+ label: string
15
+ /** Icone Phosphor sans le prefixe "Ph". */
16
+ icon: string
17
+ to?: string
18
+ /** Barre depliee : l'intitule sort et la bulle se tait. */
19
+ sidebarOpen?: boolean
20
+ /** Bouton d'action plutot que lien de navigation. */
21
+ action?: boolean
22
+ newTab?: boolean
23
+ }>(), {
24
+ to: '/',
25
+ sidebarOpen: true,
26
+ action: false,
27
+ newTab: false,
28
+ })
29
+
30
+ const emit = defineEmits<{ click: [] }>()
31
+ </script>
32
+
33
+ <template>
34
+ <NuxtLink
35
+ v-if="!action"
36
+ :to="to"
37
+ class="rm-menu-link"
38
+ :target="newTab ? '_blank' : '_self'"
39
+ >
40
+ <RmTooltip
41
+ :text="label"
42
+ :disabled="sidebarOpen"
43
+ dir="right"
44
+ class="rm-menu-link__mark"
45
+ :class="{ 'rm-menu-link__mark--grow': !sidebarOpen }"
46
+ >
47
+ <RmIcon :name="icon" :size="20" />
48
+ </RmTooltip>
49
+ <span v-show="sidebarOpen">{{ label }}</span>
50
+ </NuxtLink>
51
+
52
+ <button v-else type="button" class="rm-menu-link" @click="emit('click')">
53
+ <RmTooltip
54
+ :text="label"
55
+ :disabled="sidebarOpen"
56
+ dir="right"
57
+ class="rm-menu-link__mark"
58
+ :class="{ 'rm-menu-link__mark--grow': !sidebarOpen }"
59
+ >
60
+ <RmIcon :name="icon" :size="20" />
61
+ </RmTooltip>
62
+ <span v-show="sidebarOpen">{{ label }}</span>
63
+ </button>
64
+ </template>
65
+
66
+ <style scoped>
67
+ .rm-menu-link{align-items:center;align-self:stretch;background:none;border:none;border-radius:var(--radius-sm,4px);color:var(--text-primary);cursor:pointer;display:flex;font-family:inherit;font-size:var(--font-14,14px);gap:8px;line-height:1.6;margin:8px 0;padding:8px;text-align:left;text-decoration:none;text-transform:capitalize;width:100%}.rm-menu-link :deep(svg){color:var(--text-secondary)}.rm-menu-link:hover{background:var(--bg-hover)}.rm-menu-link:hover :deep(svg){color:var(--color-primary)}.rm-menu-link:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-menu-link__mark{display:flex}.rm-menu-link__mark--grow{flex:1 1 100%}
68
+ </style>
@@ -0,0 +1,69 @@
1
+ <!--
2
+ RmSubMenu - groupe repliable dans une barre laterale.
3
+
4
+ Reprend `arg-sub-menu` d'Auriga : un intitule, un chevron qui pivote, et les
5
+ entrees en dessous. Repliee, la barre ne garde que le chevron, la bulle porte
6
+ alors le nom du groupe.
7
+
8
+ A poser dans `RmSidebar`, autour de `RmMenuItem`. Pour un menu entier decrit
9
+ en donnees plutot qu'en balises, c'est `RmAccordionMenu`.
10
+
11
+ Deux choses changent : le bouton porte `aria-expanded`, et l'ouverture est un
12
+ `v-model` optionnel. Chez Auriga l'etat etait interne et lu une seule fois au
13
+ montage : une page qui voulait ouvrir un groupe apres coup ne pouvait pas.
14
+ -->
15
+ <script setup lang="ts">
16
+ withDefaults(defineProps<{
17
+ label: string
18
+ /** Icone Phosphor devant l'intitule. A defaut, `flagIso`. */
19
+ icon?: string
20
+ /** Code ISO 3166-1 alpha-2, pour un groupe qui designe un pays. */
21
+ flagIso?: string
22
+ /** Barre depliee : l'intitule sort et la bulle se tait. */
23
+ sidebarOpen?: boolean
24
+ }>(), {
25
+ icon: undefined,
26
+ flagIso: undefined,
27
+ sidebarOpen: true,
28
+ })
29
+
30
+ /** Groupe ouvert. Ouvert par defaut : un menu tout replie cache la navigation. */
31
+ const open = defineModel<boolean>('open', { default: true })
32
+ </script>
33
+
34
+ <template>
35
+ <div class="rm-submenu" :class="{ 'rm-submenu--collapsed': !sidebarOpen }">
36
+ <button
37
+ type="button"
38
+ class="rm-submenu__head"
39
+ :aria-expanded="open"
40
+ @click="open = !open"
41
+ >
42
+ <span v-if="sidebarOpen" class="rm-submenu__title">
43
+ <RmIcon v-if="icon" :name="icon" :size="12" :class="{ 'is-open': open }" />
44
+ <RmFlag v-else-if="flagIso" :code="flagIso" :height="12" />
45
+ <span>{{ label }}</span>
46
+ </span>
47
+
48
+ <RmTooltip
49
+ :text="label"
50
+ :disabled="sidebarOpen"
51
+ dir="right"
52
+ class="rm-submenu__caret-slot"
53
+ :class="{ 'rm-submenu__caret-slot--grow': !sidebarOpen }"
54
+ >
55
+ <span class="rm-submenu__caret" :class="{ 'is-open': open }">
56
+ <RmIcon name="CaretDown" :size="12" />
57
+ </span>
58
+ </RmTooltip>
59
+ </button>
60
+
61
+ <div v-show="open" class="rm-submenu__body">
62
+ <slot />
63
+ </div>
64
+ </div>
65
+ </template>
66
+
67
+ <style scoped>
68
+ .rm-submenu{align-items:flex-start;flex-direction:column;gap:8px}.rm-submenu,.rm-submenu__head{align-self:stretch;display:flex;width:100%}.rm-submenu__head{align-items:center;background:none;border:none;color:var(--text-primary);cursor:pointer;font-family:inherit;font-size:var(--font-14,14px);font-weight:700;gap:10px;justify-content:space-between;line-height:normal;padding:3px 8px 6px}.rm-submenu__head:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}.rm-submenu__title{align-items:center;display:flex;gap:4px}.rm-submenu__title :deep(svg){color:var(--text-secondary)}.rm-submenu__title :deep(svg.is-open){color:var(--color-primary)}.rm-submenu__caret-slot{display:flex}.rm-submenu__caret-slot--grow{flex:1 1 100%}.rm-submenu__caret{color:var(--text-secondary);display:flex;transition:transform .3s ease}.rm-submenu__caret.is-open{color:var(--color-primary);transform:rotate(180deg)}.rm-submenu__body{display:flex;flex-direction:column;gap:8px;width:100%}
69
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",