@itfin/components 1.5.2 → 1.5.3

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 (57) hide show
  1. package/package.json +12 -1
  2. package/src/ITFSettings.js +0 -6
  3. package/src/components/button/Button.vue +1 -3
  4. package/src/components/button/NativeButton.js +0 -4
  5. package/src/components/button/index.stories.js +2 -2
  6. package/src/components/card/BentoGrid.vue +0 -2
  7. package/src/components/customize/PropertiesList.vue +2 -0
  8. package/src/components/customize/PropertiesPopupMenu.vue +1 -1
  9. package/src/components/customize/PropertyItem.vue +24 -6
  10. package/src/components/datepicker/DatePicker.vue +1 -1
  11. package/src/components/datepicker/MonthPicker.vue +21 -1
  12. package/src/components/dropdown/Dropdown.vue +1 -1
  13. package/src/components/dropdown/DropdownMenu.vue +1 -1
  14. package/src/components/editable/EditButton.vue +1 -1
  15. package/src/components/filter/FilterBadge.vue +5 -4
  16. package/src/components/filter/FilterFacetsList.vue +3 -2
  17. package/src/components/filter/FilterPanel.vue +20 -6
  18. package/src/components/icon/components/nomi-calendar-view.vue +4 -0
  19. package/src/components/icon/components/nomi-help.vue +3 -2
  20. package/src/components/icon/components/nomi-kanban-view.vue +6 -0
  21. package/src/components/icon/components/nomi-list-view.vue +7 -0
  22. package/src/components/icon/components/nomi-lock.vue +1 -1
  23. package/src/components/icon/components/nomi-project.vue +2 -2
  24. package/src/components/icon/components/nomi-scissors.vue +1 -1
  25. package/src/components/icon/components/nomi-table-config.vue +9 -0
  26. package/src/components/icon/components/nomi-table-view.vue +4 -1
  27. package/src/components/icon/components/nomi-user.vue +3 -3
  28. package/src/components/icon/convert-icons.js +0 -3
  29. package/src/components/icon/icons.js +402 -397
  30. package/src/components/icon/new-icons/calendar-view.svg +3 -0
  31. package/src/components/icon/new-icons/help.svg +3 -2
  32. package/src/components/icon/new-icons/kanban-view.svg +5 -0
  33. package/src/components/icon/new-icons/list-view.svg +6 -0
  34. package/src/components/icon/new-icons/lock.svg +1 -1
  35. package/src/components/icon/new-icons/project.svg +2 -2
  36. package/src/components/icon/new-icons/scissors.svg +1 -1
  37. package/src/components/icon/new-icons/table-config.svg +8 -0
  38. package/src/components/icon/new-icons/table-view.svg +4 -1
  39. package/src/components/icon/new-icons/user.svg +3 -3
  40. package/src/components/kanban/BoardCard.vue +1 -1
  41. package/src/components/kanban/BoardCardTimer.vue +1 -1
  42. package/src/components/overlay/SensitiveOverlay.vue +4 -2
  43. package/src/components/panels/Panel.vue +21 -0
  44. package/src/components/panels/PanelList.vue +14 -3
  45. package/src/components/table/Table2.vue +65 -60
  46. package/src/components/table/TableBody.vue +6 -0
  47. package/src/components/table/TableGroup.vue +13 -4
  48. package/src/components/table/TableHeader.vue +77 -76
  49. package/src/components/table/TableRowToggle.vue +9 -1
  50. package/src/components/table/TableRows.vue +54 -30
  51. package/src/components/table/table2.scss +15 -34
  52. package/src/components/tree/TreeEditor.vue +2 -3
  53. package/src/components/view/View.vue +214 -59
  54. package/src/helpers/validators.js +35 -9
  55. package/src/helpers/validators.spec.js +48 -11
  56. package/src/locales/en.js +1 -1
  57. package/src/locales/uk.js +4 -9
@@ -4,26 +4,29 @@
4
4
  <itf-filter-panel
5
5
  :search-placeholder="searchPlaceholder"
6
6
  search
7
+ show-filter
7
8
  :visible="!noFilters"
8
9
  :filters-only="filtersOnly"
9
10
  ref="filters"
10
11
  :mini="panel.isMultiple()"
11
12
  class="py-2 px-3"
13
+ :static-filters="filters"
12
14
  :endpoint="filtersEndpoint"
13
15
  :panel="panel"
14
16
  v-model="filter"
15
17
  @loaded="onFilterSet($event, true)"
16
18
  @change="onFilterSet($event, false)"
19
+ @set-table-schema="setTableSchema"
17
20
  >
18
21
  <template #after-filter-btn>
19
- <itf-dropdown v-if="$refs.table && schema" shadow append-to-context :button-options="{ default: true, icon: true }" class="h-100" autoclose="outside">
22
+ <itf-dropdown v-if="$refs.table && tableSchema" shadow append-to-context :button-options="{ default: true, icon: true }" class="h-100" autoclose="outside">
20
23
  <template #button>
21
- <itf-icon new name="table-view" />
24
+ <itf-icon new name="table-config" />
22
25
  </template>
23
26
  <div class="dropdown-header">
24
27
  {{ $t('components.table.columns') }}
25
28
  </div>
26
- <a v-for="(property, n) of schema.properties" :key="n" href="" @click.stop.prevent="$refs.table.toggleVisibility(property.property)" class="dropdown-item justify-content-between d-flex gap-3">
29
+ <a v-for="(property, n) of tableSchema.properties" :key="n" href="" @click.stop.prevent="$refs.table.toggleVisibility(property.property)" class="dropdown-item justify-content-between d-flex gap-3">
27
30
  <div class="d-flex align-items-center gap-1">
28
31
  <itf-icon v-if="property.icon" :size="24" new :name="property.icon" />
29
32
  {{getTitle(property.title)}}
@@ -42,49 +45,87 @@
42
45
  </div>
43
46
  </a>
44
47
  </itf-dropdown>
45
- </template>
46
- </itf-filter-panel>
48
+ <itf-dropdown v-if="downloadEndpoint" shadow append-to-context :button-options="{ default: true, small: true }" class="h-100" autoclose="outside">
49
+ <template #button>
50
+ <itf-icon name="download"/>
51
+ {{ $t('export') }}
52
+ </template>
53
+ <a v-for="(item, n) in getDownloadLinks()" target="_blank" :key="n" :href="item.link" class="dropdown-item">
54
+ {{item.title}}
55
+ </a>
56
+ </itf-dropdown>
57
+
58
+ <slot name="before-tabs"></slot>
47
59
 
48
- <div class="flex-grow-1 px-3 d-flex flex-column">
49
- <div class="position-relative flex-grow-1">
50
- <itf-table
51
- ref="table"
52
- style="--shadow-area-width: 0px;"
53
- absolute
54
- striped
55
- clickable
56
- column-sorting
57
- column-resizing
58
- :indicator-type="indicatorType"
59
- class="permanent-checkboxes"
60
- divider-property="hasDivider"
61
- subrows-property="children"
62
- :state-name="stateName"
63
- id-property="id"
64
- :rows="preparedItems"
65
- :schema="schema"
66
- :sorting="sorting"
67
- :active="activeIds"
68
- :show-actions="showActions"
69
- :expanded-ids.sync="expandedIds"
70
- :indicator-width="indicatorWidth"
71
- :no-select-all="noSelectAll"
72
- v-model="selectedIds"
73
- @row-click="$emit('open', $event)"
74
- @update:sorting="updateSorting($event)"
60
+ <itf-segmented-control
61
+ v-if="tabs.length > 1"
62
+ class="small"
63
+ v-model="currentTab"
64
+ item-key="value"
65
+ :items="tabs"
75
66
  >
76
- <template v-for="(_, name) in $slots" #[name]="slotData">
77
- <slot :name="name" v-bind="slotData || {}"/>
78
- </template>
79
- <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
80
- <slot :name="name" v-bind="slotData || {}"/>
67
+ <template #item="{ item }">
68
+ <div class="d-flex align-items-center">
69
+ <itf-icon class="text-muted" new :name="item.icon" />
70
+ {{item.text}}
71
+ </div>
81
72
  </template>
82
- </itf-table>
73
+ </itf-segmented-control>
74
+ </template>
75
+ </itf-filter-panel>
76
+
77
+ <div v-if="currentTab === 'list'" class="position-relative flex-grow-1">
78
+ <div class="position-absolute" style="top: 0; left: 0; right: 0; bottom: 0; overflow: auto;">
79
+ <slot name="list-view" :items="items" :loading="loading"></slot>
83
80
  </div>
84
81
  </div>
82
+ <slot v-else-if="currentTab === 'board'" name="kanban-view"></slot>
83
+ <slot v-else-if="currentTab === 'calendar'" name="calendar-view"></slot>
84
+ <slot v-else name="table-view">
85
+ <div class="flex-grow-1 px-3 d-flex flex-column">
86
+ <div class="position-relative flex-grow-1">
87
+ <itf-table
88
+ ref="table"
89
+ style="--shadow-area-width: 0px;"
90
+ absolute
91
+ striped
92
+ clickable
93
+ column-sorting
94
+ column-resizing
95
+ :indicator-type="indicatorType"
96
+ class="permanent-checkboxes"
97
+ :state-name="stateName"
98
+ id-property="id"
99
+ divider-property="hasDivider"
100
+ subrows-property="children"
101
+ :sort-as-string="sortAsString"
102
+ :rows="preparedItems"
103
+ :group-by="groupBy"
104
+ :schema="tableSchema"
105
+ :sorting="sorting"
106
+ :active="activeIds"
107
+ :expanded-ids.sync="expandedIds"
108
+ :no-select-all="noSelectAll"
109
+ :show-actions="showActions"
110
+ :indicator-width="indicatorWidth"
111
+ v-model="selectedIds"
112
+ @row-click="$emit('open', $event)"
113
+ @update:sorting="updateSorting($event)"
114
+ >
115
+ <template v-for="(_, name) in $slots" #[name]="slotData">
116
+ <slot :name="name" v-bind="slotData || {}"/>
117
+ </template>
118
+ <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
119
+ <slot :name="name" v-bind="slotData || {}"/>
120
+ </template>
121
+ </itf-table>
122
+ </div>
123
+ </div>
124
+ </slot>
85
125
 
86
126
  <itf-pagination
87
127
  class="my-2 px-3"
128
+ v-if="showPagination"
88
129
  show-size
89
130
  :size="size"
90
131
  :items="preparedItems"
@@ -95,14 +136,14 @@
95
136
  @per-page="updateSizePerPage($event)"
96
137
  >
97
138
  <template #center>
98
- <slot name="pagination-center" />
139
+ <slot name="pagination-center" :totals="totals" />
99
140
  </template>
100
141
  </itf-pagination>
101
142
  </div>
102
143
 
103
144
  </template>
104
145
  <script>
105
- import { Vue, ModelSync, Component, Prop, Inject } from 'vue-property-decorator';
146
+ import {Vue, ModelSync, Component, Prop, Inject, PropSync, Watch} from 'vue-property-decorator';
106
147
  import loading from '../../directives/loading';
107
148
  import itfTable from '../table/Table2.vue';
108
149
  import itfFilterPanel from '../filter/FilterPanel.vue';
@@ -110,11 +151,16 @@ import itfPagination from '../pagination/Pagination2.vue';
110
151
  import itfTableBody from "../table/TableBody.vue";
111
152
  import itfIcon from "../icon/Icon.vue";
112
153
  import itfDropdown from "../dropdown/Dropdown.vue";
154
+ import itfSegmentedControl from '../segmented-control/SegmentedControl.vue';
155
+ import itfButton from '@itfin/components/src/components/button/Button.vue';
113
156
 
114
157
  export default @Component({
115
158
  name: 'itfView',
116
159
  components: {
117
- itfDropdown, itfIcon,
160
+ itfButton,
161
+ itfSegmentedControl,
162
+ itfDropdown,
163
+ itfIcon,
118
164
  itfPagination,
119
165
  itfFilterPanel,
120
166
  itfTableBody,
@@ -132,23 +178,33 @@ class itfView extends Vue {
132
178
  @Prop({ type: Boolean }) filtersOnly;
133
179
  @Prop({ type: Boolean }) noFilters;
134
180
  @Prop({ type: Array }) filters;
135
- @Prop({ type: Object, required: true }) schema;
136
- // @Prop({ default: 20 }) size;
137
- // @Prop({ default: 1 }) page;
181
+ @Prop({ type: Object }) schema;
138
182
  @Prop(String) defaultSorting;
139
183
  @Prop(String) endpoint;
140
184
  @Prop(String) filtersEndpoint;
141
185
  @Prop(String) itemsKey;
186
+ @Prop({ type: String, default: null }) groupBy;
187
+ @Prop({ type: String, default: null }) downloadEndpoint; // префікс апі для завантаження
188
+ @Prop({ type: String, default: 'totals' }) totalsKey;
142
189
  @Prop(String) panelKey;
143
190
  @Prop(String) stateName;
144
191
  @Prop({ type: String, default: 'checkbox' }) indicatorType;
192
+ @Prop({ type: String, default: 'list' }) tab;
145
193
  @Prop({ type: String, default () { return this.$t('components.table.search'); } }) searchPlaceholder;
146
194
  @Prop() panel;
147
195
  @Prop() hardFilter; // встановлює жорсткий фільтр, наприклад, треба завжди показувати лише по контрагенту
148
196
  @Prop(Boolean) showActions;
197
+ @Prop({ type: Boolean, default: true }) showPagination;
198
+ @Prop(Boolean) listViewEnabled;
199
+ @Prop(Boolean) kanbanViewEnabled;
200
+ @Prop(Boolean) calendarViewEnabled;
201
+ @Prop(Boolean) noSelectAll;
202
+ @Prop({ type: Boolean, default: true }) tableViewEnabled;
203
+ @Prop(Boolean) sortAsString;
204
+ @Prop(Boolean) oldFormat;
149
205
  @Prop({ type: Array, default: () => [] }) disabledIds;
150
206
  @Prop({ default: 45 }) indicatorWidth;
151
- @Prop(Boolean) noSelectAll;
207
+ @Prop({type: Function, default: null }) onSplitSlectedIds // якщо потрібно розділяти вибрані рядки в таблиці на дві групи
152
208
  @Prop({ type: Boolean, default: false }) showTotalCount;
153
209
 
154
210
  page = 1;
@@ -159,6 +215,8 @@ class itfView extends Vue {
159
215
  filter = {};
160
216
  loadingData = false;
161
217
  activeIds = [];
218
+ tableColumns = null;
219
+ totals = null;
162
220
  expandedIds = [];
163
221
 
164
222
  get preparedItems() {
@@ -166,6 +224,73 @@ class itfView extends Vue {
166
224
  return this.items.map(item => ({ ...item, isDisabled: disabledIdsSet.has(item.id) }));
167
225
  }
168
226
 
227
+ @Watch('selectedIds', { deep: true, immediate: true })
228
+ updateSelectedIds() {
229
+ if(this.onSplitSlectedIds && this.items.length && this.selectedIds.length) {
230
+ this.onSplitSlectedIds(this.selectedIds, this.items);
231
+ }
232
+ }
233
+
234
+ getDownloadLinks() {
235
+ const state = this.$refs.table ? this.$refs.table.getTableState() : null;
236
+ const filter = { ...this.filter };
237
+ const sorting = this.sorting;
238
+ const filterableColumnsNames = (state?.columns ?? []).filter(column => column.visible).map(column => column.property);
239
+
240
+ const filterWithValue = Object.fromEntries(Object.entries(filter).filter(([_, value]) => typeof value !== 'undefined'));
241
+ const params = {
242
+ ...filterWithValue,
243
+ sort: sorting
244
+ };
245
+ if (filterableColumnsNames.length) {
246
+ params.columns = filterableColumnsNames.join(',')
247
+ }
248
+ const xlsQueryParams = new URLSearchParams({ ...params, format: 'xlsx' }).toString();
249
+ const csvQueryParams = new URLSearchParams({ ...params, format: 'csv' }).toString();
250
+ return [
251
+ { title: 'Excel (xlsx)', link: `${this.downloadEndpoint}?${xlsQueryParams}` },
252
+ { title: 'Plain text (csv)', link: `${this.downloadEndpoint}?${csvQueryParams}` },
253
+ ];
254
+ }
255
+
256
+ get currentTab() {
257
+ return this.tab;
258
+ }
259
+
260
+ set currentTab(val) {
261
+ this.$emit('update:tab', val);
262
+ this.setPanelPayload({ tab: val });
263
+ }
264
+
265
+ get tabs() {
266
+ const views = [];
267
+ if (this.listViewEnabled) {
268
+ views.push({ value: 'list', text: this.$t('list'), icon: 'list-view' });
269
+ }
270
+ if (this.kanbanViewEnabled) {
271
+ views.push({ value: 'board', text: this.$t('board'), icon: 'kanban-view' });
272
+ }
273
+ if (this.calendarViewEnabled) {
274
+ views.push({ value: 'calendar', text: this.$t('calendar'), icon: 'calendar-view' });
275
+ }
276
+ if (this.tableViewEnabled) {
277
+ views.push({ value: 'table', text: this.$t('table'), icon: 'table-view' });
278
+ }
279
+ return views;
280
+ }
281
+
282
+ get tableSchema() {
283
+ if (this.tableColumns) {
284
+ return {
285
+ properties: this.tableColumns
286
+ }
287
+ } else if (this.schema) {
288
+ return this.schema
289
+ } else {
290
+ return {}
291
+ }
292
+ }
293
+
169
294
  created() {
170
295
  const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
171
296
 
@@ -195,29 +320,42 @@ class itfView extends Vue {
195
320
  this.$emit('load', this.filter);
196
321
  this.loadingData = true;
197
322
  await this.$try(async () => {
198
- const res = await this.$axios.$get(this.endpoint, {
323
+ let filter = { ...this.filter };
324
+ if (this.oldFormat) {
325
+ filter = Object.keys(filter).reduce((acc, key) => {
326
+ acc[`filter[${key}]`] = filter[key];
327
+ return acc;
328
+ }, {})
329
+ }
330
+ this.selectedIds = [];
331
+ const { data, headers } = await this.$axios.get(this.endpoint, {
199
332
  preventRaceCondition: true,
200
333
  params: {
201
- ...Object.assign(this.filter ?? {}, this.hardFilter ?? {}),
334
+ ...filter,
202
335
  page: this.page,
203
336
  size: this.size,
204
337
  sort: this.sorting
205
338
  }
206
339
  });
207
- this.selectedIds = [];
208
- this.items = res[this.itemsKey];
209
- this.page = res.meta.page;
210
- this.total = res.meta.total;
211
- this.size = res.meta.size;
212
- if (res.columns) {
213
- this.$emit('columns-loaded', res.columns);
340
+ if (this.oldFormat) {
341
+ this.items = data;
342
+ this.page = Number(headers['x-page'] ?? 1);
343
+ this.total = Number(headers['x-count'] ?? 0);
344
+ this.size = Number(headers['x-size'] ?? 20);
345
+ } else {
346
+ this.items = data[this.itemsKey];
347
+ this.totals = data[this.totalsKey];
348
+ this.page = data.meta.page;
349
+ this.total = data.meta.total;
350
+ this.size = data.meta.size;
214
351
  }
215
-
216
- this.$emit('update:items', this.items);
217
- this.loadingData = false;
218
- }, () => {
219
- this.loadingData = false;
220
352
  });
353
+ if (res.columns) {
354
+ this.$emit('columns-loaded', res.columns);
355
+ }
356
+
357
+ this.$emit('update:items', this.items);
358
+ this.loadingData = false;
221
359
  this.$emit('loaded', this.filter);
222
360
  }
223
361
 
@@ -247,6 +385,7 @@ class itfView extends Vue {
247
385
  updateSizePerPage (val) {
248
386
  this.page = 1;
249
387
  this.size = val;
388
+ this.selectedIds = [];
250
389
  this.setPanelPayload({ page: null, size: val });
251
390
  this.loadData();
252
391
  localStorage.setItem('sizePerPage', val);
@@ -283,10 +422,26 @@ class itfView extends Vue {
283
422
  this.loadData();
284
423
  }
285
424
 
425
+ setTableSchema(tableSchema) {
426
+ this.tableColumns = tableSchema;
427
+ }
428
+
429
+ getSorting() {
430
+ return this.sorting;
431
+ }
432
+
286
433
  loadFilters() {
287
434
  if (this.filtersEndpoint) {
288
435
  this.$refs.filters?.loadData();
289
436
  }
290
437
  }
438
+
439
+ getFilter() {
440
+ return this.filter;
441
+ }
442
+
443
+ getTableState() {
444
+ return this.$refs.table ? this.$refs.table.getTableState() : null;
445
+ }
291
446
  }
292
447
  </script>
@@ -8,12 +8,12 @@ const LINKED_IN_REGEXP = /(http(s)?:\/\/)?([\w]+\.)?linkedin\.com\/(pub|in|profi
8
8
  const SPECIAL_CHARS_REGEXP = /^[\w_\-+~,/\\:'"().&*|[\]?# ]+$/i;
9
9
  const GREETINGS_REGEXP = /\b(dr|mr|mister|mrs|ms|miss|sir|hello|hi)\b/i;
10
10
  const PHONE_REGEXP = /(\+?\(?\+?[0-9]{1,3}\)?[-. ]+([0-9]{2,4})[-. ]?([0-9]{3,5}))|\+?[0-9]{7,}/gi;
11
- const INTEGER_REGEXP = /^-?\d{0,11}$/;
12
11
  const DOUBLE_REGEXP = /^-?\d{0,11}(\.\d{0,8}){0,1}$/;
13
- const EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
12
+ const EMAIL_REGEXP = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/g;
14
13
  const EMAIL_LIST_REGEXP = /^(\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]{2,4}\s*?,?\s*?)+$/g;
15
14
  const HEX_REGEXP = /[0-9A-Fa-f]{6}/;
16
- const PASSWORD_REGEX = /^(?=.*\p{Lu})(?=.*\p{Ll})(?=.*\d)(?=.*[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]).{8,}$/u;
15
+ const PASSWORD_MIN_LENGTH = 8;
16
+ const PASSWORD_MAX_LENGTH = 50;
17
17
 
18
18
  const STANDART_DATE_FORMAT = 'yyyy-MM-dd';
19
19
 
@@ -67,10 +67,6 @@ export function emptyArrayValidation (message) {
67
67
  return (v, $t = (s) => s) => (Array.isArray(v) && v.length > 0) || (message || $t('components.thisFieldIsRequired'));
68
68
  }
69
69
 
70
- export function integerValidation (message) {
71
- return (v, $t = (s) => s) => !v || !!(v + '').match(INTEGER_REGEXP) || (message || $t('components.thisFieldMustBeANumberFormatZero'));
72
- }
73
-
74
70
  export function doubleValidation (message) {
75
71
  return (v, $t = (s) => s) => !v || !!(v + '').match(DOUBLE_REGEXP) || (message || $t('components.thisFieldMustBeANumberFormatZero'));
76
72
  }
@@ -188,8 +184,38 @@ export function dateSameOrBeforeValidation (dateCompare, message) {
188
184
  };
189
185
  }
190
186
 
191
- export function passwordValidation(message) {
187
+ export function hasUppercaseValidation(message) {
188
+ return (v, $t) => {
189
+ return !isEmpty(v) && /\p{Lu}/u.test(v + '') || (message || $t('components.atLeastOneUppercaseLetterRequired'));
190
+ };
191
+ }
192
+
193
+ export function hasLowercaseValidation(message) {
194
+ return (v, $t) => {
195
+ return !isEmpty(v) && /\p{Ll}/u.test(v + '') || (message || $t('components.atLeastOneLowercaseLetterRequired'));
196
+ };
197
+ }
198
+
199
+ export function hasDigitValidation(message) {
200
+ return (v, $t) => {
201
+ return !isEmpty(v) && /\d/.test(v + '') || (message || $t('components.atLeastOneNumberRequired'));
202
+ };
203
+ }
204
+
205
+ export function hasSpecialCharValidation(message) {
192
206
  return (v, $t) => {
193
- return !isEmpty(v) && PASSWORD_REGEX.test(v.trim() + '') || (message || $t('components.passwordValidation'));
207
+ return !isEmpty(v) && /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(v + '') || (message || $t('components.atLeastOneSpecialCharacterRequired'));
194
208
  };
195
209
  }
210
+
211
+ export function accountPasswordValidation(message) {
212
+ return [
213
+ emptyValidation(message),
214
+ minLengthValidation(PASSWORD_MIN_LENGTH, message),
215
+ lengthValidation(PASSWORD_MAX_LENGTH, message),
216
+ hasUppercaseValidation(message),
217
+ hasLowercaseValidation(message),
218
+ hasDigitValidation(message),
219
+ hasSpecialCharValidation(message),
220
+ ];
221
+ }
@@ -7,7 +7,10 @@ import {
7
7
  dateAfterValidation,
8
8
  dateSameOrAfterValidation,
9
9
  dateSameOrBeforeValidation,
10
- passwordValidation,
10
+ hasUppercaseValidation,
11
+ hasLowercaseValidation,
12
+ hasDigitValidation,
13
+ hasSpecialCharValidation,
11
14
  } from './validators';
12
15
 
13
16
  describe('Validators', () => {
@@ -83,16 +86,50 @@ describe('Validators', () => {
83
86
  expect(mediumTextValidation()('0'.repeat(16777216), $t)).toEqual('components.mediumTextLength');
84
87
  });
85
88
 
89
+ test('hasUppercaseValidation', () => {
90
+ expect(hasUppercaseValidation()('', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
91
+ expect(hasUppercaseValidation()(' ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
92
+ expect(hasUppercaseValidation()(null, $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
93
+ expect(hasUppercaseValidation()(undefined, $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
94
+ expect(hasUppercaseValidation()('A', $t)).toEqual(true);
95
+ expect(hasUppercaseValidation()(' Test ', $t)).toEqual(true);
96
+ expect(hasUppercaseValidation()(' test ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
97
+ expect(hasUppercaseValidation()(' Тест ', $t)).toEqual(true);
98
+ expect(hasUppercaseValidation()(' тест ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
99
+ expect(hasUppercaseValidation()(' ТЕСТ ', $t)).toEqual(true);
100
+ });
101
+
102
+ test('hasLowercaseValidation', () => {
103
+ expect(hasLowercaseValidation()('', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
104
+ expect(hasLowercaseValidation()(' ', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
105
+ expect(hasLowercaseValidation()(null, $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
106
+ expect(hasLowercaseValidation()(undefined, $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
107
+ expect(hasLowercaseValidation()('a', $t)).toEqual(true);
108
+ expect(hasLowercaseValidation()(' test ', $t)).toEqual(true);
109
+ expect(hasLowercaseValidation()('A', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
110
+ expect(hasLowercaseValidation()(' Test ', $t)).toEqual(true);
111
+ expect(hasLowercaseValidation()(' Тест ', $t)).toEqual(true);
112
+ expect(hasLowercaseValidation()(' тест ', $t)).toEqual(true);
113
+ expect(hasLowercaseValidation()(' ТЕСТ ', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
114
+ })
115
+
116
+ test('hasDigitValidation', () => {
117
+ expect(hasDigitValidation()('', $t)).toEqual('components.atLeastOneNumberRequired');
118
+ expect(hasDigitValidation()(' ', $t)).toEqual('components.atLeastOneNumberRequired');
119
+ expect(hasDigitValidation()(null, $t)).toEqual('components.atLeastOneNumberRequired');
120
+ expect(hasDigitValidation()(undefined, $t)).toEqual('components.atLeastOneNumberRequired');
121
+ expect(hasDigitValidation()('1', $t)).toEqual(true);
122
+ expect(hasDigitValidation()(' 1 ', $t)).toEqual(true);
123
+ expect(hasDigitValidation()('a', $t)).toEqual('components.atLeastOneNumberRequired');
124
+ });
86
125
 
87
- test('passwordValidation', () => {
88
- expect(passwordValidation()('', $t)).toEqual('components.passwordValidation');
89
- expect(passwordValidation()(' ', $t)).toEqual('components.passwordValidation');
90
- expect(passwordValidation()(null, $t)).toEqual('components.passwordValidation');
91
- expect(passwordValidation()(undefined, $t)).toEqual('components.passwordValidation');
92
- expect(passwordValidation()(' тесттест ', $t)).toEqual('components.passwordValidation');
93
- expect(passwordValidation()(' ТЕСТТЕСТ ', $t)).toEqual('components.passwordValidation');
94
- expect(passwordValidation()(' ТестТест ', $t)).toEqual('components.passwordValidation');
95
- expect(passwordValidation()(' ТестТест1 ', $t)).toEqual('components.passwordValidation');
96
- expect(passwordValidation()(' ТестТест1@ ', $t)).toEqual(true);
126
+ test('hasSpecialCharValidation', () => {
127
+ expect(hasSpecialCharValidation()('', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
128
+ expect(hasSpecialCharValidation()(' ', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
129
+ expect(hasSpecialCharValidation()(null, $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
130
+ expect(hasSpecialCharValidation()(undefined, $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
131
+ expect(hasSpecialCharValidation()('@', $t)).toEqual(true);
132
+ expect(hasSpecialCharValidation()(' @ ', $t)).toEqual(true);
133
+ expect(hasSpecialCharValidation()('a', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
97
134
  });
98
135
  });
package/src/locales/en.js CHANGED
@@ -104,7 +104,7 @@ module.exports = {
104
104
  copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
105
105
  },
106
106
  table: {
107
- new: 'New',
107
+ new: 'New row',
108
108
  noResults: 'No items',
109
109
  sortAscending: 'Sort ascending',
110
110
  sortDescending: 'Sort descending',
package/src/locales/uk.js CHANGED
@@ -28,8 +28,7 @@ module.exports = {
28
28
  pleaseEnterValidURL: 'Будь ласка, введіть дійсний URL',
29
29
  pleaseEnteraValidHex: 'Будь ласка, введіть дійсний код кольору',
30
30
  passwordsDontMatch: 'Паролі не збігаються',
31
- thisFieldMustBeAIntegerFormatZero: 'Це поле має містити число',
32
- thisFieldMustBeANumberFormatZero: 'Це поле має містити число (формат 0.00)',
31
+ thisFieldMustBeANumberFormatZero: 'Це поле має містити числом (формат 0.00)',
33
32
  theValueMustBeGreaterThanOrEqualToMinAndMax: 'Значення має бути більше або дорівнювати {min} і менше або дорівнювати {max}',
34
33
  theValueMustBeGreaterThanOrEqualToMin: 'Значення має бути більше або дорівнювати {min}',
35
34
  mustBeLessThanMax: 'Має бути менше ніж {max}',
@@ -61,7 +60,6 @@ module.exports = {
61
60
  atLeastOneLowercaseLetterRequired: 'Має бути принаймні одна мала літера',
62
61
  atLeastOneNumberRequired: 'Має бути принаймні одна цифра',
63
62
  atLeastOneSpecialCharacterRequired: 'Має бути принаймні один спеціальний символ (@,$,!,%,*,?,&)',
64
- dropzoneText: 'Натисніть або перетягніть файл для завантаження',
65
63
 
66
64
  select: {
67
65
  loading: 'Завантаження...',
@@ -105,12 +103,12 @@ module.exports = {
105
103
  copyingToClipboardWasSuccessful: 'Скопійовано в буфер',
106
104
  },
107
105
  pagination: {
108
- itemsPerPage: 'На сторінці',
106
+ itemsPerPage: 'К-ть на сторінці',
109
107
  previous: 'Попередня',
110
108
  next: 'Наступна',
111
109
  },
112
110
  table: {
113
- new: 'Додати',
111
+ new: 'Додати рядок',
114
112
  noResults: 'Немає записів',
115
113
  sortAscending: 'Сортувати за зростанням',
116
114
  sortDescending: 'Сортувати за спаданням',
@@ -158,8 +156,5 @@ module.exports = {
158
156
  from: 'Від',
159
157
  to: 'До',
160
158
  value: 'Значення',
161
- },
162
- passwordValidation: 'Пароль має містити принаймні 8 символів, включаючи великі літери, малі літери, цифри та спеціальні символи (@, $, !, %, *, ?, &)',
163
- iveGotIt: 'Зрозуміло',
164
- totalItems: 'Всього',
159
+ }
165
160
  };