@itfin/components 1.0.52 → 1.0.56

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,7 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.0.52",
4
-
3
+ "version": "1.0.56",
5
4
  "main": "dist/itfin-components.umd.js",
6
5
  "unpkg": "dist/itfin-components.common.js",
7
6
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
@@ -31,7 +30,6 @@
31
30
  "lodash": "^4.17.20",
32
31
  "luxon": "^2.0.2",
33
32
  "pdfjs-dist": "^2.10.377",
34
- "ramjet": "^0.5.0",
35
33
  "tippy.js": "^6.3.2",
36
34
  "vue": "^2.6.12",
37
35
  "vue-imask": "^6.2.2",
@@ -67,6 +67,8 @@ $dark-btn-secondary-color: $dark-secondary;
67
67
  $dark-input-box-shadow: #3d3d3d;
68
68
  $dark-input-focus-border: rgb(244 206 176 / 25%);
69
69
 
70
+ $pagination-border-width: 0;
71
+
70
72
  // 3. Include remainder of required Bootstrap stylesheets
71
73
  @import "~bootstrap/scss/variables.scss";
72
74
  @import "~bootstrap/scss/mixins.scss";
@@ -81,9 +81,6 @@ export default @Component({
81
81
  })
82
82
  class itfApp extends Vue {
83
83
  @Provide() globalApp = this;
84
- @Provide() appendContext = function appendContext() {
85
- return globalApp.$el;
86
- };
87
84
 
88
85
  created() {
89
86
  globalApp = this;
@@ -58,7 +58,7 @@ export default @Component({
58
58
  name: 'itfAvatar',
59
59
  })
60
60
  class itfAvatar extends Vue {
61
- @Prop({ type: Number, default: 32 }) size;
61
+ @Prop({ type: [String, Number], default: 32 }) size;
62
62
 
63
63
  @Prop({ type: String, default: null }) src;
64
64
 
@@ -9,7 +9,7 @@
9
9
  :class="{'active': n === items.length - 1}"
10
10
  :aria-current="n === items.length - 1 ? 'page' : null"
11
11
  >
12
- <span v-if="crumb.disabled">{{crumb.text}}</span>
12
+ <span v-if="crumb.disabled || !crumb.to">{{crumb.text}}</span>
13
13
  <nuxt-link v-else :to="crumb.to" :exact="crumb.exact">{{crumb.text}}</nuxt-link>
14
14
  </li>
15
15
  </ol>
@@ -163,7 +163,7 @@ class itfButton extends Vue {
163
163
  @Prop(String) target;
164
164
 
165
165
  get isLink() {
166
- return this.to || this.href;
166
+ return this.href || this.to;
167
167
  }
168
168
  }
169
169
  </script>
@@ -74,7 +74,7 @@ import { IMask, IMaskComponent } from 'vue-imask';
74
74
  import { DateTime } from 'luxon';
75
75
  import { debounce } from '../../helpers/debounce';
76
76
  import tippy from 'tippy.js';
77
- import itfIcon from '../icon/Icon';
77
+ import itfIcon from '../icon/Icon.vue';
78
78
  import itfDatePickerInline from './DatePickerInline.vue';
79
79
  import ITFSettings from '../../ITFSettings';
80
80
 
@@ -88,7 +88,6 @@ export default @Component({
88
88
  })
89
89
  class itfDatePicker extends Vue {
90
90
  @Inject({ default: null }) itemLabel;
91
- @Inject({ default: null }) appendContext;
92
91
 
93
92
  @Prop({ type: String }) value;
94
93
  @Prop({ type: [Number, String], default: 300 }) delayInput;
@@ -131,7 +130,7 @@ class itfDatePicker extends Vue {
131
130
 
132
131
  mounted() {
133
132
  // якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
134
- const context = (this.appendContext && this.appendContext()) || document.body;
133
+ const context = this.$el.closest('.itf-append-context') || document.body;
135
134
  this.tooltip = tippy(this.$refs.input.$el, {
136
135
  interactiveBorder: 30,
137
136
  interactiveDebounce: 75,
@@ -118,9 +118,8 @@ class itfDatePickerInline extends Vue {
118
118
  }
119
119
  if (this.range) {
120
120
  this.updateValue(
121
- DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat).
122
- false,
123
- DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat)
121
+ DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat),
122
+ false
124
123
  );
125
124
  } else {
126
125
  this.updateValue(DateTime.fromJSDate(date).toFormat(this.displayFormat));
@@ -131,7 +130,10 @@ class itfDatePickerInline extends Vue {
131
130
  if (!this.customDays[strDate]) {
132
131
  return { html: false, classes: false };
133
132
  }
134
- return { html: this.customDays[strDate].text || false, classes: this.customDays[strDate].class || false };
133
+ return {
134
+ html: this.customDays[strDate].text || false,
135
+ classes: this.customDays[strDate].class || false
136
+ };
135
137
  },
136
138
  });
137
139
  if (this.valueAsLuxon) {
@@ -146,7 +146,6 @@ export default @Component({
146
146
  })
147
147
  class itfDateRangePicker extends Vue {
148
148
  @Inject({ default: null }) itemLabel;
149
- @Inject({ default: null }) appendContext;
150
149
 
151
150
  @Prop({ default: () => ([]) }) value;
152
151
  @Prop({ type: String, default: 'ISO' }) valueFormat;
@@ -189,7 +188,7 @@ class itfDateRangePicker extends Vue {
189
188
 
190
189
  mounted() {
191
190
  // якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
192
- const context = (this.appendContext && this.appendContext()) || document.body;
191
+ const context = this.$el.closest('.itf-append-context') || document.body;
193
192
  this.tooltip = tippy(this.$refs.group, {
194
193
  interactiveBorder: 30,
195
194
  interactiveDebounce: 75,
@@ -80,7 +80,6 @@ export default @Component({
80
80
  })
81
81
  class itfMonthPicker extends Vue {
82
82
  @Inject({ default: null }) itemLabel;
83
- @Inject({ default: null }) appendContext;
84
83
 
85
84
  @Prop({ type: String }) value;
86
85
  @Prop({ type: String, default: 'ISO' }) valueFormat;
@@ -102,7 +101,7 @@ class itfMonthPicker extends Vue {
102
101
 
103
102
  mounted() {
104
103
  // якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
105
- const context = (this.appendContext && this.appendContext()) || document.body;
104
+ const context = this.$el.closest('.itf-append-context') || document.body;
106
105
  this.tooltip = tippy(this.$refs.input, {
107
106
  interactiveBorder: 30,
108
107
  interactiveDebounce: 75,
@@ -233,6 +233,7 @@ class itfPeriodPicker extends Vue {
233
233
  }
234
234
 
235
235
  mounted() {
236
+ const context = this.$el.closest('.itf-append-context') || document.body;
236
237
  this.tooltip = tippy(this.$refs.input, {
237
238
  interactiveBorder: 30,
238
239
  interactiveDebounce: 75,
@@ -243,7 +244,7 @@ class itfPeriodPicker extends Vue {
243
244
  trigger: 'click',
244
245
  interactive: true,
245
246
  placement: this.placement,
246
- appendTo: document.body,
247
+ appendTo: context,
247
248
  });
248
249
  }
249
250
 
@@ -51,9 +51,6 @@ export default @Component({
51
51
  name: 'itfModal',
52
52
  components: {
53
53
  itfButton
54
- },
55
- provide() {
56
- return { appendContext: () => this.$refs.content };
57
54
  }
58
55
  })
59
56
  class itfModal extends Vue {
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
 
3
- <div class="b-sensitive-overlay">
3
+ <div class="itf-sensitive-overlay">
4
4
  <slot></slot>
5
5
 
6
- <div class="b-sensitive-overlay__cover">
6
+ <div class="itf-sensitive-overlay__cover">
7
7
  <slot name="hint">
8
8
  <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 104.8 122.88" style="enable-background:new 0 0 104.8 122.88" xml:space="preserve"><g><path fill="currentColor" d="M39.92,0c11.02,0,21,4.47,28.23,11.69c7.22,7.22,11.69,17.2,11.69,28.23c0,3.8-0.53,7.47-1.52,10.95l-3.7-2.53 c0.65-2.7,1-5.52,1-8.42c0-9.86-4-18.78-10.46-25.24C58.7,8.21,49.78,4.22,39.92,4.22c-9.86,0-18.78,4-25.24,10.46 C8.21,21.13,4.22,30.06,4.22,39.92c0,9.86,4,18.78,10.46,25.24c4.79,4.79,10.93,8.22,17.8,9.68l0.34,4.36 c-8.17-1.47-15.48-5.43-21.11-11.06C4.47,60.92,0,50.94,0,39.92c0-11.02,4.47-21,11.69-28.23C18.91,4.47,28.89,0,39.92,0L39.92,0z M81.84,122.5c-0.89,0.41-1.88,0.48-2.76,0.25c-0.97-0.26-1.83-0.87-2.36-1.78l-9.91-17.08l-9.42,10.57 c-1.31,1.47-2.8,2.58-4.24,3.16c-1.11,0.45-2.2,0.59-3.22,0.37c-1.13-0.24-2.09-0.89-2.8-2c-0.56-0.89-0.93-2.07-1.05-3.59 l-5.25-68.42c-0.01-0.05-0.01-0.11-0.01-0.15c-0.01-0.43,0.07-0.85,0.25-1.23c0.19-0.44,0.51-0.84,0.91-1.13 c0.17-0.13,0.36-0.22,0.56-0.27c0.39-0.13,0.8-0.18,1.19-0.13c0.38,0.04,0.75,0.15,1.09,0.35c0.11,0.05,0.23,0.12,0.33,0.2 l56.52,38.7c1.25,0.86,2.09,1.77,2.58,2.7c0.62,1.17,0.69,2.32,0.33,3.42c-0.32,0.99-1,1.87-1.93,2.6 c-1.21,0.95-2.92,1.69-4.86,2.09c-0.02,0.01-0.05,0.01-0.07,0.01L83.92,94l9.84,17.13c0.52,0.91,0.62,1.96,0.36,2.92 c-0.25,0.94-0.85,1.8-1.72,2.37c-0.03,0.03-0.07,0.05-0.11,0.07l-10.21,5.9C81.99,122.43,81.92,122.47,81.84,122.5L81.84,122.5 L81.84,122.5z M79.99,119.28c0.1,0.03,0.2,0.03,0.29-0.01c0.03-0.02,0.07-0.04,0.11-0.05l10.08-5.82c0.09-0.07,0.15-0.17,0.19-0.27 c0.02-0.09,0.02-0.17-0.01-0.22L79.11,93.16h0.01c-0.09-0.16-0.16-0.34-0.2-0.53c-0.2-0.97,0.43-1.91,1.39-2.11l16.22-2.88 c0.02-0.01,0.04-0.01,0.07-0.01c1.4-0.29,2.58-0.79,3.38-1.41c0.39-0.31,0.65-0.61,0.74-0.9c0.06-0.18,0.03-0.4-0.1-0.65 c-0.23-0.43-0.69-0.89-1.43-1.41L45.01,46.02l5.12,65.4c0.07,0.91,0.25,1.54,0.5,1.94c0.15,0.23,0.33,0.37,0.51,0.41 c0.3,0.07,0.69-0.01,1.15-0.19c0.93-0.37,1.94-1.15,2.9-2.22l10.11-12.18l0,0c0.13-0.14,0.27-0.26,0.44-0.36 c0.85-0.49,1.95-0.21,2.44,0.65l11.63,19.69C79.85,119.22,79.91,119.26,79.99,119.28L79.99,119.28L79.99,119.28z M80.39,119.22 c0.23-0.11,0.5-0.18,0.78-0.18L80.39,119.22L80.39,119.22L80.39,119.22z M39.92,27.34c3.47,0,6.62,1.41,8.89,3.69 c0.23,0.23,0.46,0.48,0.67,0.73c-0.12-0.04-0.24-0.08-0.36-0.12c-0.65-0.21-1.35-0.37-2.09-0.48c-0.23-0.04-0.47-0.08-0.71-0.1 c-1.05-0.12-2.14-0.12-3.23,0c-0.15,0.02-0.3,0.04-0.46,0.06c-0.86-0.27-1.78-0.41-2.73-0.41c-2.55,0-4.86,1.03-6.52,2.69 l-0.01,0.01c-1.66,1.66-2.69,3.97-2.69,6.52c0,1.3,0.11,1.34-0.16,2.69c-0.14,0.68-0.22,1.44-0.24,2.28 c-0.04,0.56-0.04,1.13,0,1.71l0.12,1.58c-1.92-2.21-3.08-5.09-3.08-8.25c0-3.46,1.41-6.61,3.69-8.89l0.01-0.01 C33.31,28.74,36.45,27.34,39.92,27.34L39.92,27.34z M39.92,13.68c7.24,0,13.8,2.94,18.55,7.68c4.75,4.75,7.68,11.31,7.68,18.55 c0,0.86-0.04,1.7-0.12,2.54l-3.85-2.64c-0.03-6.11-2.51-11.64-6.52-15.64c-4.03-4.03-9.59-6.52-15.74-6.52 c-6.15,0-11.71,2.49-15.74,6.52c-4.03,4.03-6.52,9.59-6.52,15.74c0,6.15,2.49,11.71,6.52,15.74c2.04,2.04,4.48,3.69,7.19,4.82 l0.34,4.37c-3.94-1.3-7.47-3.51-10.34-6.37c-4.75-4.75-7.68-11.31-7.68-18.55c0-7.24,2.94-13.8,7.68-18.55 C26.11,16.62,32.67,13.68,39.92,13.68L39.92,13.68z"/></g></svg>
9
9
 
@@ -18,7 +18,7 @@
18
18
 
19
19
  </template>
20
20
  <style lang="scss">
21
- .b-sensitive-overlay {
21
+ .itf-sensitive-overlay {
22
22
  position: relative;
23
23
  min-height: 100px;
24
24
 
@@ -0,0 +1,127 @@
1
+ <template>
2
+
3
+ <nav aria-label="Page navigation example">
4
+ <ul class="pagination itf-pagination">
5
+ <li
6
+ v-for="(page, n) in pagesArr"
7
+ :key="n"
8
+ class="page-item"
9
+ :class="{'active': page.current, 'disabled': !page.active && !page.current }"
10
+ >
11
+ <a v-if="page.type === 'prev'" href="" class="page-link" aria-label="Previous" @click.prevent="onPage(page.number)">
12
+ <itf-icon name="chevron_left" aria-hidden="true" />
13
+ <span class="sr-only">Previous</span>
14
+ </a>
15
+ <a v-else-if="page.type === 'first'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
16
+ <a v-else-if="page.type === 'page'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
17
+ <a v-else-if="page.type === 'last'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
18
+ <span v-else-if="page.type === 'more'" class="page-link">&hellip;</span>
19
+ <a v-if="page.type === 'next'" href="" class="page-link" aria-label="Next" @click.prevent="onPage(page.number)">
20
+ <span class="sr-only">Next</span>
21
+ <itf-icon name="chevron_right" aria-hidden="true" />
22
+ </a>
23
+ </li>
24
+ </ul>
25
+ </nav>
26
+
27
+ </template>
28
+ <style lang="scss">
29
+ @import '../../assets/scss/variables';
30
+ @import '~bootstrap/scss/pagination';
31
+
32
+ .itf-pagination.pagination {
33
+ padding-left: 0;
34
+
35
+ .page-item {
36
+ margin-right: 3px;
37
+ .page-link {
38
+ border-radius: .5rem;
39
+ }
40
+ }
41
+ }
42
+ </style>
43
+ <script>
44
+ import { Vue, Component, Model, Prop } from 'vue-property-decorator';
45
+ import itfIcon from '../icon/Icon.vue';
46
+
47
+ const MIN_PAGES_BLOCKS = 2;
48
+ const MAX_PAGES_BLOCKS = 6;
49
+
50
+ export default @Component({
51
+ name: 'itfPagination',
52
+ components: {
53
+ itfIcon
54
+ }
55
+ })
56
+ class itfPagination extends Vue {
57
+ @Model('input') value;
58
+ @Prop({ type: [String, Number], default: 0 }) length;
59
+ @Prop({ type: [String, Number], default: 20 }) size;
60
+ @Prop({ type: [String, Number] }) pages;
61
+ @Prop({ type: [String, Number], default: MIN_PAGES_BLOCKS }) minBlocks;
62
+ @Prop({ type: [String, Number], default: MAX_PAGES_BLOCKS }) maxBlocks;
63
+
64
+ onPage(page) {
65
+ this.$emit('input', page);
66
+ }
67
+
68
+ get pagesArr () {
69
+ const pageSize = Number(this.size);
70
+ const totalItems = Number(this.length);
71
+ const currentPage = this.value;
72
+ let maxBlocks = Number(this.maxBlocks);
73
+ maxBlocks = maxBlocks && maxBlocks < MAX_PAGES_BLOCKS ? MAX_PAGES_BLOCKS : maxBlocks;
74
+ const minBlocks = Number(this.minBlocks);
75
+
76
+ const pages = [];
77
+ const numPages = this.pages ? Number(this.pages) : Math.ceil(totalItems / pageSize);
78
+
79
+ if (numPages > 1) {
80
+ pages.push({
81
+ type: 'prev',
82
+ number: Math.max(1, currentPage - 1),
83
+ active: currentPage > 1
84
+ });
85
+ pages.push({
86
+ type: 'first',
87
+ number: 1,
88
+ active: currentPage > 1,
89
+ current: currentPage === 1
90
+ });
91
+ const maxPivotPages = Math.round((maxBlocks - minBlocks) / 2);
92
+ let minPage = Math.max(2, currentPage - maxPivotPages);
93
+ const maxPage = Math.min(numPages - 1, currentPage + maxPivotPages * 2 - (currentPage - minPage));
94
+ minPage = Math.max(2, minPage - (maxPivotPages * 2 - (maxPage - minPage)));
95
+ let i = minPage;
96
+ while (i <= maxPage) {
97
+ if ((i === minPage && i !== 2) || (i === maxPage && i !== numPages - 1)) {
98
+ pages.push({
99
+ type: 'more',
100
+ active: false
101
+ });
102
+ } else {
103
+ pages.push({
104
+ type: 'page',
105
+ number: i,
106
+ active: currentPage !== i,
107
+ current: currentPage === i
108
+ });
109
+ }
110
+ i++;
111
+ }
112
+ pages.push({
113
+ type: 'last',
114
+ number: numPages,
115
+ active: currentPage !== numPages,
116
+ current: currentPage === numPages
117
+ });
118
+ pages.push({
119
+ type: 'next',
120
+ number: Math.min(numPages, currentPage + 1),
121
+ active: currentPage < numPages
122
+ });
123
+ }
124
+ return pages;
125
+ }
126
+ }
127
+ </script>
@@ -0,0 +1,41 @@
1
+ import { storiesOf } from '@storybook/vue';
2
+ import itfPagination from './Pagination.vue';
3
+ import itfApp from '../app/App.vue';
4
+
5
+ storiesOf('Common', module)
6
+ .add('Pagination', () => ({
7
+ components: {
8
+ itfApp,
9
+ itfPagination
10
+ },
11
+ data() {
12
+ return {
13
+ page: 1,
14
+ customDays: {
15
+ '2021-10-21': { text: '🎉', class: 'test' }
16
+ },
17
+ dateRange: null,
18
+ month: '09-2021'
19
+ }
20
+ },
21
+ template: `<div>
22
+ <p>You need wrap whole application with this tag</p>
23
+
24
+ <h2>Usage</h2>
25
+
26
+ <pre>
27
+
28
+ </pre>
29
+
30
+ <h2>Example</h2>
31
+
32
+ <itf-app ref="app">
33
+
34
+ {{page}}
35
+
36
+ <br />
37
+ <itf-pagination :length="500" v-model.lazy="page"></itf-pagination>
38
+
39
+ </itf-app>
40
+ </div>`,
41
+ }));
@@ -17,7 +17,7 @@
17
17
  .itf-popover {
18
18
  display: none;
19
19
 
20
- .popover-body & {
20
+ .popover-body > & {
21
21
  display: block;
22
22
  }
23
23
  .border-right {
@@ -57,8 +57,6 @@ export default @Component({
57
57
  },
58
58
  })
59
59
  class itfPopover extends Vue {
60
- @Inject({ default: null }) appendContext;
61
-
62
60
  @PropSync('visible') value;
63
61
  @Prop({ type: String, default: 'bottom', validator: (value) => ['bottom', 'left', 'right', 'top'].includes(value) }) placement;
64
62
  @Prop({ type: String, default: 'click', validator: (value) => ['click', 'focus', 'hover', 'manual'].includes(value) }) trigger;
@@ -95,23 +93,25 @@ class itfPopover extends Vue {
95
93
  const el = this.$refs.popover;
96
94
  const { default: Popover } = await import('bootstrap/js/src/popover.js');
97
95
 
98
- const elContext = (this.appendContext && this.appendContext()) || document.body;
96
+ const context = this.$el.closest('.itf-append-context') || document.body;
99
97
  this.modalEl = new Popover(this.$el, {
100
98
  content: el,
101
- container: elContext,
99
+ container: context,
102
100
  sanitize: false,
103
101
  html: true,
104
102
  customClass: this.customClass,
105
103
  placement: this.placement,
106
- trigger: this.trigger
104
+ trigger: this.trigger,
107
105
  });
108
106
  this.onVisibleChanged(this.value);
109
107
  this.$el.addEventListener('shown.bs.popover', () => {
110
108
  document.body.addEventListener('click', this.clickOutside);
109
+ this.$emit('update:visible', true);
111
110
  });
112
111
  this.$el.addEventListener('hidden.bs.popover', () => {
113
112
  this.value = false;
114
113
  document.body.removeEventListener('click', this.clickOutside);
114
+ this.$emit('update:visible', false);
115
115
  });
116
116
  }
117
117
 
@@ -1,149 +1,238 @@
1
1
  <template>
2
-
3
2
  <div class="itf-segmeneted-control">
4
- <div
5
- v-for="(item, n) of items"
6
- :key="n"
7
- class="itf-segmeneted-control__segment">
8
- <button
9
- @click="onSelect(item, n)"
10
- type="button"
11
- class="itf-segmeneted-control__button"
12
- :class="{'active': currentIndex === n}">
13
- <span class="itf-segmeneted-control__slider" ref="button"></span>
14
- <span class="itf-segmeneted-control__text">{{ itemText ? item[itemText] : item }}</span>
15
- </button>
3
+ <span v-if="!isUndefined" class="selection" :class="{'elevation-1': !disabled}"></span>
4
+
5
+ <div class="option" v-for="(item, n) in itemsWithNames" :key="n">
6
+ <label>
7
+ <input
8
+ type="radio"
9
+ :name="name"
10
+ :value="n"
11
+ :checked="isChecked(item)"
12
+ @change="onItemChanged(item)" />
13
+ <span>
14
+ <slot name="item" :item="item" :itemKey="n">{{item[itemText]}}</slot>
15
+ </span>
16
+ </label>
16
17
  </div>
17
18
  </div>
18
-
19
19
  </template>
20
- <style lang="scss" scoped>
20
+ <style lang="scss">
21
21
  @import '../../assets/scss/variables';
22
22
 
23
23
  .itf-segmeneted-control {
24
- --shadow: 0 1px 3px rgba(0,0,0,0.1);
24
+ --itf-segmeneted-control--backgroung: #{$input-bg};
25
+ --itf-segmeneted-control--border-radius: #{$border-radius};
26
+ --itf-segmeneted-control--shadow: 0 1px 3px rgba(0,0,0,0.1);
25
27
 
26
- background-color: $input-bg;
27
- border-radius: $border-radius;
28
- display: inline-flex;
28
+ cursor: pointer;
29
+ background: var(--itf-segmeneted-control--backgroung);
30
+ border-radius: var(--itf-segmeneted-control--border-radius);
29
31
  margin: 0;
30
- padding: 4px;
31
-
32
- &__segment {
32
+ padding: 2px;
33
+ border: none;
34
+ outline: none;
35
+ display: inline-grid;
36
+ grid-auto-flow: column;
37
+ grid-auto-columns: 1fr;
38
+ -webkit-user-select: none;
39
+ -moz-user-select: none;
40
+ -ms-user-select: none;
41
+ user-select: none;
42
+ overflow: hidden;
43
+
44
+ .option {
33
45
  position: relative;
34
- margin-bottom: 0;
35
- line-height: 1;
36
- }
46
+ cursor: pointer;
37
47
 
38
- &__button {
39
- position: relative;
40
- margin: 0;
41
- padding: 7px 30px;
42
- line-height: 1;
43
- background: transparent;
44
- border: none;
45
- outline: none;
46
-
47
- &:hover,
48
- &:focus {
49
- cursor: pointer;
48
+ &:hover input:not(:checked) + label span,
49
+ &:active input:not(:checked) + label span,
50
+ &:focus input:not(:checked) + label span {
51
+ /*opacity: .2;*/
52
+ color: var(--v-primary-base);
50
53
  }
51
- &:after {
52
- position: absolute;
53
- top: 15%;
54
- right: -0.5px;
55
- display: block;
56
- width: 1px;
57
- height: 70%;
58
- background-color: rgba(0, 0, 0, 0.08);
59
- -webkit-transition: opacity .2s ease-out;
60
- transition: opacity .2s ease-out;
61
- content: "";
54
+ &:active input:not(:checked) + label span {
55
+ transform: scale(.95);
62
56
  }
57
+ &:first-of-type {
58
+ grid-column: 1;
59
+ grid-row: 1;
60
+ box-shadow: none;
63
61
 
64
- &.active:after {
65
- opacity: 0;
62
+ label::before {
63
+ opacity: 0;
64
+ }
65
+ label::after {
66
+ opacity: 0;
67
+ }
66
68
  }
67
- }
68
- &__segment:last-child &__button:after {
69
- display: none;
70
- }
71
-
72
- &__text {
73
- position: relative;
74
- z-index: 2;
75
- }
76
- &__slider {
77
- -webkit-transition: opacity .2s ease-out;
78
- transition: opacity .2s ease-out;
79
- transition-delay: 400;
80
- position: absolute;
81
- opacity: 0;
82
- top: 0;
83
- right: 0;
84
- bottom: 0;
85
- left: 0;
86
- z-index: 1;
87
- background-color: #fff;
88
- border-radius: calc(#{$border-radius} - 3px);
89
- box-shadow: var(--shadow);
90
- content: "";
91
- &.active {
92
- box-shadow: none;
93
- opacity: 1 !important;
69
+ label {
70
+ cursor: pointer;
71
+ position: relative;
72
+ display: block;
73
+ text-align: center;
74
+ padding: .25rem 1.5rem;
75
+ background: rgba(255, 255, 255, 0);
76
+ color: rgba(0, 0, 0, 1);
77
+ font-size: 14px;
78
+
79
+ &::before,
80
+ &::after {
81
+ content: '';
82
+ width: 1px;
83
+ background: rgba(142, 142, 147, .15);
84
+ position: absolute;
85
+ top: 14%;
86
+ bottom: 14%;
87
+ border-radius: var(--itf-segmeneted-control--border-radius);
88
+ will-change: background;
89
+ -webkit-transition: background .2s ease;
90
+ transition: background .2s ease;
91
+ }
92
+ &::before {
93
+ left: 0;
94
+ transform: translateX(-.5px);
95
+ }
96
+ &::after {
97
+ right: 0;
98
+ transform: translateX(.5px);
99
+ }
100
+ span {
101
+ display: block;
102
+ position: relative;
103
+ z-index: 2;
104
+ -webkit-transition: all .2s ease;
105
+ transition: all .2s ease;
106
+ will-change: transform;
107
+ }
94
108
  }
95
- }
96
- &__button.active &__slider {
97
- opacity: 1;
98
- }
99
-
100
- @media (prefers-color-scheme: notdark) {
101
- background-color: $dark-input-bg;
102
109
 
103
- &__slider {
104
- background-color: $dark-primary;
105
- }
106
- &__button {
107
- color: $dark-body-color;
110
+ input {
111
+ position: absolute;
112
+ top: 0;
113
+ left: 0;
114
+ right: 0;
115
+ bottom: 0;
116
+ width: 100%;
117
+ height: 100%;
118
+ padding: 0;
119
+ margin: 0;
120
+ -webkit-appearance: none;
121
+ -moz-appearance: none;
122
+ appearance: none;
123
+ outline: none;
124
+ border: none;
125
+ opacity: 0;
108
126
 
109
- &.active {
110
- color: #222;
127
+ &:checked + label {
128
+ cursor: default;
111
129
  }
112
- &:after {
113
- border-color: rgba(255, 255, 255, 0.08);
130
+ &:checked + label::before,
131
+ &:checked + label::after {
132
+ background: var(--itf-segmeneted-control--backgroung);
133
+ z-index: 1;
114
134
  }
115
135
  }
116
136
  }
137
+ .selection {
138
+ background: rgba(255, 255, 255, 1);
139
+ border: .5px solid rgba(0, 0, 0, 0.04);
140
+ box-shadow: var(--itf-segmeneted-control--shadow);
141
+ /*box-shadow: 0 3px 8px 0 rgba(0,0,0,0.12), 0 3px 1px 0 rgba(0,0,0,0.04);*/
142
+ border-radius: var(--itf-segmeneted-control--border-radius);
143
+ grid-column: 1;
144
+ grid-row: 1;
145
+ z-index: 2;
146
+ will-change: transform;
147
+ -webkit-transition: transform .2s ease;
148
+ transition: transform .2s ease;
149
+ }
117
150
  }
118
151
  </style>
119
152
  <script>
120
- import { Vue, Prop, Model, Component } from 'vue-property-decorator';
153
+ import { Vue, Component, Model, Prop, Watch } from 'vue-property-decorator';
121
154
 
122
155
  export default @Component({
123
156
  name: 'itfSegmentedControl',
124
- components: {
125
- }
157
+ components: {}
126
158
  })
127
159
  class itfSegmentedControl extends Vue {
128
160
  @Model('input') value;
129
- @Prop() items;
130
- @Prop(String) itemKey;
131
- @Prop(String) itemText;
161
+ @Prop({ type: Array, required: true }) items;
162
+ @Prop({ type: String, default: 'Id' }) itemKey;
163
+ @Prop({ type: String, default: 'Name' }) itemText;
164
+ @Prop({ type: Boolean, default: false }) returnObject;
165
+ @Prop({ type: Boolean, default: false }) disabled;
166
+
167
+ get name () {
168
+ return `sc${this._uid}`;
169
+ }
132
170
 
133
- get currentIndex() {
134
- return this.items.findIndex((item) => (this.itemKey ? item[this.itemKey] : item) === this.value);
171
+ get itemsWithNames() {
172
+ return this.items.map((item, n) => {
173
+ return {
174
+ ...item,
175
+ $itemName: this.disabled ? null : `item${this.name}${n}`
176
+ };
177
+ });
135
178
  }
136
179
 
137
- mounted() {
180
+ mounted () {
181
+ this.$nextTick(() => this.init());
138
182
  }
139
183
 
140
- async onSelect(item, index) {
141
- const ramjet = await import('ramjet');
142
- ramjet.transform(this.$refs.button[this.currentIndex], this.$refs.button[index], {
143
- duration: 200
144
- });
184
+ get itemValue () {
185
+ if (typeof this.value === 'undefined' || this.value === null) {
186
+ return this.value;
187
+ }
188
+ return typeof this.value === 'object' ? this.value[this.itemKey] : this.value;
189
+ }
190
+
191
+ isChecked (item) {
192
+ return this.itemValue === item[this.itemKey];
193
+ }
194
+
195
+ @Watch('value')
196
+ @Watch('items')
197
+ onItemsChanged() {
198
+ this.$nextTick(() => this.init());
199
+ }
200
+
201
+ get isUndefined () {
202
+ return typeof this.value === 'undefined';
203
+ }
204
+
205
+ init() {
206
+ const INDIVIDUAL_SEGMENT_SELECTOR = '.option input';
207
+ const BACKGROUND_PILL_SELECTOR = '.selection';
208
+
209
+ this.$el.addEventListener('change', () => updatePillPosition(this.$el));
210
+ window.addEventListener('resize', () => updatePillPosition(this.$el)); // Prevent pill from detaching from element when window resized. Becuase this is rare I haven't bothered with throttling the event
211
+
212
+ updatePillPosition(this.$el);
213
+
214
+ function updatePillPosition (el) {
215
+ forEachElement(el, INDIVIDUAL_SEGMENT_SELECTOR, (elem, index) => {
216
+ if (elem.checked) {
217
+ moveBackgroundPillToElement(el, elem, index);
218
+ }
219
+ })
220
+ }
221
+
222
+ function moveBackgroundPillToElement (el, elem, index) {
223
+ const slider = el.querySelector(BACKGROUND_PILL_SELECTOR);
224
+ if (slider) {
225
+ slider.style.transform = 'translateX(' + (elem.offsetWidth * index) + 'px)';
226
+ }
227
+ }
228
+
229
+ function forEachElement(el, className, fn) {
230
+ Array.from(el.querySelectorAll(className)).forEach(fn);
231
+ }
232
+ }
145
233
 
146
- this.$emit('input', this.itemKey ? item[this.itemKey] : item);
234
+ onItemChanged (item) {
235
+ this.$emit('input', this.returnObject ? item : item[this.itemKey]);
147
236
  }
148
237
  }
149
238
  </script>
@@ -729,7 +729,7 @@ export default {
729
729
  _value: [], // Internal value managed by Vue Select if no `value` prop is passed
730
730
  }
731
731
  },
732
- inject: { itemLabel: { default: null }, appendContext: { default: null } },
732
+ inject: { itemLabel: { default: null } },
733
733
  computed: {
734
734
  /**
735
735
  * Determine if the component needs to
@@ -9,8 +9,7 @@ export default {
9
9
  } = context.$refs.toggle.getBoundingClientRect();
10
10
 
11
11
  const { calculatePosition } = context;
12
- const { appendContext } = context;
13
- const rootEl = appendContext && appendContext();
12
+ const rootEl = el.closest('.itf-append-context') || document.body;
14
13
  if (rootEl) {
15
14
  let {
16
15
  top: parentTop,