@ouestfrance/sipa-bms-ui 8.15.0 → 8.16.0

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.15.0",
3
+ "version": "8.16.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -30,47 +30,47 @@
30
30
  "url": "https://gitlab.ouest-france.fr/sipa-ouest-france/platform/platform-library-vuejs-bms.git"
31
31
  },
32
32
  "devDependencies": {
33
- "@chromatic-com/storybook": "^4.0.0",
33
+ "@chromatic-com/storybook": "^4.1.2",
34
34
  "@codemirror/lang-html": "6.4.11",
35
35
  "@codemirror/lang-json": "6.0.2",
36
36
  "@commitlint/cli": "20.1.0",
37
37
  "@commitlint/config-conventional": "20.0.0",
38
38
  "@formkit/vue": "1.6.9",
39
39
  "@mdx-js/react": "3.1.1",
40
- "@storybook/addon-docs": "9.1.15",
41
- "@storybook/addon-links": "9.1.15",
42
- "@storybook/vue3-vite": "9.1.15",
43
- "@types/lodash": "4.17.20",
40
+ "@storybook/addon-docs": "10.0.8",
41
+ "@storybook/addon-links": "10.0.8",
42
+ "@storybook/vue3-vite": "10.0.8",
43
+ "@types/lodash": "4.17.21",
44
44
  "@types/uuid": "11.0.0",
45
- "@vitejs/plugin-vue": "6.0.1",
45
+ "@vitejs/plugin-vue": "6.0.2",
46
46
  "@vue/test-utils": "2.4.6",
47
47
  "@vueuse/core": "13.9.0",
48
48
  "@vueuse/motion": "^3.0.0",
49
- "axios": "1.12.2",
49
+ "axios": "1.13.2",
50
50
  "blob-util": "^2.0.2",
51
- "chromatic": "13.3.2",
51
+ "chromatic": "13.3.4",
52
52
  "codemirror": "6.0.2",
53
53
  "cors": "^2.8.5",
54
54
  "cross-env": "^10.0.0",
55
55
  "cy2": "^4.0.0",
56
- "cypress": "15.5.0",
56
+ "cypress": "15.7.0",
57
57
  "express": "^5.0.0",
58
58
  "husky": "9.1.7",
59
- "jsdom": "27.0.1",
59
+ "jsdom": "27.2.0",
60
60
  "keycloak-js": "26.1.2",
61
- "lint-staged": "16.2.6",
61
+ "lint-staged": "16.2.7",
62
62
  "lodash": "4.17.21",
63
- "lucide-vue-next": "0.548.0",
63
+ "lucide-vue-next": "0.554.0",
64
64
  "msw-storybook-addon": "^2.0.3",
65
65
  "normalize.css": "8.0.1",
66
66
  "path": "0.12.7",
67
67
  "prettier": "3.6.2",
68
- "sass": "1.93.2",
69
- "semantic-release": "25.0.1",
70
- "start-server-and-test": "2.1.2",
71
- "storybook": "9.1.15",
72
- "storybook-addon-pseudo-states": "9.1.15",
73
- "storybook-vue3-router": "^6.0.2",
68
+ "sass": "1.94.2",
69
+ "semantic-release": "25.0.2",
70
+ "start-server-and-test": "2.1.3",
71
+ "storybook": "10.0.8",
72
+ "storybook-addon-pseudo-states": "10.0.8",
73
+ "storybook-vue3-router": "^7.0.0",
74
74
  "typescript": "5.2.2",
75
75
  "uuid": "13.0.0",
76
76
  "vite": "^7.1.12",
@@ -79,11 +79,11 @@
79
79
  "vite-plugin-pages": "0.33.1",
80
80
  "vite-svg-loader": "5.1.0",
81
81
  "vitest": "3.2.4",
82
- "vue": "3.5.22",
82
+ "vue": "3.5.24",
83
83
  "vue-codemirror": "6.1.1",
84
84
  "vue-loader": "17.4.2",
85
85
  "vue-router": "4.6.3",
86
- "vue-tsc": "3.1.1"
86
+ "vue-tsc": "3.1.5"
87
87
  },
88
88
  "files": [
89
89
  "dist",
@@ -157,7 +157,9 @@ const optionsLabelValue = computed(() =>
157
157
  );
158
158
 
159
159
  const filteredOptions = computed(() =>
160
- optionsLabelValue.value.filter((o) => searchString(o.label, searching.value)),
160
+ optionsLabelValue.value
161
+ .filter((o) => !modelValue.value?.includes(o.value))
162
+ .filter((o) => searchString(o.label, searching.value)),
161
163
  );
162
164
  </script>
163
165
 
@@ -174,6 +176,8 @@ const filteredOptions = computed(() =>
174
176
  border: none;
175
177
  background-color: transparent;
176
178
  flex-grow: 1;
179
+ flex-basis: 40px;
180
+ width: 100%;
177
181
  }
178
182
  }
179
183
  .icon-container {
@@ -13,7 +13,6 @@
13
13
  :placeholder="placeholder"
14
14
  :small="small"
15
15
  :canAddNewOption="false"
16
- :loading="loading"
17
16
  @input="onInput"
18
17
  @select="(option: any) => emits('select', option)"
19
18
  >
@@ -44,11 +43,12 @@
44
43
  </template>
45
44
 
46
45
  <script setup lang="ts">
47
- import { computed, onMounted, ref, watch } from 'vue';
46
+ import { computed, onMounted, ref, useTemplateRef } from 'vue';
48
47
  import RawAutocomplete from './RawAutocomplete.vue';
49
48
  import { InputOption } from '@/models';
50
49
  import { FieldComponentProps } from '@/plugins/field/field-component.model';
51
50
  import BmsLoader from '../feedback/BmsLoader.vue';
51
+ import { useDebounceFn } from '@vueuse/core';
52
52
 
53
53
  export interface Props extends FieldComponentProps {
54
54
  url?: string;
@@ -88,6 +88,11 @@ const emits = defineEmits<{
88
88
  select: [option: InputOption];
89
89
  }>();
90
90
 
91
+ const debouncedLoadData = useDebounceFn(
92
+ (search?: string) => loadData(search ?? ''),
93
+ 500,
94
+ );
95
+
91
96
  const options = ref<InputOption[]>([]);
92
97
  const loading = ref(true);
93
98
  const controller = ref<AbortController>();
@@ -113,7 +118,12 @@ const loadData = async (search: string) => {
113
118
 
114
119
  onMounted(() => loadData(''));
115
120
  const onInput = (e: InputEvent) => {
116
- loadData((e.target as HTMLInputElement)?.value);
121
+ const search = (e.target as HTMLInputElement)?.value;
122
+ if (search?.length > 2) {
123
+ debouncedLoadData(search);
124
+ } else {
125
+ options.value = [];
126
+ }
117
127
  };
118
128
 
119
129
  const currentOptionIcon = computed(() => {
@@ -0,0 +1,109 @@
1
+ import BmsFloatingWindow from '@/components/layout/BmsFloatingWindow.vue';
2
+ import { vueRouter } from 'storybook-vue3-router';
3
+
4
+ export default {
5
+ title: 'Composants/layout/FloatingWindow',
6
+ component: BmsFloatingWindow,
7
+ };
8
+
9
+ const Template = (args) => ({
10
+ components: {
11
+ BmsFloatingWindow,
12
+ },
13
+ setup() {
14
+ return { args };
15
+ },
16
+ template: `
17
+ <div style="width: ${args.width}; height: ${args.height}; background: purple; position:relative">
18
+ <BmsFloatingWindow v-bind="args">
19
+ ${args?.content}
20
+ </BmsFloatingWindow>
21
+ </div>
22
+ `,
23
+ });
24
+
25
+ export const Default = Template.bind({});
26
+ Default.args = {
27
+ width: '200px',
28
+ height: '200px',
29
+ title: 'My title',
30
+ modelValue: true,
31
+ };
32
+
33
+ export const Large = Template.bind({});
34
+ Large.args = {
35
+ width: '1200px',
36
+ height: '100vh',
37
+ title: 'My title',
38
+ modelValue: true,
39
+ };
40
+
41
+ export const Closed = Template.bind({});
42
+ Closed.args = {
43
+ width: '1200px',
44
+ height: '100vh',
45
+ title: 'My title',
46
+ modelValue: false,
47
+ };
48
+
49
+ export const Overflow = Template.bind({});
50
+ Overflow.args = {
51
+ width: '1200px',
52
+ height: '100vh',
53
+ title: 'Cyrano de Bergerac',
54
+ modelValue: true,
55
+ content: `Ah ! non ! c’est un peu court, jeune homme !
56
+ On pouvait dire… Oh ! Dieu ! … bien des choses en somme…
57
+ En variant le ton, – par exemple, tenez :
58
+ Agressif : « Moi, monsieur, si j’avais un tel nez,
59
+ Il faudrait sur-le-champ que je me l’amputasse ! »
60
+ Amical : « Mais il doit tremper dans votre tasse
61
+ Pour boire, faites-vous fabriquer un hanap ! »
62
+ Descriptif : « C’est un roc ! … c’est un pic ! … c’est un cap !
63
+ Que dis-je, c’est un cap ? … C’est une péninsule ! »
64
+ Curieux : « De quoi sert cette oblongue capsule ?
65
+ D’écritoire, monsieur, ou de boîte à ciseaux ? »
66
+ Gracieux : « Aimez-vous à ce point les oiseaux
67
+ Que paternellement vous vous préoccupâtes
68
+ De tendre ce perchoir à leurs petites pattes ? »
69
+ Truculent : « Ça, monsieur, lorsque vous pétunez,
70
+ La vapeur du tabac vous sort-elle du nez
71
+ Sans qu’un voisin ne crie au feu de cheminée ? »
72
+ Prévenant : « Gardez-vous, votre tête entraînée
73
+ Par ce poids, de tomber en avant sur le sol ! »
74
+ Tendre : « Faites-lui faire un petit parasol
75
+ De peur que sa couleur au soleil ne se fane ! »
76
+ Pédant : « L’animal seul, monsieur, qu’Aristophane
77
+ Appelle Hippocampéléphantocamélos
78
+ Dut avoir sous le front tant de chair sur tant d’os ! »
79
+ Cavalier : « Quoi, l’ami, ce croc est à la mode ?
80
+ Pour pendre son chapeau, c’est vraiment très commode ! »
81
+ Emphatique : « Aucun vent ne peut, nez magistral,
82
+ T’enrhumer tout entier, excepté le mistral ! »
83
+ Dramatique : « C’est la Mer Rouge quand il saigne ! »
84
+ Admiratif : « Pour un parfumeur, quelle enseigne ! »
85
+ Lyrique : « Est-ce une conque, êtes-vous un triton ? »
86
+ Naïf : « Ce monument, quand le visite-t-on ? »
87
+ Respectueux : « Souffrez, monsieur, qu’on vous salue,
88
+ C’est là ce qui s’appelle avoir pignon sur rue ! »
89
+ Campagnard : « Hé, ardé ! C’est-y un nez ? Nanain !
90
+ C’est queuqu’navet géant ou ben queuqu’melon nain ! »
91
+ Militaire : « Pointez contre cavalerie ! »
92
+ Pratique : « Voulez-vous le mettre en loterie ?
93
+ Assurément, monsieur, ce sera le gros lot ! »
94
+ Enfin parodiant Pyrame en un sanglot :
95
+ « Le voilà donc ce nez qui des traits de son maître
96
+ A détruit l’harmonie ! Il en rougit, le traître ! »
97
+ – Voilà ce qu’à peu près, mon cher, vous m’auriez dit
98
+ Si vous aviez un peu de lettres et d’esprit
99
+ Mais d’esprit, ô le plus lamentable des êtres,
100
+ Vous n’en eûtes jamais un atome, et de lettres
101
+ Vous n’avez que les trois qui forment le mot : sot !
102
+ Eussiez-vous eu, d’ailleurs, l’invention qu’il faut
103
+ Pour pouvoir là, devant ces nobles galeries,
104
+ me servir toutes ces folles plaisanteries,
105
+ Que vous n’en eussiez pas articulé le quart
106
+ De la moitié du commencement d’une, car
107
+ Je me les sers moi-même, avec assez de verve,
108
+ Mais je ne permets pas qu’un autre me les serve.`,
109
+ };
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <div class="floating-window-wrapper">
3
+ <div class="floating-window" v-show="open">
4
+ <div class="floating-window__header">
5
+ <h2>{{ title }}</h2>
6
+ <div class="floating-window__header__buttons">
7
+ <BmsIconButton :tooltip-text="'Maximiser la fenêtre'" disabled>
8
+ <Maximize2 />
9
+ </BmsIconButton>
10
+ <BmsIconButton @click.prevent.stop="open = false">
11
+ <X />
12
+ </BmsIconButton>
13
+ </div>
14
+ </div>
15
+ <div class="floating-window__content">
16
+ <slot />
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { Maximize2, X } from 'lucide-vue-next';
24
+ import BmsIconButton from '../button/BmsIconButton.vue';
25
+
26
+ defineProps<{ title: string }>();
27
+
28
+ const open = defineModel<boolean>({
29
+ default: false,
30
+ });
31
+ </script>
32
+
33
+ <style scoped lang="scss">
34
+ .floating-window-wrapper {
35
+ --breakpoint: 900px;
36
+ container-name: floating-window;
37
+ container-type: inline-size;
38
+ width: 100%;
39
+ height: 100%;
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ pointer-events: none;
47
+
48
+ .floating-window {
49
+ background-color: var(--bms-white);
50
+ height: 100%;
51
+ width: 100%;
52
+ border-radius: var(--bms-border-radius-large);
53
+ border: 1px solid var(--bms-grey-10);
54
+ pointer-events: all;
55
+ display: grid;
56
+ grid-template-rows: auto 1fr;
57
+ box-shadow: 0px 8px 8px 0px rgba(0, 0, 0, 0.25);
58
+
59
+ &__header {
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ border-bottom: 1px solid var(--bms-grey-10);
64
+ padding: 1em;
65
+ h2 {
66
+ margin: 0;
67
+ }
68
+ }
69
+ &__content {
70
+ overflow-y: auto;
71
+ height: auto;
72
+ scrollbar-width: thin;
73
+ }
74
+ }
75
+ }
76
+
77
+ @container floating-window (min-width:900px) {
78
+ .floating-window {
79
+ max-height: 300px;
80
+ max-width: 80%;
81
+ }
82
+ }
83
+ </style>
@@ -233,7 +233,7 @@ onUnmounted(() => {
233
233
  display: flex;
234
234
  align-items: center;
235
235
  justify-content: center;
236
- z-index: 10;
236
+ z-index: var(--bms-z-index-fixed);
237
237
 
238
238
  &.Default {
239
239
  --modal-header-border-size: 1px;
@@ -0,0 +1,155 @@
1
+ import BmsSplitWindow from './BmsSplitWindow.vue';
2
+
3
+ export default {
4
+ title: 'Composants/layout/SplitWindow',
5
+ component: BmsSplitWindow,
6
+ argTypes: {
7
+ splitOrientation: {
8
+ control: { type: 'select' },
9
+ options: ['horizontal', 'vertical'],
10
+ defaultValue: 'vertical',
11
+ },
12
+ min: {
13
+ control: {
14
+ type: 'number',
15
+ min: 0,
16
+ max: 100,
17
+ },
18
+ defaultValue: 20,
19
+ },
20
+ max: {
21
+ control: {
22
+ type: 'number',
23
+ min: 0,
24
+ max: 100,
25
+ },
26
+ defaultValue: 80,
27
+ },
28
+ primary: {
29
+ control: { type: 'select' },
30
+ options: ['first', 'second'],
31
+ defaultValue: 'first',
32
+ },
33
+ collapsed: {
34
+ control: { type: 'boolean' },
35
+ defaultValue: false,
36
+ },
37
+ ariaLabel: {
38
+ control: { type: 'text' },
39
+ },
40
+ split: {
41
+ control: {
42
+ type: 'number',
43
+ min: 0,
44
+ max: 100,
45
+ },
46
+ },
47
+ },
48
+ };
49
+
50
+ const Template = (args) => ({
51
+ components: { BmsSplitWindow },
52
+ setup() {
53
+ return { args };
54
+ },
55
+ template: `
56
+ <div style="height: 400px; border: 1px solid #ccc;">
57
+ <BmsSplitWindow
58
+ v-model="args.split"
59
+ :split-orientation="args.splitOrientation"
60
+ :min="args.min"
61
+ :max="args.max"
62
+ :primary="args.primary"
63
+ :collapsed="args.collapsed"
64
+ :aria-label="args.ariaLabel"
65
+ @update:collapsed="(collapsed) => { args.collapsed = collapsed }"
66
+ >
67
+ <template #first>
68
+ <div style="display:flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: lightblue">
69
+ <p>First</p>
70
+ </div>
71
+ </template>
72
+ <template #second>
73
+ <div style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: lightpink">
74
+ <p>Second</p>
75
+ </div>
76
+ </template>
77
+ </BmsSplitWindow>
78
+ </div>
79
+ `,
80
+ });
81
+
82
+ export const Default = Template.bind({});
83
+ // More on args: https://storybook.js.org/docs/vue/writing-stories/args
84
+ Default.args = {
85
+ splitOrientation: 'vertical',
86
+ min: 20,
87
+ max: 80,
88
+ primary: 'first',
89
+ ariaLabel: 'Split window divider',
90
+ split: 50,
91
+ collapsed: false,
92
+ };
93
+
94
+ export const MultiSplit = (args) => ({
95
+ components: { BmsSplitWindow },
96
+ setup() {
97
+ return { args };
98
+ },
99
+ template: `
100
+ <div style="height: 400px; border: 1px solid #ccc;">
101
+ <BmsSplitWindow
102
+ v-model="args.split"
103
+ :split-orientation="'vertical'"
104
+ :min="args.min"
105
+ :max="args.max"
106
+ :primary="'first'"
107
+ :collapsed="args.collapsed"
108
+ :aria-label="args.ariaLabel"
109
+ >
110
+ <template #first>
111
+ <BmsSplitWindow
112
+ v-model="args.split2"
113
+ :split-orientation="'horizontal'"
114
+ :min="args.min"
115
+ :max="args.max"
116
+ :primary="'first'"
117
+ :aria-label="args.ariaLabel"
118
+ style="height: 100%;"
119
+ >
120
+ <template #first>
121
+ <div style="display:flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: lightgreen">
122
+ <p>Multi First</p>
123
+ </div>
124
+ </template>
125
+ <template #second>
126
+ <div style="display:flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: lightyellow">
127
+ <p>Multi Second</p>
128
+ </div>
129
+ </template>
130
+ </BmsSplitWindow>
131
+ </template>
132
+ <template #second>
133
+ <div style="display:flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: lightcoral">
134
+ <p>Third Split</p>
135
+ </div>
136
+ </template>
137
+ </BmsSplitWindow>
138
+ </div>
139
+ `,
140
+ data() {
141
+ return {
142
+ split2: 50,
143
+ };
144
+ },
145
+ });
146
+
147
+ MultiSplit.args = {
148
+ splitOrientation: 'vertical',
149
+ split: 30,
150
+ min: 20,
151
+ max: 80,
152
+ primary: 'first',
153
+ collapsed: false,
154
+ ariaLabel: 'Multi split window divider',
155
+ };