@ouestfrance/sipa-bms-ui 8.1.3 → 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', {
@@ -157,7 +156,6 @@ const areAllCurrentItemsSelected = ref(props.selectMode === SelectMode.ALL);
157
156
  const selectAll = () => {
158
157
  emits('selectAll');
159
158
  areAllCurrentItemsSelected.value = true;
160
- selectedItems.value = props.items;
161
159
  };
162
160
 
163
161
  const onToggleSelectAllCurrentItems = () => {
@@ -324,7 +322,6 @@ onMounted(() => {
324
322
  <th
325
323
  v-for="header in filteredHeaders"
326
324
  :style="{
327
- // @ts-ignore
328
325
  '--table-cell-width': header?.width || undefined,
329
326
  }"
330
327
  :class="getHeaderClasses(header)"
@@ -346,38 +343,44 @@ onMounted(() => {
346
343
  <tbody class="bms-table__body">
347
344
  <template v-if="items.length">
348
345
  <template v-for="item in items" :key="item">
349
- <tr :class="{ selected: isItemSelected(item) }">
350
- <td v-if="selectable" class="bms-table__cell__checkbox">
351
- <BmsTooltip
352
- :direction="TooltipDirection.Right"
353
- 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"
354
- :activated="selectMode === SelectMode.ALL"
355
- >
356
- <UiBmsInputCheckbox
357
- :name="uuid()"
358
- :disabled="
359
- selectMode === SelectMode.ALL || selectableDisabled
360
- "
361
- :model-value="isItemSelected(item)"
362
- @update:model-value="onItemSelect(item)"
363
- />
364
- </BmsTooltip>
365
- </td>
366
- <slot :row="item">
367
- <td
368
- v-for="cell in filteredHeaders"
369
- :class="[getAlignClass(cell), 'bms-table__cell']"
370
- :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"
371
374
  >
372
- <div v-if="cell?.action" class="bms-table__cell--action">
373
- <slot :name="cell.key" :row="item"></slot>
374
- </div>
375
- <slot v-else :name="cell.key" :row="item"
376
- >{{ _get(item, cell.key) || '' }}
377
- </slot>
378
- </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>
379
382
  </slot>
380
- </tr>
383
+ </template>
381
384
  </template>
382
385
  </template>
383
386
  <template v-else>
@@ -503,44 +506,44 @@ onMounted(() => {
503
506
  td {
504
507
  padding: var(--table-cell-padding);
505
508
  }
509
+ }
506
510
 
507
- th {
508
- --header-content-sort-icon-color: var(--bms-grey-25);
509
- --header-content-justify: start;
511
+ th {
512
+ --header-content-sort-icon-color: var(--bms-grey-25);
513
+ --header-content-justify: start;
510
514
 
511
- width: var(--table-cell-width, auto);
512
- padding: var(--table-cell-padding);
515
+ width: var(--table-cell-width, auto);
516
+ padding: var(--table-cell-padding);
513
517
 
514
- &.sortable:hover {
515
- --header-content-sort-icon-color: var(--bms-grey-100);
516
- cursor: pointer;
517
- }
518
+ &.sortable:hover {
519
+ --header-content-sort-icon-color: var(--bms-grey-100);
520
+ cursor: pointer;
521
+ }
518
522
 
519
- &.u-text-align-start {
520
- --header-content-justify: start;
521
- }
523
+ &.u-text-align-start {
524
+ --header-content-justify: start;
525
+ }
522
526
 
523
- &.u-text-align-center {
524
- --header-content-justify: center;
525
- }
527
+ &.u-text-align-center {
528
+ --header-content-justify: center;
529
+ }
526
530
 
527
- &.u-text-align-end {
528
- --header-content-justify: end;
529
- }
531
+ &.u-text-align-end {
532
+ --header-content-justify: end;
533
+ }
530
534
 
531
- &.sorted {
532
- --header-content-sort-icon-color: var(--bms-grey-100);
533
- }
535
+ &.sorted {
536
+ --header-content-sort-icon-color: var(--bms-grey-100);
537
+ }
534
538
 
535
- .header-content {
536
- display: flex;
537
- align-items: center;
538
- justify-content: var(--header-content-justify);
539
- gap: 0.5em;
539
+ .header-content {
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: var(--header-content-justify);
543
+ gap: 0.5em;
540
544
 
541
- &-sort {
542
- color: var(--header-content-sort-icon-color);
543
- }
545
+ &-sort {
546
+ color: var(--header-content-sort-icon-color);
544
547
  }
545
548
  }
546
549
  }
@@ -598,22 +601,16 @@ onMounted(() => {
598
601
  border-bottom-right-radius: var(--table-cell-radius);
599
602
  }
600
603
 
601
- td,
602
604
  th {
603
605
  background-color: rgba(255, 255, 255, 1);
604
606
  }
605
607
 
606
608
  tbody {
607
609
  overflow: hidden;
610
+ background: white;
608
611
 
609
612
  tr {
610
613
  position: relative;
611
-
612
- &.selected {
613
- td {
614
- background-color: var(--bms-main-10);
615
- }
616
- }
617
614
  }
618
615
  }
619
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[]>([]);