@ouestfrance/sipa-bms-ui 8.21.0 → 8.22.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouestfrance/sipa-bms-ui",
3
- "version": "8.21.0",
3
+ "version": "8.22.1",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -0,0 +1,60 @@
1
+ import BmsGhost from './BmsGhost.vue';
2
+
3
+ export default {
4
+ title: 'Composants/feedback/Ghost',
5
+ component: BmsGhost,
6
+ };
7
+
8
+ const Template = (args) => ({
9
+ components: {
10
+ BmsGhost,
11
+ },
12
+ data() {
13
+ return { args };
14
+ },
15
+ template: `
16
+ <div
17
+ :style="{
18
+ position: 'fixed',
19
+ top: 0,
20
+ left: 0,
21
+ width: '100%',
22
+ height: '100%',
23
+ padding: '1em',
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ justifyContent: 'center',
27
+ userSelect: 'none',
28
+ cursor: 'pointer'
29
+ }"
30
+ @pointerdown="args.modelValue = !args.modelValue"
31
+ >
32
+ <p>Cliquez pour faire apparaître/Disparaître le fantôme</p>
33
+ </div>
34
+ <BmsGhost
35
+ v-model="args.modelValue"
36
+ :opacity-when-visible="args.opacityWhenVisible"
37
+ >
38
+ <div
39
+ :style="{
40
+ background: 'darkslategray',
41
+ color: 'white',
42
+ padding: '1em',
43
+ borderRadius: '8px',
44
+ }"
45
+ >
46
+ <h3 :style="{
47
+ margin: 0,
48
+ color: 'white',
49
+ }">Bonjour !</h3>
50
+ <p style="margin: 0">Je suis un fantôme</p>
51
+ </div>
52
+ </BmsGhost>
53
+ `,
54
+ });
55
+
56
+ export const Default = Template.bind({});
57
+ Default.args = {
58
+ modelValue: false,
59
+ opacityWhenVisible: 0.6,
60
+ };
@@ -0,0 +1,82 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ opacityWhenVisible?: number;
4
+ }
5
+
6
+ const SCREEN_MARGIN = 10;
7
+
8
+ import { onBeforeMount, onBeforeUnmount, ref } from 'vue';
9
+
10
+ const visible = defineModel<boolean>({
11
+ default: false,
12
+ });
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ opacityWhenVisible: 0.8,
16
+ });
17
+
18
+ const ghost = ref<HTMLElement | null>(null);
19
+ const x = ref<number>(0);
20
+ const y = ref<number>(0);
21
+
22
+ onBeforeMount(() => {
23
+ document.addEventListener('pointermove', onPointerMove);
24
+ });
25
+
26
+ onBeforeUnmount(() => {
27
+ document.removeEventListener('pointermove', onPointerMove);
28
+ });
29
+
30
+ const onPointerMove = (e: PointerEvent) => {
31
+ e.preventDefault();
32
+ e.stopPropagation();
33
+
34
+ updateGhost(e.clientX, e.clientY);
35
+ };
36
+
37
+ const updateGhost = (posX: number, posY: number) => {
38
+ if (!ghost.value) return;
39
+
40
+ const rect = ghost.value.getBoundingClientRect();
41
+ const screenWidth = window.innerWidth;
42
+ const screenHeight = window.innerHeight;
43
+
44
+ //Update position
45
+ x.value = Math.max(
46
+ Math.min(posX, screenWidth - rect.width - SCREEN_MARGIN),
47
+ SCREEN_MARGIN,
48
+ );
49
+ y.value = Math.max(
50
+ Math.min(posY, screenHeight - rect.height - SCREEN_MARGIN),
51
+ SCREEN_MARGIN,
52
+ );
53
+ };
54
+ </script>
55
+
56
+ <template>
57
+ <div
58
+ ref="ghost"
59
+ aria-hidden="true"
60
+ class="bms-ghost"
61
+ :style="{
62
+ '--ghost-x': `${x}px`,
63
+ '--ghost-y': `${y}px`,
64
+ opacity: visible ? props.opacityWhenVisible : 0,
65
+ }"
66
+ >
67
+ <slot />
68
+ </div>
69
+ </template>
70
+
71
+ <style scoped lang="scss">
72
+ .bms-ghost {
73
+ position: fixed;
74
+ top: var(--ghost-y, 0);
75
+ left: var(--ghost-x, 0);
76
+ z-index: 1000;
77
+
78
+ pointer-events: none;
79
+ user-select: none;
80
+ transition: opacity 0.2s ease-in-out;
81
+ }
82
+ </style>
@@ -5,6 +5,7 @@
5
5
  :visible="showToolTip && activated"
6
6
  :direction="direction"
7
7
  :getAnchorElementValues="getAnchorElementValues"
8
+ :class="classes"
8
9
  >
9
10
  <slot name="tooltipText">
10
11
  {{ tooltipText }}
@@ -26,9 +27,12 @@ import {
26
27
  MaybeElementRef,
27
28
  useElementVisibility,
28
29
  } from '@vueuse/core';
29
- import { ref, watchEffect } from 'vue';
30
+ import { computed, ref, watchEffect } from 'vue';
30
31
  import UiTooltip from './UiTooltip.vue';
31
32
 
33
+ import { useAttrs } from 'vue';
34
+ const attrs = useAttrs();
35
+
32
36
  const tooltip = ref(null);
33
37
  const tooltipAnchor = ref<Element>();
34
38
  const showToolTip = ref(true);
@@ -51,6 +55,16 @@ withDefaults(
51
55
  },
52
56
  );
53
57
 
58
+ const classes = computed(() => {
59
+ const parentClasses = attrs.class as string | undefined;
60
+ return (
61
+ parentClasses
62
+ ?.split(' ')
63
+ .map((cls) => `${cls}__bms-tooltip`)
64
+ .join(' ') ?? ''
65
+ );
66
+ });
67
+
54
68
  const isUnderOverlay = (): boolean => {
55
69
  const overlays = Array.from(document.getElementsByClassName('overlay'));
56
70
  if (overlays.length === 0) {
@@ -25,6 +25,9 @@ const calculatedTooltipTransform: Ref<string> = ref<string>('none');
25
25
  const windowSize = useWindowSize();
26
26
  const { getTooltipTranslatePosition } = useTooltipHelper();
27
27
 
28
+ import { useAttrs } from 'vue';
29
+ const attrs = useAttrs();
30
+
28
31
  const props = withDefaults(
29
32
  defineProps<{
30
33
  visible?: boolean;
@@ -61,7 +64,8 @@ const anchorElement = ref<
61
64
  >(undefined);
62
65
 
63
66
  const classes = computed(
64
- () => `bms-tooltip__text bms-tooltip__text--${props.direction}`,
67
+ () =>
68
+ `bms-tooltip__text bms-tooltip__text--${props.direction} ${attrs.class ? attrs.class : ''}`,
65
69
  );
66
70
 
67
71
  const setTooltipPosition = async () => {
@@ -136,6 +140,10 @@ const style = computed(() => {
136
140
  transform: var(--bms-tooltip-transform);
137
141
  overflow-wrap: break-word;
138
142
 
143
+ &.floating-window__expand-btn__bms-tooltip {
144
+ z-index: calc(var(--bms-z-index-modal) + 1);
145
+ }
146
+
139
147
  &::before {
140
148
  content: '';
141
149
  position: absolute;
@@ -14,7 +14,7 @@ const Template = (args) => ({
14
14
  return { args };
15
15
  },
16
16
  template: `
17
- <div style="width: ${args.width}; height: ${args.height}; background: purple; position:relative">
17
+ <div style="width: ${args.containerWidth || '1200px'}; height: ${args.containerHeight || '100vh'}; background: purple; position:relative">
18
18
  <BmsFloatingWindow v-bind="args">
19
19
  ${args?.content}
20
20
  </BmsFloatingWindow>
@@ -24,32 +24,31 @@ const Template = (args) => ({
24
24
 
25
25
  export const Default = Template.bind({});
26
26
  Default.args = {
27
- width: '200px',
28
- height: '200px',
29
27
  title: 'My title',
30
28
  modelValue: true,
31
29
  };
32
30
 
33
31
  export const Large = Template.bind({});
34
32
  Large.args = {
33
+ containerWidth: '1200px',
34
+ containerHeight: '100vh',
35
35
  width: '1200px',
36
- height: '100vh',
37
36
  title: 'My title',
38
37
  modelValue: true,
39
38
  };
40
39
 
41
40
  export const Closed = Template.bind({});
42
41
  Closed.args = {
43
- width: '1200px',
44
- height: '100vh',
42
+ containerWidth: '1200px',
43
+ containerHeight: '100vh',
45
44
  title: 'My title',
46
45
  modelValue: false,
47
46
  };
48
47
 
49
48
  export const Overflow = Template.bind({});
50
49
  Overflow.args = {
51
- width: '1200px',
52
- height: '100vh',
50
+ containerWidth: '1200px',
51
+ containerHeight: '100vh',
53
52
  title: 'Cyrano de Bergerac',
54
53
  modelValue: true,
55
54
  content: `Ah ! non ! c’est un peu court, jeune homme !
@@ -99,11 +98,226 @@ Si vous aviez un peu de lettres et d’esprit
99
98
  Mais d’esprit, ô le plus lamentable des êtres,
100
99
  Vous n’en eûtes jamais un atome, et de lettres
101
100
  Vous n’avez que les trois qui forment le mot : sot !
102
- Eussiez-vous eu, dailleurs, linvention quil faut
101
+ Eussiez-vous eu, d'ailleurs, l'invention qu'il faut
103
102
  Pour pouvoir là, devant ces nobles galeries,
104
103
  me servir toutes ces folles plaisanteries,
105
- Que vous nen eussiez pas articulé le quart
106
- De la moitié du commencement dune, car
104
+ Que vous n'en eussiez pas articulé le quart
105
+ De la moitié du commencement d'une, car
107
106
  Je me les sers moi-même, avec assez de verve,
108
- Mais je ne permets pas quun autre me les serve.`,
107
+ Mais je ne permets pas qu'un autre me les serve.`,
108
+ };
109
+
110
+ const IframeTemplate = (args) => ({
111
+ components: {
112
+ BmsFloatingWindow,
113
+ },
114
+ setup() {
115
+ return { args };
116
+ },
117
+ template: `
118
+ <div style="width: 100%; height: 100vh; position: relative;">
119
+ <iframe
120
+ src="https://example.com"
121
+ style="width: 100%; height: 100%; border: none;"
122
+ ></iframe>
123
+ <BmsFloatingWindow v-bind="args">
124
+ <p style="padding: 1em;">Essayez de déplacer cette fenêtre au-dessus de l'iframe.</p>
125
+ <p style="padding: 1em;">Le drag and drop fonctionne grâce à l'overlay qui intercepte les événements.</p>
126
+ </BmsFloatingWindow>
127
+ </div>
128
+ `,
129
+ });
130
+
131
+ export const WithIframe = IframeTemplate.bind({});
132
+ WithIframe.args = {
133
+ title: 'Drag me over the iframe',
134
+ modelValue: true,
135
+ };
136
+ WithIframe.parameters = {
137
+ docs: {
138
+ description: {
139
+ story:
140
+ "Démontre le drag and drop fonctionnant au-dessus d'une iframe. L'overlay transparent capture les événements souris même quand la souris passe au-dessus de l'iframe.",
141
+ },
142
+ },
143
+ };
144
+
145
+ export const PlacementTopLeft = Template.bind({});
146
+ PlacementTopLeft.args = {
147
+ containerWidth: '1200px',
148
+ containerHeight: '100vh',
149
+ title: 'Top Left Placement',
150
+ modelValue: true,
151
+ defaultPlacement: 'top-left',
152
+ };
153
+ PlacementTopLeft.parameters = {
154
+ docs: {
155
+ description: {
156
+ story: 'Fenêtre positionnée par défaut en haut à gauche.',
157
+ },
158
+ },
159
+ };
160
+
161
+ export const PlacementTopRight = Template.bind({});
162
+ PlacementTopRight.args = {
163
+ containerWidth: '1200px',
164
+ containerHeight: '100vh',
165
+ title: 'Top Right Placement',
166
+ modelValue: true,
167
+ defaultPlacement: 'top-right',
168
+ };
169
+
170
+ export const PlacementBottomLeft = Template.bind({});
171
+ PlacementBottomLeft.args = {
172
+ containerWidth: '1200px',
173
+ containerHeight: '100vh',
174
+ title: 'Bottom Left Placement',
175
+ modelValue: true,
176
+ defaultPlacement: 'bottom-left',
177
+ };
178
+
179
+ export const PlacementBottomRight = Template.bind({});
180
+ PlacementBottomRight.args = {
181
+ containerWidth: '1200px',
182
+ containerHeight: '100vh',
183
+ title: 'Bottom Right Placement',
184
+ modelValue: true,
185
+ defaultPlacement: 'bottom-right',
186
+ };
187
+
188
+ export const Expandable = Template.bind({});
189
+ Expandable.args = {
190
+ containerWidth: '1200px',
191
+ containerHeight: '100vh',
192
+ title: 'Fenêtre expandable',
193
+ modelValue: true,
194
+ expandable: true,
195
+ content:
196
+ '<p style="padding: 1em;">Cliquez sur l\'icône maximize pour agrandir la fenêtre au parent.</p>',
197
+ };
198
+ Expandable.parameters = {
199
+ docs: {
200
+ description: {
201
+ story:
202
+ "Fenêtre avec bouton expand activé. Cliquez sur l'icône pour agrandir/réduire.",
203
+ },
204
+ },
205
+ };
206
+
207
+ export const ExpandableCustomSize = Template.bind({});
208
+ ExpandableCustomSize.args = {
209
+ containerWidth: '1200px',
210
+ containerHeight: '100vh',
211
+ title: 'Expand avec dimensions custom',
212
+ modelValue: true,
213
+ expandable: true,
214
+ expandedWidth: '80%',
215
+ expandedHeight: '80%',
216
+ content:
217
+ '<p style="padding: 1em;">Expand avec dimensions personnalisées (80% x 80%).</p>',
218
+ };
219
+ ExpandableCustomSize.parameters = {
220
+ docs: {
221
+ description: {
222
+ story:
223
+ 'Fenêtre expandable avec dimensions personnalisées (expandedWidth et expandedHeight).',
224
+ },
225
+ },
226
+ };
227
+
228
+ const NestedContainersTemplate = (args) => ({
229
+ components: {
230
+ BmsFloatingWindow,
231
+ },
232
+ setup() {
233
+ return { args };
234
+ },
235
+ template: `
236
+ <div id="outer-container"
237
+ style="width: 100%; height: 100vh; background-color: #667eea; padding: 2em; box-sizing: border-box;">
238
+ <p style="color: white; margin: 0 0 1em;">Container externe - id="outer-container"</p>
239
+ <div id="inner-container"
240
+ style="width: 80%; height: 80%; background-color: #f093fb; padding: 2em; box-sizing: border-box; position: relative;">
241
+ <p style="color: white; margin: 0 0 1em;">Container interne - id="inner-container"</p>
242
+ <BmsFloatingWindow v-bind="args">
243
+ <p style="padding: 1em;">Cliquez sur expand pour voir la fenêtre s'agrandir sur le container
244
+ <strong>interne</strong>.</p>
245
+ <p style="padding: 0 1em;">La prop <code>expandTarget="#inner-container"</code> définit la cible.</p>
246
+ </BmsFloatingWindow>
247
+ </div>
248
+ </div>
249
+ `,
250
+ });
251
+
252
+ export const ExpandToInnerTarget = NestedContainersTemplate.bind({});
253
+ ExpandToInnerTarget.args = {
254
+ title: 'Expand vers inner container',
255
+ modelValue: true,
256
+ expandable: true,
257
+ expandTarget: '#inner-container',
258
+ defaultPlacement: 'top-left',
259
+ };
260
+ ExpandToInnerTarget.parameters = {
261
+ docs: {
262
+ description: {
263
+ story:
264
+ "Démontre expandTarget : la fenêtre s'agrandit sur le container interne (rose) plutôt que sur son parent direct.",
265
+ },
266
+ },
267
+ };
268
+
269
+ export const ExpandToOuterTarget = NestedContainersTemplate.bind({});
270
+ ExpandToOuterTarget.args = {
271
+ title: 'Expand vers outer container',
272
+ modelValue: true,
273
+ expandable: true,
274
+ expandTarget: '#outer-container',
275
+ defaultPlacement: 'top-left',
276
+ };
277
+ ExpandToOuterTarget.parameters = {
278
+ docs: {
279
+ description: {
280
+ story:
281
+ "Démontre expandTarget : la fenêtre s'agrandit sur le container externe (violet).",
282
+ },
283
+ },
284
+ };
285
+
286
+ const ForeignContainersTemplate = (args) => ({
287
+ components: {
288
+ BmsFloatingWindow,
289
+ },
290
+ setup() {
291
+ return { args };
292
+ },
293
+ template: `
294
+ <div id="foreign-container" style="width: 80%; height: 80%; background-color: green; padding: 2em; box-sizing: border-box;">
295
+ <p style="color: white; margin: 0 0 1em;">Container interne - id="foreign-container"</p>
296
+ </div>
297
+ <p style="color: white; margin: 0 0 1em;">Container externe - id="outer-container"</p>
298
+ <div id="inner-container" style="width: 80%; height: 80%; background-color: #f093fb; padding: 2em; box-sizing: border-box; ">
299
+ <p style="color: white; margin: 0 0 1em;">Container interne - id="inner-container"</p>
300
+ <BmsFloatingWindow v-bind="args">
301
+ <p style="padding: 1em;">Cliquez sur expand pour voir la fenêtre s'agrandir sur le container <strong>interne</strong>.</p>
302
+ <p style="padding: 0 1em;">La prop <code>expandTarget="#inner-container"</code> définit la cible.</p>
303
+ </BmsFloatingWindow>
304
+ </div>
305
+ `,
306
+ });
307
+
308
+ export const ForeignContainers = ForeignContainersTemplate.bind({});
309
+ ForeignContainers.args = {
310
+ title: 'Expand vers outer foreign',
311
+ modelValue: true,
312
+ expandable: true,
313
+ expandTarget: '#foreign-container',
314
+ defaultPlacement: 'top-left',
315
+ };
316
+ ForeignContainers.parameters = {
317
+ docs: {
318
+ description: {
319
+ story:
320
+ "Démontre expandTarget : la fenêtre s'agrandit sur le container extérieur à son arbre (violet).",
321
+ },
322
+ },
109
323
  };