@ouestfrance/sipa-bms-ui 8.17.0 → 8.19.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.17.0",
3
+ "version": "8.19.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -55,6 +55,7 @@ const open = defineModel<boolean>({
55
55
  display: grid;
56
56
  grid-template-rows: auto 1fr;
57
57
  box-shadow: 0px 8px 8px 0px rgba(0, 0, 0, 0.25);
58
+ z-index: var(--bms-z-index-modal);
58
59
 
59
60
  &__header {
60
61
  display: flex;
@@ -55,6 +55,7 @@ const collapsedLocal = ref(props.collapsed ?? false);
55
55
  watch(
56
56
  () => props.collapsed,
57
57
  (val) => {
58
+ console.log('watch:collapsed', val);
58
59
  collapsedLocal.value = val ?? false;
59
60
  },
60
61
  );
@@ -110,7 +111,9 @@ onBeforeUnmount(() => {
110
111
 
111
112
  function onPointerDown(evt: PointerEvent) {
112
113
  isDragging.value = true;
113
- setCollapsed(false);
114
+ if (collapsedLocal.value) {
115
+ setCollapsed(false);
116
+ }
114
117
  startSplit.value = clampSplit.value;
115
118
  startPosition.value =
116
119
  props.splitOrientation === 'vertical' ? evt.clientX : evt.clientY;
@@ -146,14 +149,14 @@ function onKeyDown(evt: KeyboardEvent) {
146
149
  switch (evt.key) {
147
150
  case 'ArrowLeft':
148
151
  case 'ArrowUp':
149
- if (collapsedLocal.value === true) {
152
+ if (collapsedLocal.value) {
150
153
  setCollapsed(false);
151
154
  }
152
155
  split.value = Math.max(min.value, clampSplit.value - 1);
153
156
  break;
154
157
  case 'ArrowRight':
155
158
  case 'ArrowDown':
156
- if (collapsedLocal.value === true) {
159
+ if (collapsedLocal.value) {
157
160
  setCollapsed(false);
158
161
  }
159
162
  split.value = Math.min(max.value, clampSplit.value + 1);
@@ -162,13 +165,13 @@ function onKeyDown(evt: KeyboardEvent) {
162
165
  setCollapsed(!collapsedLocal.value);
163
166
  break;
164
167
  case 'Home':
165
- if (collapsedLocal.value === true) {
168
+ if (collapsedLocal.value) {
166
169
  setCollapsed(false);
167
170
  }
168
171
  split.value = props.primary === 'first' ? min.value : max.value;
169
172
  break;
170
173
  case 'End':
171
- if (collapsedLocal.value === true) {
174
+ if (collapsedLocal.value) {
172
175
  setCollapsed(false);
173
176
  }
174
177
  split.value = props.primary === 'first' ? max.value : min.value;
@@ -202,17 +205,20 @@ function clamp(value: number, minValue: number, maxValue: number) {
202
205
  <div
203
206
  ref="split-window"
204
207
  class="split-window"
205
- :class="`split-window--${splitOrientation}`"
208
+ :class="[
209
+ `split-window--${splitOrientation}`,
210
+ { 'split-window--dragging': isDragging },
211
+ ]"
206
212
  :style="gridStyle"
207
213
  >
208
214
  <div
209
- class="split-window__first-pane"
215
+ class="split-window__pane split-window__first-pane"
210
216
  :id="primary === 'first' ? primaryId : undefined"
211
217
  >
212
218
  <slot name="first" />
213
219
  </div>
214
220
  <div
215
- class="split-window__separator"
221
+ class="split-window__separator toto"
216
222
  role="separator"
217
223
  tabindex="0"
218
224
  :aria-label="props.ariaLabel || 'Séparateur de volet'"
@@ -225,7 +231,7 @@ function clamp(value: number, minValue: number, maxValue: number) {
225
231
  @keydown="onKeyDown"
226
232
  />
227
233
  <div
228
- class="split-window__second-pane"
234
+ class="split-window__pane split-window__second-pane"
229
235
  :id="primary === 'second' ? primaryId : undefined"
230
236
  >
231
237
  <slot name="second" />
@@ -238,10 +244,17 @@ function clamp(value: number, minValue: number, maxValue: number) {
238
244
  display: grid;
239
245
  width: 100%;
240
246
  height: 100%;
247
+ overflow: hidden;
248
+
249
+ &__pane {
250
+ display: flex;
251
+ max-height: 100%;
252
+ overflow: hidden;
253
+ }
241
254
 
242
255
  &__separator {
243
256
  position: relative;
244
- z-index: 2;
257
+ z-index: var(--bms-z-index-fixed);
245
258
 
246
259
  &:before {
247
260
  content: '';
@@ -286,5 +299,18 @@ function clamp(value: number, minValue: number, maxValue: number) {
286
299
  }
287
300
  }
288
301
  }
302
+
303
+ &--dragging {
304
+ .split-window__separator {
305
+ &:before {
306
+ position: fixed;
307
+ top: 0;
308
+ left: 0;
309
+ width: 100%;
310
+ height: 100%;
311
+ transform: unset;
312
+ }
313
+ }
314
+ }
289
315
  }
290
316
  </style>
@@ -1,4 +1,5 @@
1
1
  import BmsTabs from '@/components/navigation/BmsTabs.vue';
2
+ import { vueRouter } from 'storybook-vue3-router';
2
3
 
3
4
  export default {
4
5
  title: 'Composants/navigation/Tabs',
@@ -6,6 +7,21 @@ export default {
6
7
  argTypes: {
7
8
  items: {},
8
9
  },
10
+ decorators: [
11
+ vueRouter([
12
+ {
13
+ name: 'titi',
14
+ path: '/titi',
15
+ },
16
+ {
17
+ path: '/',
18
+ },
19
+ {
20
+ name: 'toto',
21
+ path: '/toto',
22
+ },
23
+ ]),
24
+ ],
9
25
  };
10
26
 
11
27
  const Template = (args) => ({
@@ -20,11 +36,49 @@ const Template = (args) => ({
20
36
  `,
21
37
  });
22
38
 
23
- export const Primary = Template.bind({});
24
- Primary.args = {
39
+ export const Default = Template.bind({});
40
+ Default.args = {
41
+ title: 'Title',
42
+ tabs: [{ name: 'Titi', id: 'titi' }, { name: 'Toto' }],
43
+ };
44
+ export const WithSelectedTabId = Template.bind({});
45
+ WithSelectedTabId.args = {
46
+ title: 'Title',
47
+ initialTabId: 'Toto',
48
+ tabs: [
49
+ { name: 'Titi', id: 'titi' },
50
+ { name: 'Tata' },
51
+ { name: 'Toto' },
52
+ { name: 'Tutu' },
53
+ ],
54
+ };
55
+
56
+ export const WithRouterEngine = Template.bind({});
57
+ WithRouterEngine.args = {
58
+ title: 'Title',
59
+ initialTabId: 'titi',
60
+ tabs: [
61
+ { name: 'Titi', routeName: 'titi', id: 'titi' },
62
+ { name: 'Toto', routePath: '/toto' },
63
+ ],
64
+ };
65
+
66
+ export const WithError = Template.bind({});
67
+ WithError.args = {
68
+ title: 'Title',
69
+ initialTabId: 'titi',
70
+ tabs: [
71
+ { name: 'Titi', routeName: 'titi', id: 'titi' },
72
+ { name: 'Toto', routePath: '/toto', error: true },
73
+ ],
74
+ };
75
+
76
+ export const WithDisabled = Template.bind({});
77
+ WithDisabled.args = {
25
78
  title: 'Title',
79
+ initialTabId: 'titi',
26
80
  tabs: [
27
- { name: 'Titi', routePath: 'titi' },
28
- { name: 'Toto', routePath: 'toto' },
81
+ { name: 'Titi', routeName: 'titi', id: 'titi' },
82
+ { name: 'Toto', routePath: '/toto', disabled: true },
29
83
  ],
30
84
  };
@@ -1,46 +1,66 @@
1
1
  <template>
2
- <div class="tabs-header">
3
- <div class="tabs-title">
4
- <h3>{{ title }}</h3>
5
- </div>
6
- <UiTab
7
- v-for="tab in tabs"
8
- :key="tab.name"
9
- :tab="tab"
10
- :current-route="currentRoute"
11
- />
12
- </div>
2
+ <UiTabs
3
+ :title="title"
4
+ :tabs="tabs"
5
+ :initial-tab-id="selectedTabId"
6
+ @click="$emits('click', $event)"
7
+ >
8
+ <template v-if="needRouterEngine" #router="{ tab }">
9
+ <router-link class="tab" :to="getTabTarget(tab)">{{
10
+ tab.name
11
+ }}</router-link>
12
+ </template>
13
+ </UiTabs>
13
14
  </template>
14
15
 
15
16
  <script setup lang="ts">
16
- import { useRouter } from 'vue-router';
17
17
  import { Tab } from '@/models/tab.model';
18
- import UiTab from './UiTab.vue';
18
+ import UiTabs from './UiTabs.vue';
19
+ import { computed, ComputedRef, onMounted, ref, watch } from 'vue';
20
+ import { RouteLocation, useRouter } from 'vue-router';
21
+ import { getTabId } from '@/helpers/tab.helper';
19
22
 
20
23
  const { currentRoute } = useRouter();
21
24
 
22
- defineProps<{ title: string; tabs: Tab[] }>();
23
- </script>
25
+ const props = defineProps<{
26
+ title: string;
27
+ tabs: Tab[];
28
+ initialTabId?: string;
29
+ }>();
24
30
 
25
- <style lang="scss" scoped>
26
- .tabs-header {
27
- display: flex;
28
- align-items: center;
29
- border-bottom: 1px solid var(--bms-grey-25);
30
- box-sizing: border-box;
31
+ const getTabTarget = (tab: Tab) => {
32
+ return tab.routePath ? { path: tab.routePath } : { name: tab.routeName };
33
+ };
31
34
 
32
- .tabs-title {
33
- margin-right: auto;
34
- margin-bottom: 16px;
35
+ const $emits = defineEmits<{
36
+ (e: 'click', tab: Tab): void;
37
+ }>();
35
38
 
36
- h3 {
37
- margin: 0;
38
- }
39
- }
39
+ const needRouterEngine = computed(() => {
40
+ return (
41
+ !!props.tabs &&
42
+ !!props.tabs[0] &&
43
+ !!(props.tabs[0].routePath || props.tabs[0].routeName)
44
+ );
45
+ });
40
46
 
41
- a {
42
- position: relative;
43
- top: 1px;
47
+ const selectedTabId: ComputedRef<string | null> = computed(() => {
48
+ if (needRouterEngine.value) {
49
+ const selectedTab =
50
+ props.tabs.find((t) => isTabSelected(t, currentRoute.value)) || null;
51
+ return selectedTab ? getTabId(selectedTab) : null;
52
+ } else if (props.initialTabId) {
53
+ return props.initialTabId;
54
+ } else {
55
+ return null;
44
56
  }
45
- }
46
- </style>
57
+ });
58
+
59
+ const isTabSelected = (tab: Tab, currentRoute: RouteLocation) =>
60
+ tab.routePath
61
+ ? currentRoute.path.includes(tab.routePath)
62
+ : tab.routeName
63
+ ? !!currentRoute.name &&
64
+ (currentRoute.name as string).includes(tab.routeName)
65
+ : false;
66
+ </script>
@@ -30,31 +30,31 @@ const Template = (args) => ({
30
30
 
31
31
  export const Default = Template.bind({});
32
32
  Default.args = {
33
- currentRoute: { path: 'toto' },
33
+ selectedTabId: 'toto',
34
34
  tab: { name: 'Titi', routePath: 'titi' },
35
35
  };
36
36
 
37
37
  export const Active = Template.bind({});
38
38
  Active.args = {
39
- currentRoute: { path: 'titi-subRoute' },
39
+ selectedTabId: 'Titi',
40
40
  tab: { name: 'Titi', routePath: 'titi' },
41
41
  };
42
42
 
43
43
  export const Disabled = Template.bind({});
44
44
  Disabled.args = {
45
- currentRoute: { path: 'toto' },
45
+ selectedTabId: 'toto',
46
46
  tab: { name: 'Titi', routePath: 'titi', disabled: true },
47
47
  };
48
48
 
49
49
  export const Error = Template.bind({});
50
50
  Error.args = {
51
- currentRoute: { path: 'toto' },
51
+ selectedTabId: 'toto',
52
52
  tab: { name: 'Titi', routePath: 'titi', error: true },
53
53
  };
54
54
 
55
55
  export const Hover = Template.bind({});
56
56
  Hover.args = {
57
- currentRoute: { path: 'toto' },
57
+ selectedTabId: 'toto',
58
58
  tab: { name: 'Titi', routePath: 'titi' },
59
59
  };
60
60
  Hover.play = async ({ canvasElement }) => {
@@ -1,40 +1,30 @@
1
1
  <template>
2
- <router-link
3
- :to="tabTarget"
2
+ <span
4
3
  data-testid="tab"
5
4
  class="tab"
6
5
  :class="{ active: isTabSelected, error: tab.error, disabled: tab.disabled }"
7
6
  >
8
- <slot>{{ tab.name }}</slot>
9
- </router-link>
7
+ <slot name="router" :tab="tab">{{ tab.name }}</slot>
8
+ </span>
10
9
  </template>
11
10
 
12
11
  <script lang="ts" setup>
13
12
  import { Tab } from '@/models/tab.model';
14
- import { ComputedRef, computed } from 'vue';
15
- import { RouteLocation, RouteLocationRaw } from 'vue-router';
13
+ import { computed, ComputedRef } from 'vue';
14
+ import { getTabId } from '@/helpers/tab.helper';
16
15
 
17
16
  const props = withDefaults(
18
17
  defineProps<{
19
- currentRoute: RouteLocation;
18
+ selectedTabId: string | null;
20
19
  tab: Tab;
21
20
  }>(),
22
21
  {},
23
22
  );
24
23
 
25
- const isTabSelected: ComputedRef<boolean> = computed(() =>
26
- props.tab.routePath
27
- ? props.currentRoute.path.includes(props.tab.routePath)
28
- : props.tab.routeName
29
- ? !!props.currentRoute.name &&
30
- (props.currentRoute.name as string).includes(props.tab.routeName)
31
- : false,
32
- );
24
+ const tabId = computed(() => getTabId(props.tab));
33
25
 
34
- const tabTarget: ComputedRef<RouteLocationRaw> = computed(() =>
35
- props.tab.routePath
36
- ? { path: props.tab.routePath }
37
- : { name: props.tab.routeName },
26
+ const isTabSelected: ComputedRef<boolean> = computed(
27
+ () => (tabId.value && tabId.value === props.selectedTabId) || false,
38
28
  );
39
29
  </script>
40
30
 
@@ -47,10 +37,15 @@ const tabTarget: ComputedRef<RouteLocationRaw> = computed(() =>
47
37
  text-decoration: none;
48
38
  border-bottom: 4px solid var(--tab-border-color);
49
39
  padding: 0 8px 16px 8px;
50
-
40
+ cursor: pointer;
41
+ :deep(a) {
42
+ color: var(--tab-color);
43
+ text-decoration: none;
44
+ }
51
45
  &:hover,
52
46
  &__hover {
53
47
  --tab-border-color: var(--bms-main-50);
48
+ text-decoration: none;
54
49
  }
55
50
 
56
51
  &.active {
@@ -0,0 +1,34 @@
1
+ import UiTabs from '@/components/navigation/UiTabs.vue';
2
+ import template from '@/documentation/template_field_dependency.mdx';
3
+
4
+ export default {
5
+ parameters: {
6
+ docs: {
7
+ page: template,
8
+ },
9
+ },
10
+ title: 'Composants/navigation/UiTabs',
11
+ component: UiTabs,
12
+ argTypes: {
13
+ items: {},
14
+ },
15
+ tags: ['technical'],
16
+ };
17
+
18
+ const Template = (args) => ({
19
+ components: {
20
+ UiTabs,
21
+ },
22
+ setup() {
23
+ return { args };
24
+ },
25
+ template: `
26
+ <UiTabs v-bind="args" />
27
+ `,
28
+ });
29
+
30
+ export const Primary = Template.bind({});
31
+ Primary.args = {
32
+ title: 'Title',
33
+ tabs: [{ name: 'Titi', id: 'titi' }, { name: 'Toto' }],
34
+ };
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="tabs-header">
3
+ <div class="tabs-title">
4
+ <h3>{{ title }}</h3>
5
+ </div>
6
+ <UiTab
7
+ v-for="tab in tabs"
8
+ :key="getTabId(tab)"
9
+ :selectedTabId="selectedTabId"
10
+ :tab="tab"
11
+ @click="onTabClick(tab)"
12
+ >
13
+ <template #router="{ tab }"><slot name="router" :tab="tab" /></template>
14
+ </UiTab>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { Tab } from '@/models/tab.model';
20
+ import UiTab from './UiTab.vue';
21
+ import { computed, ComputedRef, onMounted, ref, watch } from 'vue';
22
+ import { getTabId } from '@/helpers/tab.helper';
23
+
24
+ const props = defineProps<{
25
+ title: string;
26
+ tabs: Tab[];
27
+ initialTabId?: string | null;
28
+ }>();
29
+
30
+ const selectedTabId = ref<string | null>(null);
31
+
32
+ onMounted(() => {
33
+ selectedTabId.value = props.initialTabId || null;
34
+ });
35
+
36
+ watch(
37
+ () => props.initialTabId,
38
+ () => {
39
+ selectedTabId.value = props.initialTabId || null;
40
+ },
41
+ );
42
+
43
+ const $emits = defineEmits<{
44
+ (e: 'click', value: any): void;
45
+ }>();
46
+
47
+ const onTabClick = (tab: Tab) => {
48
+ if (getTabId(tab) !== selectedTabId.value) {
49
+ $emits('click', tab);
50
+ selectedTabId.value = getTabId(tab);
51
+ }
52
+ };
53
+ </script>
54
+
55
+ <style lang="scss" scoped>
56
+ .tabs-header {
57
+ display: flex;
58
+ align-items: center;
59
+ border-bottom: 1px solid var(--bms-grey-25);
60
+ box-sizing: border-box;
61
+
62
+ .tabs-title {
63
+ margin-right: auto;
64
+ margin-bottom: 16px;
65
+
66
+ h3 {
67
+ margin: 0;
68
+ }
69
+ }
70
+
71
+ span {
72
+ position: relative;
73
+ top: 1px;
74
+ }
75
+ }
76
+ </style>
@@ -0,0 +1,3 @@
1
+ import { Tab } from '@/models';
2
+
3
+ export const getTabId = (tab: Tab) => tab.id || tab.name;
@@ -1,7 +1,13 @@
1
1
  export interface Tab {
2
2
  routePath?: string;
3
3
  routeName?: string;
4
+ id?: string;
4
5
  name: string;
5
6
  disabled?: boolean;
6
7
  error?: boolean;
7
8
  }
9
+
10
+ export interface RouteTab extends Tab {
11
+ routePath?: string;
12
+ routeName?: string;
13
+ }