@eturnity/eturnity_reusable_components 8.19.2 → 8.19.4

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/assets/svgIcons/download.svg +3 -3
  3. package/src/assets/svgIcons/filter.svg +3 -0
  4. package/src/assets/svgIcons/kanban_view.svg +6 -0
  5. package/src/assets/svgIcons/plus_button.svg +3 -3
  6. package/src/assets/svgIcons/table_view.svg +3 -0
  7. package/src/assets/theme.js +53 -5
  8. package/src/components/banner/banner/Banner.stories.js +99 -43
  9. package/src/components/banner/infoBanner/InfoBanner.stories.js +72 -30
  10. package/src/components/buttons/buttonIcon/ButtonIcon.stories.js +167 -0
  11. package/src/components/buttons/buttonIcon/index.vue +40 -6
  12. package/src/components/buttons/mainButton/MainButton.stories.js +115 -34
  13. package/src/components/draggableCard/draggableCard.stories.js +110 -54
  14. package/src/components/filter/filterSettings.vue +1 -1
  15. package/src/components/filterComponent/viewFilter.vue +605 -0
  16. package/src/components/filterComponent/viewSettings.vue +281 -0
  17. package/src/components/filterComponent/viewSort.vue +330 -0
  18. package/src/components/icon/Icon.stories.js +220 -0
  19. package/src/components/icon/index.vue +8 -1
  20. package/src/components/infoCard/index.vue +7 -3
  21. package/src/components/inputs/searchInput/index.vue +1 -1
  22. package/src/components/inputs/select/index.vue +6 -5
  23. package/src/components/inputs/select/option/index.vue +5 -0
  24. package/src/components/projectMarker/ProjectMarker.stories.js +130 -0
  25. package/src/components/selectedOptions/index.vue +13 -2
  26. package/src/components/selectedOptions/selectedOptions.stories.js +135 -37
  27. package/src/components/spinner/Spinner.stories.js +70 -18
  28. package/src/components/spinnerGif/SpinnerGif.stories.js +86 -0
  29. package/src/components/tableDropdown/TableDropdown.stories.js +192 -0
  30. package/src/components/tables/mainTable/MainTable.stories.js +151 -0
  31. package/src/components/tabsHeader/TabsHeader.stories.js +142 -0
  32. package/src/components/tabsHeader/index.vue +33 -9
  33. package/src/components/videoThumbnail/videoThumbnail.stories.js +90 -17
  34. package/src/helpers/translateLang.js +95 -24
  35. package/src/components/icon/Icons.stories.js +0 -31
@@ -0,0 +1,605 @@
1
+ <template>
2
+ <PageContainer>
3
+ <ButtonIcon
4
+ ref="buttonIcon"
5
+ class="button-icon"
6
+ fill-type="stroke"
7
+ icon-name="filter"
8
+ :number-count="getNumberCount()"
9
+ :text="$gettext('filter')"
10
+ type="filter"
11
+ @click="toggleOptions"
12
+ />
13
+ <ButtonWrapper v-if="isOptionsVisible">
14
+ <BoxContainer ref="boxContainer" class="box-container">
15
+ <BoxTitle>{{ $gettext('filter') }}</BoxTitle>
16
+ <SortOptionsContainer>
17
+ <template v-if="selectedSort.length > 0">
18
+ <SortDropdownContainer
19
+ v-for="(item, index) in selectedSort"
20
+ :key="index"
21
+ >
22
+ <LeadingText>{{ $gettext('if') }}</LeadingText>
23
+ <SelectComponent
24
+ ref="selectSortDropdown"
25
+ align-items="vertical"
26
+ class="sort-dropdown"
27
+ :dropdown-auto-close="false"
28
+ select-height="40px"
29
+ @input-change="
30
+ onSelectFilter({
31
+ type: 'column',
32
+ value: $event,
33
+ index: index,
34
+ })
35
+ "
36
+ >
37
+ <template #selector>
38
+ <SelectedText>{{
39
+ getSelectedLabel({
40
+ type: 'column',
41
+ value: item.column,
42
+ index,
43
+ }) || $gettext('category')
44
+ }}</SelectedText>
45
+ </template>
46
+ <template #dropdown>
47
+ <SelectOption
48
+ v-for="category in filterCategories"
49
+ :key="category.name"
50
+ :value="category.name"
51
+ >
52
+ {{ $gettext(category.text) }}
53
+ </SelectOption>
54
+ </template>
55
+ </SelectComponent>
56
+ <SelectComponent
57
+ ref="selectSortDropdown"
58
+ align-items="vertical"
59
+ class="sort-dropdown"
60
+ :disabled="!item.column"
61
+ :dropdown-auto-close="false"
62
+ select-height="40px"
63
+ @input-change="
64
+ onSelectFilter({
65
+ type: 'constraint',
66
+ value: $event,
67
+ index: index,
68
+ })
69
+ "
70
+ >
71
+ <template #selector>
72
+ <SelectedText>{{
73
+ getSelectedLabel({
74
+ type: 'constraint',
75
+ value: item.constraint,
76
+ index,
77
+ }) || $gettext('constraint')
78
+ }}</SelectedText>
79
+ </template>
80
+ <template #dropdown>
81
+ <SelectOption
82
+ v-for="(item, index) in constraintOptions(index)"
83
+ :key="item.value"
84
+ :value="item.value"
85
+ >
86
+ {{ item.label }}
87
+ </SelectOption>
88
+ </template>
89
+ </SelectComponent>
90
+ <template v-if="item.column === 'updated'">
91
+ <DatePickerContainer>
92
+ <VueDatePicker
93
+ ref="datePicker"
94
+ :auto-apply="true"
95
+ :clearable="true"
96
+ :disabled="!item.constraint"
97
+ :enable-time-picker="false"
98
+ :format="datePickerFormat"
99
+ :input-class-name="'dp-input'"
100
+ :locale="datePickerLocale"
101
+ model-type="format"
102
+ :model-value="item.selectedDate"
103
+ :placeholder="$gettext('Select')"
104
+ :range="false"
105
+ :single-picker="true"
106
+ text-input
107
+ @click="handleDatePickerClick"
108
+ @open="handleDatePickerClick"
109
+ @update:model-value="
110
+ onSelectFilter({
111
+ type: 'selectedDate',
112
+ value: $event,
113
+ index: index,
114
+ constraint: item.constraint,
115
+ })
116
+ "
117
+ />
118
+ </DatePickerContainer>
119
+ </template>
120
+ <SelectComponent
121
+ v-else
122
+ ref="selectSortDropdown"
123
+ align-items="vertical"
124
+ class="sort-dropdown"
125
+ :disabled="!item.column || !item.constraint"
126
+ :dropdown-auto-close="false"
127
+ :is-searchable="false"
128
+ option-width="max-content"
129
+ select-height="40px"
130
+ @input-change="
131
+ onSelectFilter({
132
+ type: 'selectedOptions',
133
+ value: $event,
134
+ index: index,
135
+ })
136
+ "
137
+ >
138
+ <template #selector>
139
+ <SelectedText>{{
140
+ getSelectedLabel({
141
+ type: 'selectedOptions',
142
+ value: item.constraint,
143
+ index,
144
+ }) || $gettext('Options')
145
+ }}</SelectedText>
146
+ </template>
147
+ <template #dropdown>
148
+ <OptionsContainer class="options-container">
149
+ <SelectOption
150
+ v-for="option in optionsList(index)"
151
+ :key="option.value"
152
+ :value="option.value"
153
+ @click="
154
+ onSelectFilter({
155
+ type: 'selectedOptions',
156
+ value: option.value,
157
+ index: index,
158
+ })
159
+ "
160
+ >
161
+ <RcCheckbox
162
+ :is-checked="
163
+ selectedSort[index].selectedOptions?.includes(
164
+ option.value
165
+ )
166
+ "
167
+ :label="$gettext(option.label)"
168
+ size="small"
169
+ @on-event-handler="
170
+ onSelectFilter({
171
+ type: 'selectedOptions',
172
+ value: option.value,
173
+ index: index,
174
+ })
175
+ "
176
+ />
177
+ </SelectOption>
178
+ </OptionsContainer>
179
+ </template>
180
+ </SelectComponent>
181
+ <IconContainer>
182
+ <RCIcon
183
+ :color="theme.semanticColors.teal[800]"
184
+ cursor="pointer"
185
+ name="close"
186
+ size="14px"
187
+ @click.stop="removeItem(index)"
188
+ />
189
+ </IconContainer>
190
+ </SortDropdownContainer>
191
+ </template>
192
+ <template v-else>
193
+ <SortDropdownContainer>
194
+ <LeadingText>{{ $gettext('if') }}</LeadingText>
195
+ <SelectComponent
196
+ ref="selectSortDropdown"
197
+ align-items="vertical"
198
+ class="sort-dropdown"
199
+ :dropdown-auto-close="false"
200
+ select-height="40px"
201
+ @input-change="
202
+ onSelectFilter({
203
+ type: 'column',
204
+ value: $event,
205
+ index: 0,
206
+ })
207
+ "
208
+ >
209
+ <template #selector>
210
+ <SelectedText>{{ $gettext('category') }}</SelectedText>
211
+ </template>
212
+ <template #dropdown>
213
+ <SelectOption
214
+ v-for="item in filterCategories"
215
+ :key="item.name"
216
+ :value="item.name"
217
+ >
218
+ {{ $gettext(item.text) }}
219
+ </SelectOption>
220
+ </template>
221
+ </SelectComponent>
222
+ <SelectComponent
223
+ ref="selectSortDropdown"
224
+ align-items="vertical"
225
+ class="sort-dropdown"
226
+ :disabled="true"
227
+ :dropdown-auto-close="false"
228
+ select-height="40px"
229
+ >
230
+ <template #selector>
231
+ <SelectedText>{{ $gettext('constraint') }}</SelectedText>
232
+ </template>
233
+ </SelectComponent>
234
+ <SelectComponent
235
+ ref="selectSortDropdown"
236
+ align-items="vertical"
237
+ class="sort-dropdown"
238
+ :disabled="true"
239
+ :dropdown-auto-close="false"
240
+ select-height="40px"
241
+ >
242
+ <template #selector>
243
+ <SelectedText>{{ $gettext('Options') }}</SelectedText>
244
+ </template>
245
+ </SelectComponent>
246
+ </SortDropdownContainer>
247
+ </template>
248
+ </SortOptionsContainer>
249
+ <BottomContainer>
250
+ <PlaceholderText>{{ $gettext('if') }}</PlaceholderText>
251
+ <ButtonIcon
252
+ icon-name="plus_button"
253
+ :text="$gettext('add_filter')"
254
+ type="ghost"
255
+ @click="onAddFilter"
256
+ />
257
+ </BottomContainer>
258
+ </BoxContainer>
259
+ </ButtonWrapper>
260
+ </PageContainer>
261
+ </template>
262
+
263
+ <script>
264
+ import styled from 'vue3-styled-components'
265
+ import theme from '../../assets/theme'
266
+ import ButtonIcon from '../buttons/buttonIcon/index.vue'
267
+ import SelectComponent from '../inputs/select/index.vue'
268
+ import SelectOption from '../inputs/select/option/index.vue'
269
+ import RcCheckbox from '../inputs/checkbox/index.vue'
270
+ import RCIcon from '../icon'
271
+ import VueDatePicker from '@vuepic/vue-datepicker'
272
+ import { datePickerLang, getDateFormat } from '../../helpers/translateLang'
273
+
274
+ const PageContainer = styled.div``
275
+
276
+ const ButtonWrapper = styled.div`
277
+ position: relative;
278
+ `
279
+
280
+ const BoxContainer = styled.div`
281
+ position: absolute;
282
+ z-index: 99;
283
+ top: 8px;
284
+ left: 100%;
285
+ transform: translateX(-50%);
286
+ width: max-content;
287
+ max-width: calc(100vw - 32px);
288
+ background-color: ${(props) => props.theme.colors.white};
289
+ border: 1px solid ${(props) => props.theme.semanticColors.grey[300]};
290
+ border-radius: 4px;
291
+ padding: 24px;
292
+ `
293
+
294
+ const BoxTitle = styled.div`
295
+ font-size: 14px;
296
+ font-weight: 500;
297
+ color: ${(props) => props.theme.semanticColors.teal[800]};
298
+ margin-bottom: 16px;
299
+ `
300
+
301
+ const SortOptionsContainer = styled.div`
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: 16px;
305
+ white-space: nowrap;
306
+ `
307
+
308
+ const SortDropdownContainer = styled.div`
309
+ display: grid;
310
+ grid-template-columns: auto repeat(3, 1fr) auto;
311
+ grid-gap: 8px;
312
+ align-items: center;
313
+ `
314
+
315
+ const SelectedText = styled.div`
316
+ font-size: 14px;
317
+ font-weight: 400;
318
+ color: ${(props) => props.theme.semanticColors.grey[800]};
319
+ `
320
+
321
+ const IconContainer = styled.div`
322
+ margin-left: 12px;
323
+ `
324
+
325
+ const LeadingText = styled.div`
326
+ font-size: 14px;
327
+ font-weight: 400;
328
+ color: ${(props) => props.theme.semanticColors.teal[800]};
329
+ `
330
+
331
+ const BottomContainer = styled.div`
332
+ display: flex;
333
+ margin-top: 16px;
334
+ `
335
+
336
+ const PlaceholderText = styled.div`
337
+ visibility: hidden;
338
+ `
339
+
340
+ const OptionsContainer = styled.div`
341
+ width: max-content;
342
+ display: flex;
343
+ flex-direction: column;
344
+ `
345
+
346
+ const DatePickerContainer = styled.div`
347
+ height: 40px;
348
+ display: flex;
349
+ align-items: center;
350
+ min-width: 150px;
351
+ `
352
+
353
+ export default {
354
+ name: 'ViewSettings',
355
+ components: {
356
+ PageContainer,
357
+ ButtonIcon,
358
+ ButtonWrapper,
359
+ BoxContainer,
360
+ BoxTitle,
361
+ SortOptionsContainer,
362
+ SortDropdownContainer,
363
+ SelectComponent,
364
+ SelectOption,
365
+ SelectedText,
366
+ RCIcon,
367
+ IconContainer,
368
+ LeadingText,
369
+ BottomContainer,
370
+ PlaceholderText,
371
+ RcCheckbox,
372
+ OptionsContainer,
373
+ VueDatePicker,
374
+ DatePickerContainer,
375
+ },
376
+ props: {
377
+ sortOptions: {
378
+ type: Array,
379
+ required: true,
380
+ },
381
+ selectedSort: {
382
+ type: Array,
383
+ required: true,
384
+ },
385
+ filterCategories: {
386
+ type: Array,
387
+ required: true,
388
+ },
389
+ activeLanguage: {
390
+ type: String,
391
+ required: false,
392
+ default: 'en-us',
393
+ },
394
+ },
395
+ data() {
396
+ return {
397
+ isOptionsVisible: false,
398
+ }
399
+ },
400
+ computed: {
401
+ theme() {
402
+ return theme
403
+ },
404
+ datePickerLocale() {
405
+ return this.activeLanguage
406
+ },
407
+ datePickerFormat() {
408
+ return getDateFormat(this.activeLanguage)
409
+ },
410
+ },
411
+ beforeUnmount() {
412
+ document.removeEventListener('click', this.handleClickOutside)
413
+ },
414
+ methods: {
415
+ handleClickOutside(event) {
416
+ const isClickInSelect =
417
+ event.target.closest('.sort-dropdown') ||
418
+ event.target.closest('.rc-select-dropdown') ||
419
+ event.target.closest('.select-button') ||
420
+ event.target.closest('.caret_dropdown') ||
421
+ event.target.closest('.rc-select') ||
422
+ event.target.closest('.select-option') ||
423
+ event.target.closest('.icon') ||
424
+ event.target.closest('svg') ||
425
+ event.target.closest('.dp-input') ||
426
+ event.target.closest('.dp__menu')
427
+
428
+ if (
429
+ !this.$refs.buttonIcon.$el.contains(event.target) &&
430
+ !this.$refs.boxContainer.$el.contains(event.target) &&
431
+ !isClickInSelect
432
+ ) {
433
+ this.isOptionsVisible = false
434
+ document.removeEventListener('click', this.handleClickOutside)
435
+ }
436
+ },
437
+ toggleOptions() {
438
+ this.isOptionsVisible = !this.isOptionsVisible
439
+ if (this.isOptionsVisible) {
440
+ document.addEventListener('click', this.handleClickOutside)
441
+ } else {
442
+ document.removeEventListener('click', this.handleClickOutside)
443
+ }
444
+ },
445
+ getNumberCount() {
446
+ return this.selectedSort.filter((item) => {
447
+ const hasColumn = !!item.column
448
+ const hasConstraint = !!item.constraint
449
+ const hasSelectedOptions =
450
+ item.column === 'updated'
451
+ ? !!item.selectedDate
452
+ : !!item.selectedOptions?.length
453
+ return hasColumn && hasConstraint && hasSelectedOptions
454
+ }).length
455
+ },
456
+ constraintOptions(index) {
457
+ const generalOptions = [
458
+ {
459
+ value: 'contains_any',
460
+ label: this.$gettext('contains_any'),
461
+ },
462
+ {
463
+ value: 'contains_all',
464
+ label: this.$gettext('contains_all'),
465
+ },
466
+ {
467
+ value: 'contains_none',
468
+ label: this.$gettext('contains_none'),
469
+ },
470
+ ]
471
+
472
+ const dateOptions = [
473
+ {
474
+ value: 'is_before',
475
+ label: this.$gettext('is_before'),
476
+ },
477
+ {
478
+ value: 'is_before_or_on',
479
+ label: this.$gettext('is_before_or_on'),
480
+ },
481
+ {
482
+ value: 'is_after',
483
+ label: this.$gettext('is_after'),
484
+ },
485
+ {
486
+ value: 'is_after_or_on',
487
+ label: this.$gettext('is_after_or_on'),
488
+ },
489
+ ]
490
+
491
+ if (this.selectedSort?.[index]?.column === 'updated') {
492
+ return dateOptions
493
+ }
494
+
495
+ if (this.selectedSort?.[index]?.column === 'project_status') {
496
+ return generalOptions.filter(
497
+ (option) => option.value !== 'contains_none'
498
+ )
499
+ }
500
+
501
+ return generalOptions
502
+ },
503
+ handleDatePickerClick() {
504
+ // Close all Select dropdowns. This is a workaround to close the dropdown when the date picker is clicked
505
+ this.$refs.selectSortDropdown.forEach((select) => {
506
+ select.closeDropdown()
507
+ })
508
+ },
509
+ getSelectedLabel({ type, value, index }) {
510
+ if (!value) return
511
+ if (type === 'column') {
512
+ const categoryName = this.filterCategories.find(
513
+ (option) => option.name === value
514
+ ).text
515
+ return this.$gettext(categoryName) || '-'
516
+ } else if (type === 'constraint') {
517
+ return this.constraintOptions(index).find(
518
+ (option) => option.value === value
519
+ ).label
520
+ } else if (type === 'selectedOptions') {
521
+ const selectedOptions =
522
+ this.selectedSort[index]?.selectedOptions || []
523
+ if (selectedOptions.length === 0) return this.$gettext('Options')
524
+
525
+ const optionsList = this.optionsList(index)
526
+ const selectedLabels = selectedOptions
527
+ .map((optionValue) => {
528
+ const option = optionsList.find(
529
+ (opt) => opt.value === optionValue
530
+ )
531
+ return option ? option.label : optionValue
532
+ })
533
+ .join(', ')
534
+
535
+ return selectedLabels.length > 15
536
+ ? `${selectedLabels.substring(0, 15)}...`
537
+ : selectedLabels
538
+ }
539
+ },
540
+ onSelectFilter({ type, value, index, constraint }) {
541
+ if (
542
+ type === 'constraint' &&
543
+ (value === 'contains_all' || value === 'contains_none')
544
+ ) {
545
+ const options = this.optionsList(index)
546
+ const allValues = options.map((option) => option.value)
547
+
548
+ this.$emit('on-filter-change', {
549
+ type: 'selectedOptions',
550
+ value: value === 'contains_all' ? allValues : [],
551
+ index,
552
+ })
553
+ }
554
+
555
+ this.$emit('on-filter-change', {
556
+ type,
557
+ value,
558
+ index,
559
+ constraint,
560
+ })
561
+ if (type === 'column') {
562
+ this.$emit('on-filter-change', {
563
+ type: 'constraint',
564
+ value: '',
565
+ index,
566
+ })
567
+ }
568
+ this.isOptionsVisible = true
569
+ },
570
+ removeItem(index) {
571
+ this.$emit('on-filter-change', {
572
+ type: 'remove',
573
+ index,
574
+ })
575
+ },
576
+ onAddFilter() {
577
+ this.$emit('on-add-filter', {
578
+ type: 'add',
579
+ index: this.selectedSort.length,
580
+ })
581
+ },
582
+ optionsList(index) {
583
+ if (!this.selectedSort[index].column) return []
584
+ const findIndex = this.filterCategories.findIndex(
585
+ (category) => category.name === this.selectedSort[index].column
586
+ )
587
+ const selectedItem = this.filterCategories[findIndex]
588
+ return this.filterCategories[findIndex].options.choices.map(
589
+ (option) => ({
590
+ ...option,
591
+ value: option[selectedItem.valueSelector],
592
+ label: option[selectedItem.labelSelector],
593
+ })
594
+ )
595
+ },
596
+ },
597
+ }
598
+ </script>
599
+
600
+ <style>
601
+ .dp-input {
602
+ height: 40px !important;
603
+ min-width: 150px !important;
604
+ }
605
+ </style>