@ouestfrance/sipa-bms-ui 8.1.4 → 8.2.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.
@@ -5,7 +5,6 @@ import {
5
5
  SortValue,
6
6
  StatusType,
7
7
  type TableHeader,
8
- TooltipDirection,
9
8
  } from '@/models';
10
9
  import _get from 'lodash/get';
11
10
  import _isEqual from 'lodash/isEqual';
@@ -21,10 +20,9 @@ import {
21
20
  import BmsLoader from '../feedback/BmsLoader.vue';
22
21
  import { ChevronDown, ChevronsUpDown, ChevronUp } from 'lucide-vue-next';
23
22
  import UiBmsInputCheckbox from '../form/UiBmsInputCheckbox.vue';
24
- import { v4 as uuid } from 'uuid';
25
23
  import BmsAlert from '../feedback/BmsAlert.vue';
26
- import BmsTooltip from '../feedback/BmsTooltip.vue';
27
24
  import { enforceActionsColumnHeader } from '@/helpers';
25
+ import UiBmsTableRow from './UiBmsTableRow.vue';
28
26
 
29
27
  interface UiBmsTableProps {
30
28
  headers: TableHeader[];
@@ -51,6 +49,7 @@ const props = withDefaults(defineProps<UiBmsTableProps>(), {
51
49
  selectable: false,
52
50
  selectableDisabled: false,
53
51
  maxSelectedSize: Infinity,
52
+ selectMode: SelectMode.DEFAULT,
54
53
  });
55
54
 
56
55
  const selectedItems: ModelRef<unknown[]> = defineModel('selectedItems', {
@@ -323,7 +322,6 @@ onMounted(() => {
323
322
  <th
324
323
  v-for="header in filteredHeaders"
325
324
  :style="{
326
- // @ts-ignore
327
325
  '--table-cell-width': header?.width || undefined,
328
326
  }"
329
327
  :class="getHeaderClasses(header)"
@@ -345,38 +343,44 @@ onMounted(() => {
345
343
  <tbody class="bms-table__body">
346
344
  <template v-if="items.length">
347
345
  <template v-for="item in items" :key="item">
348
- <tr :class="{ selected: isItemSelected(item) }">
349
- <td v-if="selectable" class="bms-table__cell__checkbox">
350
- <BmsTooltip
351
- :direction="TooltipDirection.Right"
352
- tooltip-text="Vous ne pouvez pas désélectionner un élément unitairement si vous avez choisi de sélectionner la totalité des éléments"
353
- :activated="selectMode === SelectMode.ALL"
354
- >
355
- <UiBmsInputCheckbox
356
- :name="uuid()"
357
- :disabled="
358
- selectMode === SelectMode.ALL || selectableDisabled
359
- "
360
- :model-value="isItemSelected(item)"
361
- @update:model-value="onItemSelect(item)"
362
- />
363
- </BmsTooltip>
364
- </td>
365
- <slot :row="item">
366
- <td
367
- v-for="cell in filteredHeaders"
368
- :class="[getAlignClass(cell), 'bms-table__cell']"
369
- :key="cell.key"
346
+ <UiBmsTableRow
347
+ :item="item"
348
+ :selected-items="selectedItems"
349
+ :selectable="selectable"
350
+ :headers="filteredHeaders"
351
+ :select-mode="selectMode"
352
+ :selectable-disabled="selectableDisabled"
353
+ :dense="mode === 'dense'"
354
+ @select="onItemSelect"
355
+ >
356
+ <template v-for="cell in headers" v-slot:[cell.key]="slotData">
357
+ <slot :name="cell.key" v-bind="slotData" />
358
+ </template>
359
+ <template #default="{ row }">
360
+ <slot name="default" :row="row"></slot>
361
+ </template>
362
+ </UiBmsTableRow>
363
+ <!-- FIXME typing -->
364
+ <template v-if="(item as any)?.childElement">
365
+ <slot name="child-element">
366
+ <UiBmsTableRow
367
+ is-child-element
368
+ :item="item"
369
+ :selected-items="selectedItems"
370
+ :selectable="selectable"
371
+ :headers="filteredHeaders"
372
+ :select-mode="selectMode"
373
+ :selectable-disabled="selectableDisabled"
370
374
  >
371
- <div v-if="cell?.action" class="bms-table__cell--action">
372
- <slot :name="cell.key" :row="item"></slot>
373
- </div>
374
- <slot v-else :name="cell.key" :row="item"
375
- >{{ _get(item, cell.key) || '' }}
376
- </slot>
377
- </td>
375
+ <template
376
+ v-for="cell in headers"
377
+ v-slot:[cell.key]="slotData"
378
+ >
379
+ <slot :name="cell.key" v-bind="slotData" />
380
+ </template>
381
+ </UiBmsTableRow>
378
382
  </slot>
379
- </tr>
383
+ </template>
380
384
  </template>
381
385
  </template>
382
386
  <template v-else>
@@ -502,44 +506,44 @@ onMounted(() => {
502
506
  td {
503
507
  padding: var(--table-cell-padding);
504
508
  }
509
+ }
505
510
 
506
- th {
507
- --header-content-sort-icon-color: var(--bms-grey-25);
508
- --header-content-justify: start;
511
+ th {
512
+ --header-content-sort-icon-color: var(--bms-grey-25);
513
+ --header-content-justify: start;
509
514
 
510
- width: var(--table-cell-width, auto);
511
- padding: var(--table-cell-padding);
515
+ width: var(--table-cell-width, auto);
516
+ padding: var(--table-cell-padding);
512
517
 
513
- &.sortable:hover {
514
- --header-content-sort-icon-color: var(--bms-grey-100);
515
- cursor: pointer;
516
- }
518
+ &.sortable:hover {
519
+ --header-content-sort-icon-color: var(--bms-grey-100);
520
+ cursor: pointer;
521
+ }
517
522
 
518
- &.u-text-align-start {
519
- --header-content-justify: start;
520
- }
523
+ &.u-text-align-start {
524
+ --header-content-justify: start;
525
+ }
521
526
 
522
- &.u-text-align-center {
523
- --header-content-justify: center;
524
- }
527
+ &.u-text-align-center {
528
+ --header-content-justify: center;
529
+ }
525
530
 
526
- &.u-text-align-end {
527
- --header-content-justify: end;
528
- }
531
+ &.u-text-align-end {
532
+ --header-content-justify: end;
533
+ }
529
534
 
530
- &.sorted {
531
- --header-content-sort-icon-color: var(--bms-grey-100);
532
- }
535
+ &.sorted {
536
+ --header-content-sort-icon-color: var(--bms-grey-100);
537
+ }
533
538
 
534
- .header-content {
535
- display: flex;
536
- align-items: center;
537
- justify-content: var(--header-content-justify);
538
- gap: 0.5em;
539
+ .header-content {
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: var(--header-content-justify);
543
+ gap: 0.5em;
539
544
 
540
- &-sort {
541
- color: var(--header-content-sort-icon-color);
542
- }
545
+ &-sort {
546
+ color: var(--header-content-sort-icon-color);
543
547
  }
544
548
  }
545
549
  }
@@ -597,22 +601,16 @@ onMounted(() => {
597
601
  border-bottom-right-radius: var(--table-cell-radius);
598
602
  }
599
603
 
600
- td,
601
604
  th {
602
605
  background-color: rgba(255, 255, 255, 1);
603
606
  }
604
607
 
605
608
  tbody {
606
609
  overflow: hidden;
610
+ background: white;
607
611
 
608
612
  tr {
609
613
  position: relative;
610
-
611
- &.selected {
612
- td {
613
- background-color: var(--bms-main-10);
614
- }
615
- }
616
614
  }
617
615
  }
618
616
  }
@@ -0,0 +1,143 @@
1
+ import UiBmsTableRow from '@/components/table/UiBmsTableRow.vue';
2
+
3
+ export default {
4
+ title: 'Composants/table/UiBmsTableRow',
5
+ component: UiBmsTableRow,
6
+ };
7
+
8
+ const Template = (args) => ({
9
+ components: {
10
+ UiBmsTableRow,
11
+ },
12
+ setup() {
13
+ return { args };
14
+ },
15
+ // And then the `args` are bound to your component with `v-bind="args"`
16
+ template: `
17
+ <table style="width:100%">
18
+ <UiBmsTableRow v-bind="args">
19
+ </UiBmsTableRow>
20
+ </table>
21
+ `,
22
+ });
23
+
24
+ export const Default = Template.bind({});
25
+ Default.args = {
26
+ headers: [
27
+ {
28
+ label: 'Column 1',
29
+ key: 'col1',
30
+ align: 'start',
31
+ },
32
+ {
33
+ label: 'Column 2',
34
+ key: 'col2',
35
+ align: 'center',
36
+ },
37
+ {
38
+ label: 'Column 3',
39
+ key: 'col3',
40
+ align: 'end',
41
+ },
42
+ ],
43
+ selectedItems: [],
44
+ item: {
45
+ col1: 'Value1',
46
+ col2: 'Value2',
47
+ col3: 'Value3',
48
+ },
49
+ };
50
+
51
+ export const Dense = Template.bind({});
52
+ Dense.args = {
53
+ dense: true,
54
+ headers: [
55
+ {
56
+ label: 'Column 1',
57
+ key: 'col1',
58
+ align: 'start',
59
+ },
60
+ {
61
+ label: 'Column 2',
62
+ key: 'col2',
63
+ align: 'center',
64
+ },
65
+ {
66
+ label: 'Column 3',
67
+ key: 'col3',
68
+ align: 'end',
69
+ },
70
+ ],
71
+ selectedItems: [],
72
+ item: {
73
+ col1: 'Value1',
74
+ col2: 'Value2',
75
+ col3: 'Value3',
76
+ },
77
+ };
78
+
79
+ export const Selected = Template.bind({});
80
+ Selected.args = {
81
+ headers: [
82
+ {
83
+ label: 'Column 1',
84
+ key: 'col1',
85
+ align: 'start',
86
+ },
87
+ {
88
+ label: 'Column 2',
89
+ key: 'col2',
90
+ align: 'center',
91
+ },
92
+ {
93
+ label: 'Column 3',
94
+ key: 'col3',
95
+ align: 'end',
96
+ },
97
+ ],
98
+ selectedItems: [
99
+ {
100
+ col1: 'Value1',
101
+ col2: 'Value2',
102
+ col3: 'Value3',
103
+ },
104
+ ],
105
+ item: {
106
+ col1: 'Value1',
107
+ col2: 'Value2',
108
+ col3: 'Value3',
109
+ },
110
+ };
111
+
112
+ export const IsChildElement = Template.bind({});
113
+ IsChildElement.args = {
114
+ isChildElement: true,
115
+ headers: [
116
+ {
117
+ label: 'Column 1',
118
+ key: 'col1',
119
+ align: 'start',
120
+ },
121
+ {
122
+ label: 'Column 2',
123
+ key: 'col2',
124
+ align: 'center',
125
+ },
126
+ {
127
+ label: 'Column 3',
128
+ key: 'col3',
129
+ align: 'end',
130
+ },
131
+ ],
132
+ selectedItems: [],
133
+ item: {
134
+ col1: 'Value1',
135
+ col2: 'Value2',
136
+ col3: 'Value3',
137
+ childElement: {
138
+ col1: 'child1',
139
+ col2: 'child2',
140
+ col3: 'child3',
141
+ },
142
+ },
143
+ };
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <tr
3
+ class="bms-table__row"
4
+ :class="{
5
+ 'bms-table__row--selected': isItemSelected(item),
6
+ 'bms-table__row--disabled': isChildElement,
7
+ 'bms-table__row--dense': dense,
8
+ }"
9
+ >
10
+ <td v-if="selectable" class="bms-table__row__cell__checkbox">
11
+ <BmsTooltip
12
+ :direction="TooltipDirection.Right"
13
+ tooltip-text="Vous ne pouvez pas désélectionner un élément unitairement si vous avez choisi de sélectionner la totalité des éléments"
14
+ :activated="selectMode === SelectMode.ALL"
15
+ >
16
+ <UiBmsInputCheckbox
17
+ :name="uuid()"
18
+ :disabled="selectMode === SelectMode.ALL || selectableDisabled"
19
+ :model-value="isItemSelected(item)"
20
+ @update:model-value="emits('select', item)"
21
+ />
22
+ </BmsTooltip>
23
+ </td>
24
+ <slot name="default" :row="item">
25
+ <td
26
+ v-for="(cell, index) in headers"
27
+ :class="[getAlignClass(cell), 'bms-table__row__cell']"
28
+ :key="cell.key"
29
+ >
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>
34
+ <slot
35
+ :name="cell.key"
36
+ :row="item.childElement"
37
+ :isChildElement="isChildElement"
38
+ >
39
+ {{ _get(item.childElement, cell.key) || '' }}
40
+ </slot>
41
+ </div>
42
+ <div v-else-if="cell?.action" class="bms-table__row__cell--action">
43
+ <slot
44
+ :name="cell.key"
45
+ :row="item"
46
+ :isChildElement="isChildElement"
47
+ ></slot>
48
+ </div>
49
+ <slot
50
+ v-else
51
+ :name="cell.key"
52
+ :row="item"
53
+ :isChildElement="isChildElement"
54
+ >
55
+ {{ _get(item, cell.key) || '' }}
56
+ </slot>
57
+ </td>
58
+ </slot>
59
+ </tr>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import { SelectMode, TableHeader, TooltipDirection } from '@/models';
64
+ import { v4 as uuid } from 'uuid';
65
+ import _isEqual from 'lodash/isEqual';
66
+ import _get from 'lodash/get';
67
+ import UiBmsInputCheckbox from '../form/UiBmsInputCheckbox.vue';
68
+ import BmsTooltip from '../feedback/BmsTooltip.vue';
69
+ import { CornerDownRight } from 'lucide-vue-next';
70
+
71
+ interface Props {
72
+ item: any;
73
+ selectedItems: any[];
74
+ selectable: boolean;
75
+ headers: TableHeader[];
76
+ selectMode: SelectMode;
77
+ selectableDisabled?: boolean;
78
+ dense?: boolean;
79
+ isChildElement?: boolean;
80
+ }
81
+ const props = withDefaults(defineProps<Props>(), {
82
+ dense: false,
83
+ selectableDisabled: false,
84
+ });
85
+
86
+ const emits = defineEmits<{ select: [item: any] }>();
87
+
88
+ const isItemSelected = (item: unknown): boolean => {
89
+ return (
90
+ props.selectMode === SelectMode.ALL ||
91
+ !!props.selectedItems.find((it) => _isEqual(item, it))
92
+ );
93
+ };
94
+
95
+ const getAlignClass = (header: TableHeader) => {
96
+ const align = !header.align ? 'start' : header.align;
97
+ return `u-text-align-${align}`;
98
+ };
99
+ </script>
100
+
101
+ <style lang="scss" scoped>
102
+ .bms-table__row {
103
+ --table-cell-padding: 1em;
104
+ background-color: rgba(255, 255, 255, 1);
105
+
106
+ td {
107
+ padding: var(--table-cell-padding);
108
+ background-color: rgba(255, 255, 255, 1);
109
+ }
110
+
111
+ &--dense {
112
+ --table-cell-padding: 0.5em 1em;
113
+ }
114
+ &--selected {
115
+ td {
116
+ background-color: var(--bms-main-10);
117
+ }
118
+ }
119
+
120
+ &--disabled {
121
+ td {
122
+ color: var(--bms-grey-50);
123
+ }
124
+ }
125
+
126
+ &__cell {
127
+ &__checkbox {
128
+ width: 4em;
129
+ }
130
+ &--action {
131
+ display: flex;
132
+ justify-content: end;
133
+ }
134
+
135
+ &--empty {
136
+ height: 360px;
137
+ }
138
+
139
+ &--child-element {
140
+ display: inline-flex;
141
+ align-items: flex-end;
142
+ &__icon {
143
+ display: flex;
144
+ min-width: 1em;
145
+ margin-right: 1em;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ </style>
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ import BmsInputRadioCaption from './components/form/BmsInputRadioCaption.vue';
28
28
  import BmsInputRadioCaptionGroup from './components/form/BmsInputRadioCaptionGroup.vue';
29
29
  import BmsInputText from './components/form/BmsInputText.vue';
30
30
  import BmsInputToggle from '@/components/form/BmsInputToggle.vue';
31
+ import BmsMultiSelect from '@/components/form/BmsMultiSelect.vue';
31
32
  import BmsSearch from './components/form/BmsSearch.vue';
32
33
  import BmsSelect from './components/form/BmsSelect.vue';
33
34
  import BmsTag from './components/form/BmsTag.vue';
@@ -95,6 +96,7 @@ export const createBmsUi = () => ({
95
96
  app.component('BmsInputRadioCaptionGroup', BmsInputRadioCaptionGroup);
96
97
  app.component('BmsInputText', BmsInputText);
97
98
  app.component('BmsInputToggle', BmsInputToggle);
99
+ app.component('BmsMultiSelect', BmsMultiSelect);
98
100
  app.component('BmsSearch', BmsSearch);
99
101
  app.component('BmsSelect', BmsSelect);
100
102
  app.component('BmsTag', BmsTag);
@@ -170,6 +172,7 @@ export {
170
172
  BmsInputRadioCaptionGroup,
171
173
  BmsInputText,
172
174
  BmsInputToggle,
175
+ BmsMultiSelect,
173
176
  BmsSearch,
174
177
  BmsSelect,
175
178
  BmsTag,
@@ -1,3 +1,4 @@
1
+ import { InputType } from './form.model';
1
2
  import { Sort, SortFunction } from './sort.model';
2
3
 
3
4
  export interface TableHeader {
@@ -32,7 +33,7 @@ export type FilterType =
32
33
 
33
34
  export interface Filter {
34
35
  label: string;
35
- inputType?: string;
36
+ inputType?: InputType;
36
37
  key: string;
37
38
  type: FilterType;
38
39
  value?: any;
@@ -257,6 +257,26 @@
257
257
 
258
258
  <hr />
259
259
 
260
+ <BmsMultiSelect
261
+ label="multi select"
262
+ required
263
+ placeholder="This is a placeholder"
264
+ v-model="multiSelect"
265
+ :options="[
266
+ { label: 'toto1', value: 'value1', icon: BellIcon },
267
+ { label: 'titi2', value: 'value2', icon: Magnet },
268
+ { label: 'toto3', value: 'value3', icon: Egg },
269
+ { label: 'toto4', value: 'value4', icon: Egg },
270
+ { label: 'toto5', value: 'value5', icon: Egg },
271
+ { label: 'toto6', value: 'value6', icon: Egg },
272
+ { label: 'toto7', value: 'value7', icon: Egg },
273
+ { label: 'toto8', value: 'value8', icon: Egg },
274
+ { label: 'toto9', value: 'value9', icon: Egg },
275
+ ]"
276
+ />
277
+
278
+ <hr />
279
+
260
280
  <BmsTextArea
261
281
  label="textArea"
262
282
  required
@@ -330,9 +350,10 @@ import BmsInputDateTime from '@/components/form/BmsInputDateTime.vue';
330
350
  import BmsInputCheckboxGroup from '@/components/form/BmsInputCheckboxGroup.vue';
331
351
  import BmsInputCheckboxCaption from '@/components/form/BmsInputCheckboxCaption.vue';
332
352
  import BmsInputCheckboxCaptionGroup from '@/components/form/BmsInputCheckboxCaptionGroup.vue';
333
- import { Copy } from 'lucide-vue-next';
353
+ import { Copy, BellIcon, Egg, Magnet } from 'lucide-vue-next';
334
354
  import BmsIconButton from '@/components/button/BmsIconButton.vue';
335
355
  import { BMS_FORM_VALID_URL_REGEX } from '@/helpers/form.helper';
356
+ import BmsMultiSelect from '@/components/form/BmsMultiSelect.vue';
336
357
 
337
358
  const { success } = useNotifications();
338
359
  const text = ref('');
@@ -340,6 +361,7 @@ const text2 = ref('');
340
361
  const captionsText2 = ref();
341
362
  const selected = ref('value1');
342
363
  const selected2 = ref('');
364
+ const multiSelect = ref([]);
343
365
  const files: Ref<File[]> = ref([]);
344
366
  const selectedRadio = ref('titi');
345
367
  const selectedRadio2 = ref('titi');
@@ -8,7 +8,6 @@
8
8
  :size="5"
9
9
  :canSaveFilters="true"
10
10
  :savedFilters="savedFilters"
11
- :selectable="true"
12
11
  help-link="/help-path"
13
12
  activity-link="/activity-path"
14
13
  v-model:selectedItems="selectedItems"
@@ -43,9 +42,16 @@
43
42
  tooltip
44
43
  </BmsTooltip>
45
44
  </template>
46
- <template #action>
47
- <BmsIconButton> <Save /> </BmsIconButton>
48
- <BmsIconButton :mode="StatusType.Danger"> <Trash /> </BmsIconButton>
45
+ <template #action="{ isChildElement }">
46
+ <template v-if="isChildElement">
47
+ <BmsChip>Hérité</BmsChip>
48
+ </template>
49
+ <template v-else>
50
+ <BmsIconButton @click="success('Sauvegarde')"> <Save /> </BmsIconButton>
51
+ <BmsIconButton :mode="StatusType.Danger" @click="error('Suppression')">
52
+ <Trash />
53
+ </BmsIconButton>
54
+ </template>
49
55
  </template>
50
56
  </bms-table>
51
57
  <BmsButton @click="goBack">Go Back</BmsButton>
@@ -57,7 +63,7 @@
57
63
  </template>
58
64
 
59
65
  <script lang="ts" setup>
60
- import { BmsBackButton, BmsButton } from '@/index';
66
+ import { BmsBackButton, BmsButton, useNotifications } from '@/index';
61
67
  import {
62
68
  Filter,
63
69
  InputType,
@@ -71,8 +77,10 @@ import BmsShortLinkMenu from '@/components/navigation/BmsShortLinkMenu.vue';
71
77
  import BmsTooltip from '@/components/feedback/BmsTooltip.vue';
72
78
  import BmsIconButton from '@/components/button/BmsIconButton.vue';
73
79
  import { Delete, Save, Trash } from 'lucide-vue-next';
80
+ import BmsChip from '@/components/form/BmsChip.vue';
74
81
 
75
82
  const { goBack } = useRouterHistory();
83
+ const { error, success } = useNotifications();
76
84
 
77
85
  const headers = [
78
86
  { label: 'Colonne 1', key: 'column1', sortable: true },
@@ -125,6 +133,23 @@ const items = Array(100)
125
133
  },
126
134
  isOk: Math.random() < 0.5,
127
135
  number: Math.random(),
136
+ childElement:
137
+ Math.random() < 0.5
138
+ ? {
139
+ column1: 'Element enfant',
140
+ column2: index,
141
+ date: randomDate(),
142
+ dateUpdate: randomDate(),
143
+ dateDelete: randomDate(),
144
+ choice1: 'N/A',
145
+ choice2: 'N/A',
146
+ deepValue: {
147
+ deepAttr: index % 2 === 0,
148
+ },
149
+ isOk: true,
150
+ number: 0,
151
+ }
152
+ : undefined,
128
153
  }));
129
154
 
130
155
  const savedFilters: Ref<SavedFilter[]> = ref<SavedFilter[]>([]);