@ouestfrance/sipa-bms-ui 8.3.0 → 8.4.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.3.0",
3
+ "version": "8.4.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -36,54 +36,53 @@
36
36
  "@commitlint/config-conventional": "19.8.1",
37
37
  "@formkit/vue": "1.6.9",
38
38
  "@mdx-js/react": "3.1.0",
39
- "@storybook/addon-actions": "^8.6.14",
40
- "@storybook/addon-docs": "9.0.17",
41
- "@storybook/addon-links": "9.0.17",
42
- "@storybook/vue3-vite": "9.0.17",
39
+ "@storybook/addon-docs": "9.1.2",
40
+ "@storybook/addon-links": "9.1.2",
41
+ "@storybook/vue3-vite": "9.1.2",
43
42
  "@types/lodash": "4.17.20",
44
43
  "@types/uuid": "10.0.0",
45
- "@vitejs/plugin-vue": "6.0.0",
44
+ "@vitejs/plugin-vue": "6.0.1",
46
45
  "@vue/test-utils": "2.4.6",
47
- "@vueuse/core": "13.5.0",
46
+ "@vueuse/core": "13.6.0",
48
47
  "@vueuse/motion": "^3.0.0",
49
- "axios": "1.10.0",
48
+ "axios": "1.11.0",
50
49
  "blob-util": "^2.0.2",
51
- "chromatic": "13.1.2",
50
+ "chromatic": "13.1.3",
52
51
  "codemirror": "6.0.2",
53
52
  "cors": "^2.8.5",
54
53
  "cross-env": "^10.0.0",
55
- "cy2": "4.0.9",
56
- "cypress": "14.5.2",
54
+ "cy2": "^4.0.0",
55
+ "cypress": "14.5.3",
57
56
  "express": "^5.0.0",
58
57
  "husky": "9.1.7",
59
58
  "jsdom": "26.1.0",
60
59
  "keycloak-js": "26.1.2",
61
- "lint-staged": "16.1.2",
60
+ "lint-staged": "16.1.3",
62
61
  "lodash": "4.17.21",
63
- "lucide-vue-next": "0.525.0",
62
+ "lucide-vue-next": "0.536.0",
64
63
  "msw-storybook-addon": "^2.0.3",
65
64
  "normalize.css": "8.0.1",
66
65
  "path": "0.12.7",
67
66
  "prettier": "3.6.2",
68
67
  "sass": "1.89.2",
69
68
  "semantic-release": "24.2.7",
70
- "start-server-and-test": "2.0.12",
71
- "storybook": "9.0.17",
72
- "storybook-addon-pseudo-states": "9.0.17",
73
- "storybook-vue3-router": "^5.0.0",
69
+ "start-server-and-test": "2.0.13",
70
+ "storybook": "9.1.2",
71
+ "storybook-addon-pseudo-states": "9.1.2",
72
+ "storybook-vue3-router": "^6.0.2",
74
73
  "typescript": "5.2.2",
75
74
  "uuid": "11.1.0",
76
- "vite": "7.0.5",
75
+ "vite": "7.0.6",
77
76
  "vite-plugin-dts": "^4.1.0",
78
77
  "vite-plugin-mkcert": "1.17.8",
79
78
  "vite-plugin-pages": "0.33.1",
80
79
  "vite-svg-loader": "5.1.0",
81
80
  "vitest": "3.2.4",
82
- "vue": "3.5.17",
81
+ "vue": "3.5.18",
83
82
  "vue-codemirror": "6.1.1",
84
83
  "vue-loader": "17.4.2",
85
84
  "vue-router": "4.5.1",
86
- "vue-tsc": "3.0.3"
85
+ "vue-tsc": "3.0.5"
87
86
  },
88
87
  "files": [
89
88
  "dist",
@@ -119,8 +118,5 @@
119
118
  },
120
119
  "dependencies": {
121
120
  "msw": "^2.3.1"
122
- },
123
- "overrides": {
124
- "storybook": "$storybook"
125
121
  }
126
122
  }
@@ -27,10 +27,11 @@ const factory = (props?: PropsAndModel) => {
27
27
  });
28
28
 
29
29
  const inputElement = wrapper.get('input');
30
- return { wrapper, inputElement };
30
+ const menu = wrapper.get('[data-testid="autocomplete-menu"]');
31
+ return { wrapper, inputElement, menu };
31
32
  };
32
33
 
33
- describe('BmsAutocomplete', () => {
34
+ describe('RawAutocomplete', () => {
34
35
  it('should mount component correctly', async () => {
35
36
  const { wrapper } = factory();
36
37
 
@@ -40,8 +41,7 @@ describe('BmsAutocomplete', () => {
40
41
  });
41
42
 
42
43
  it('should show options on click', async () => {
43
- const { wrapper, inputElement } = factory();
44
- const menu = wrapper.get('[data-testid="autocomplete-menu"]');
44
+ const { wrapper, inputElement, menu } = factory();
45
45
 
46
46
  expect(menu.isVisible()).toBeFalsy();
47
47
  await inputElement.trigger('input');
@@ -58,8 +58,7 @@ describe('BmsAutocomplete', () => {
58
58
  modelValue: 'i',
59
59
  } as PropsAndModel);
60
60
  expect(inputElement.element.value).toStrictEqual('titi');
61
- wrapper.setProps({ modelValue: 'u' });
62
- await nextTick();
61
+ await wrapper.setProps({ modelValue: 'u' });
63
62
  expect(inputElement.element.value).toStrictEqual('tutu');
64
63
  });
65
64
 
@@ -68,8 +67,7 @@ describe('BmsAutocomplete', () => {
68
67
  modelValue: 'i',
69
68
  } as PropsAndModel);
70
69
  expect(inputElement.element.value).toStrictEqual('titi');
71
- wrapper.setProps({ modelValue: 'not_found' });
72
- await nextTick();
70
+ await wrapper.setProps({ modelValue: 'not_found' });
73
71
  expect(inputElement.element.value).toStrictEqual('');
74
72
  });
75
73
 
@@ -78,11 +76,33 @@ describe('BmsAutocomplete', () => {
78
76
  modelValue: 'i',
79
77
  } as PropsAndModel);
80
78
  expect(inputElement.element.value).toStrictEqual('titi');
81
- wrapper.setProps({ modelValue: undefined });
82
- await nextTick();
79
+ await wrapper.setProps({ modelValue: undefined });
83
80
  expect(inputElement.element.value).toStrictEqual('titi');
84
81
  });
85
82
 
83
+ it('should emit modelValue if clicks on option', async () => {
84
+ const { wrapper, inputElement, menu } = factory({
85
+ modelValue: null,
86
+ } as PropsAndModel);
87
+ await inputElement.trigger('click');
88
+ await menu.find('[data-testid="i"]').trigger('click');
89
+ expect(wrapper.emitted()['select']).toStrictEqual([
90
+ [{ label: 'titi', value: 'i' }],
91
+ ]);
92
+ expect(wrapper.emitted()['update:modelValue']).toStrictEqual([['i']]);
93
+ });
94
+
95
+ it('should reset modelValue if user removes text input', async () => {
96
+ const { wrapper, inputElement } = factory({
97
+ modelValue: 'i',
98
+ } as PropsAndModel);
99
+ expect(inputElement.element.value).toStrictEqual('titi');
100
+ inputElement.element.value = '';
101
+ await inputElement.trigger('input');
102
+ expect(inputElement.element.value).toStrictEqual('');
103
+ expect(wrapper.emitted()['update:modelValue']).toStrictEqual([[null]]);
104
+ });
105
+
86
106
  it('should emit event on option selection', async () => {
87
107
  const { wrapper, inputElement } = factory();
88
108
  const titi = wrapper.get('[data-testid="i"]');
@@ -150,6 +150,9 @@ watch(
150
150
  );
151
151
  const onInput = () => {
152
152
  isInputFocused.value = true;
153
+ if (inputText.value === '') {
154
+ clearInput();
155
+ }
153
156
  };
154
157
 
155
158
  const setFocus = () => {
@@ -1,8 +1,17 @@
1
1
  import BmsCard from '@/components/layout/BmsCard.vue';
2
+ import { vueRouter } from 'storybook-vue3-router';
2
3
 
3
4
  export default {
4
5
  title: 'Composants/layout/Card',
5
6
  component: BmsCard,
7
+ decorators: [
8
+ vueRouter([
9
+ {
10
+ name: 'firstRoute',
11
+ path: '/toto',
12
+ },
13
+ ]),
14
+ ],
6
15
  };
7
16
 
8
17
  const Template = (args) => ({
@@ -21,6 +30,11 @@ const Template = (args) => ({
21
30
  `,
22
31
  });
23
32
 
33
+ export const WithLink = Template.bind({});
34
+ WithLink.args = {
35
+ to: '/toto',
36
+ };
37
+
24
38
  export const Default = Template.bind({});
25
39
  Default.args = {};
26
40
 
@@ -7,7 +7,12 @@
7
7
  :class="{ animated, hasAction: !!$slots.action }"
8
8
  >
9
9
  <div class="card__body__content">
10
- <slot></slot>
10
+ <BmsLink class="card__body__content__link" v-if="to" :to="to">
11
+ <slot></slot>
12
+ </BmsLink>
13
+ <template v-else>
14
+ <slot></slot>
15
+ </template>
11
16
  </div>
12
17
  <div class="card__body__action" v-if="!!$slots.action">
13
18
  <slot name="action"></slot>
@@ -19,11 +24,13 @@
19
24
  <script lang="ts" setup>
20
25
  import { StatusType } from '@/models';
21
26
  import { ref } from 'vue';
27
+ import { RouteLocationRaw } from 'vue-router';
28
+ import BmsLink from '../navigation/BmsLink.vue';
22
29
 
23
30
  const props = withDefaults(
24
31
  defineProps<{
25
32
  animated?: boolean;
26
-
33
+ to?: RouteLocationRaw;
27
34
  type?: StatusType;
28
35
  }>(),
29
36
  {
@@ -105,6 +112,11 @@ window.addEventListener('mousemove', onMouseMove);
105
112
  background-color: var(--summary-card-background-color);
106
113
  backdrop-filter: blur(40px);
107
114
  color: var(--summary-card-color);
115
+
116
+ &__link {
117
+ color: var(--summary-card-color);
118
+ text-decoration: none;
119
+ }
108
120
  }
109
121
 
110
122
  &__action {
@@ -0,0 +1,113 @@
1
+ import BmsFixedMenu from '@/components/navigation/BmsFixedMenu.vue';
2
+ import { Activity, ArrowBigDown, Globe, User } from 'lucide-vue-next';
3
+ import { vueRouter } from 'storybook-vue3-router';
4
+
5
+ export default {
6
+ title: 'Composants/navigation/FixedMenu',
7
+ component: BmsFixedMenu,
8
+ argTypes: {
9
+ items: {},
10
+ },
11
+ decorators: [
12
+ vueRouter([
13
+ {
14
+ name: 'home',
15
+ path: '/',
16
+ },
17
+ {
18
+ name: 'activity',
19
+ path: '/activity',
20
+ },
21
+ {
22
+ name: 'users',
23
+ path: '/users',
24
+ },
25
+ {
26
+ name: 'thirdRoute',
27
+ path: '/tutu',
28
+ },
29
+ ]),
30
+ ],
31
+ };
32
+
33
+ const Template = (args) => ({
34
+ components: {
35
+ BmsFixedMenu,
36
+ },
37
+ setup() {
38
+ return { args };
39
+ },
40
+ template: `
41
+ <BmsFixedMenu v-bind="args">
42
+ <template v-if="args.additionalSlot" #additional>Additional</template>
43
+ <template v-if="args.footerSlot" #footer>Footer</template>
44
+ </BmsFixedMenu>
45
+ `,
46
+ });
47
+
48
+ export const Default = Template.bind({});
49
+ Default.args = {
50
+ items: [
51
+ {
52
+ label: 'Activity',
53
+ link: '/activity',
54
+ icon: Activity,
55
+ },
56
+ {
57
+ label: 'Users',
58
+ link: '/users',
59
+ icon: User,
60
+ },
61
+ {
62
+ label: 'Parent',
63
+ items: [
64
+ {
65
+ label: 'tata',
66
+ link: '/tata',
67
+ icon: Globe,
68
+ },
69
+ {
70
+ label: 'tutu',
71
+ link: '/tutu',
72
+ },
73
+ ],
74
+ },
75
+ ],
76
+ activeLink: '/',
77
+ };
78
+
79
+ export const WithActiveLink = Template.bind({});
80
+ WithActiveLink.args = {
81
+ items: [
82
+ {
83
+ label: 'Activity',
84
+ link: '/activity',
85
+ icon: Activity,
86
+ },
87
+ {
88
+ label: 'Users',
89
+ link: '/users',
90
+ icon: User,
91
+ },
92
+ ],
93
+ activeLink: '/activity',
94
+ };
95
+
96
+ export const WithSlots = Template.bind({});
97
+ WithSlots.args = {
98
+ items: [
99
+ {
100
+ label: 'Activity',
101
+ link: '/activity',
102
+ icon: Activity,
103
+ },
104
+ {
105
+ label: 'Users',
106
+ link: '/users',
107
+ icon: User,
108
+ },
109
+ ],
110
+ activeLink: '/users',
111
+ additionalSlot: true,
112
+ footerSlot: true,
113
+ };
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class="menu__container">
3
+ <div class="menu__top">
4
+ <slot name="top"></slot>
5
+ </div>
6
+
7
+ <BmsMenuNav :items="items" :active-link="activeLink">
8
+ <template #additional>
9
+ <slot name="additional"></slot>
10
+ </template>
11
+ </BmsMenuNav>
12
+
13
+ <div class="menu__copyright">
14
+ <slot name="footer">
15
+ <p class="menu__copyright-madein">Made with ♥ by BMS</p>
16
+ </slot>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ import type { RouteLocationNormalizedLoaded } from 'vue-router';
23
+ import { MenuItem, ParentMenuItem } from '@/models';
24
+ import BmsMenuNav from './BmsMenuNav.vue';
25
+
26
+ interface Props {
27
+ items: (MenuItem | ParentMenuItem)[];
28
+ activeLink: RouteLocationNormalizedLoaded;
29
+ }
30
+ defineProps<Props>();
31
+ </script>
32
+
33
+ <style lang="scss" scoped>
34
+ .menu {
35
+ &__container {
36
+ display: flex;
37
+ flex-direction: column;
38
+ height: 100%;
39
+ width: 100%;
40
+ background: var(--bms-white);
41
+ border-right: 1px solid var(--bms-grey-10);
42
+ padding: 0;
43
+
44
+ nav {
45
+ scrollbar-width: none;
46
+ padding: 0;
47
+ overflow-x: hidden;
48
+ max-width: calc(var(--bms-menu-width) + 5px);
49
+
50
+ :deep(ul) {
51
+ li {
52
+ width: var(--bms-menu-width);
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ &__top {
59
+ margin: 0.5em;
60
+ }
61
+
62
+ &__copyright {
63
+ display: flex;
64
+ justify-content: center;
65
+ align-items: center;
66
+ min-height: 80px;
67
+ overflow-x: hidden;
68
+ }
69
+ }
70
+ </style>
@@ -92,7 +92,11 @@ const isLinkRecognized = (link: RouteLocationRaw): boolean => {
92
92
  if ((link as RouteLocationPathRaw).path) {
93
93
  return (link as RouteLocationPathRaw).path === props.activeLink.path;
94
94
  }
95
- return link === props.activeLink.name || link === props.activeLink.path;
95
+ return (
96
+ link === props.activeLink.name ||
97
+ link === props.activeLink.path ||
98
+ link === props.activeLink
99
+ );
96
100
  };
97
101
 
98
102
  const isItemActive = (menuItem: MenuItem | ParentMenuItem): boolean => {
@@ -28,9 +28,10 @@
28
28
  :key="cell.key"
29
29
  >
30
30
  <div v-if="isChildElement" class="bms-table__row__cell--child-element">
31
- <div class="bms-table__row__cell--child-element__icon">
32
- <CornerDownRight v-if="index === 0" />
33
- </div>
31
+ <CornerDownRight
32
+ v-if="index === 0"
33
+ class="bms-table__row__cell--child-element__icon"
34
+ />
34
35
  <slot
35
36
  :name="cell.key"
36
37
  :row="item.childElement"
@@ -141,7 +142,6 @@ const getAlignClass = (header: TableHeader) => {
141
142
  align-items: flex-end;
142
143
  &__icon {
143
144
  display: flex;
144
- min-width: 1em;
145
145
  margin-right: 1em;
146
146
  }
147
147
  }
package/src/index.ts CHANGED
@@ -47,6 +47,7 @@ import BmsStepper from './components/layout/BmsStepper.vue';
47
47
 
48
48
  import BmsBackButton from './components/navigation/BmsBackButton.vue';
49
49
  import BmsBreadcrumb from './components/navigation/BmsBreadcrumb.vue';
50
+ import BmsFixedMenu from './components/navigation/BmsFixedMenu.vue';
50
51
  import BmsLink from './components/navigation/BmsLink.vue';
51
52
  import BmsMenu from './components/navigation/BmsMenu.vue';
52
53
  import BmsMenuNav from './components/navigation/BmsMenuNav.vue';
@@ -115,6 +116,7 @@ export const createBmsUi = () => ({
115
116
 
116
117
  app.component('BmsBackButton', BmsBackButton);
117
118
  app.component('BmsBreadcrumb', BmsBreadcrumb);
119
+ app.component('BmsFixedMenu', BmsFixedMenu);
118
120
  app.component('BmsLink', BmsLink);
119
121
  app.component('BmsMenu', BmsMenu);
120
122
  app.component('BmsMenuNav', BmsMenuNav);
@@ -189,6 +191,7 @@ export {
189
191
  BmsStepper,
190
192
  BmsBackButton,
191
193
  BmsBreadcrumb,
194
+ BmsFixedMenu,
192
195
  BmsLink,
193
196
  BmsMenu,
194
197
  BmsMenuNav,
@@ -1,5 +1,9 @@
1
1
  <template>
2
2
  <BmsBackButton :fallback="{ path: '/' }" />
3
+ <br />
4
+ <BmsButton @click="inputLabelValue = '1'">Set value to '1'</BmsButton>
5
+ <br />
6
+ Valeur: {{ inputLabelValue }}
3
7
  <BmsAutocomplete
4
8
  label="Autocomplete avec label/value"
5
9
  :options="optionsLabelValue"
@@ -8,22 +12,20 @@
8
12
  @add-new-option="onAddNewOption"
9
13
  />
10
14
 
11
- Valeur: {{ inputLabelValue }}
12
-
15
+ Valeur: {{ inputValueIcon }}
13
16
  <BmsAutocomplete
14
17
  label="Autocomplete avec icones"
15
18
  :options="optionsIcon"
16
19
  v-model="inputValueIcon"
17
20
  @select="(o) => console.log('select', o.value)"
18
21
  />
19
- Valeur: {{ inputValueIcon }}
20
22
 
23
+ Valeur: {{ inputText }}
21
24
  <BmsAutocomplete
22
25
  label="Autocomplete avec tableau de strings"
23
26
  :options="optionsText"
24
27
  v-model="inputText"
25
28
  />
26
- Valeur: {{ inputText }}
27
29
  </template>
28
30
 
29
31
  <script setup lang="ts">
@@ -31,6 +33,7 @@ import { BmsAutocomplete, BmsBackButton } from '@/index';
31
33
  import { Heart, Cat } from 'lucide-vue-next';
32
34
  import { range } from 'lodash';
33
35
  import { ref } from 'vue';
36
+ import BmsButton from '@/components/button/BmsButton.vue';
34
37
 
35
38
  const optionsLabelValue = ref(
36
39
  range(0, 30).map((i) =>