@ouestfrance/sipa-bms-ui 8.17.0 → 8.18.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.18.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;
@@ -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
+ }