@policystudio/policy-studio-ui-vue 1.1.44 → 1.1.48

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.
@@ -1,40 +1,109 @@
1
1
  <template>
2
- <div class="psui-el-slider">
3
- <div v-if="label" class="psui-el-slider-label">
4
- <span>{{label}}</span>
2
+ <div
3
+ class="psui-el-slider"
4
+ :class="`layout-${layout}`"
5
+ >
6
+ <div
7
+ v-if="label"
8
+ class="psui-el-slider-label"
9
+ >
10
+ <span>{{ label }}</span>
5
11
  </div>
6
- <div :class="{'psui-flex psui-items-center': !label && !bubble }">
7
- <div
8
- v-if="bubble"
9
- class="psui-relative psui-bg-gray-40 psui-text-gray-20 psui-rounded-md psui-text-white psui-font-bold psui-w-10 psui-text-center range-value"
10
- :style="{ left: `${positionBubble}px` }"
12
+
13
+ <div
14
+ class="psui-el-slider-wrapper"
15
+ :class="{ 'psui-flex psui-items-center' : !label && !bubble }"
16
+ >
17
+ <div class="psui-el-slider-wrapper-input">
18
+ <div
19
+ v-if="bubble"
20
+ class="psui-el-slider-range-value"
21
+ :style="{ left: positionBubble }"
22
+ >
23
+ {{ sliderValue }}
24
+ </div>
25
+
26
+ <div
27
+ class="slider-bar"
28
+ :style="{ width: progressWidth }"
29
+ ></div>
30
+
31
+ <div
32
+ v-for="(barStyle, index) in bgBarStyles"
33
+ :key="index"
34
+ class="slider-bar-bg"
35
+ :style="bgBarStyles[index]"
36
+ ></div>
37
+
38
+ <div
39
+ v-for="(bar, index) in barStyles"
40
+ :key="index"
41
+ class="slider-bar-dynamic"
42
+ :style="barStyles[index]"
43
+ ></div>
44
+
45
+ <input
46
+ ref="slider"
47
+ type="range"
48
+ :min="min"
49
+ :max="max"
50
+ :step="step"
51
+ class="psui-el-slider-input psui-float-left"
52
+ v-model="sliderValue"
53
+ @input="updateSlider($event)"
54
+ />
55
+ </div>
56
+
57
+ <div
58
+ v-if="dividers"
59
+ class="psui-el-slider-grid"
11
60
  >
12
- {{ value }}
61
+ <div
62
+ v-for="(grid, index) in gridData"
63
+ :key="index"
64
+ class="line"
65
+ :style="{ left: `${grid}%` }"
66
+ ></div>
67
+
68
+ <span class="info info-min">{{ min }}</span>
69
+ <span class="info info-max">{{ max }}</span>
13
70
  </div>
14
- <input
15
- type="range"
16
- :min="min"
17
- :max="max"
18
- :value="value"
19
- @input="$emit('update:value', Number($event.target.value))"
20
- class="slider psui-float-left"
21
- ref="slider"
22
- />
23
- <span v-if="!label && !bubble" class="psui-bg-gray-20 psui-font-bold psui-text-gray-50 psui-px-2 psui-py-px psui-rounded-sm psui-float-left psui-ml-2">{{ value }}</span>
71
+
72
+ <span
73
+ v-if="!label && !bubble"
74
+ class="psui-el-slider-bubble"
75
+ >
76
+ {{ sliderValue }}
77
+ </span>
24
78
  </div>
25
79
  </div>
26
80
  </template>
27
81
 
28
82
  <script>
83
+ export const sliderLayout = ['default', 'rich']
84
+
29
85
  export default {
30
86
  name: 'PsSlider',
31
87
  props: {
88
+ layout: {
89
+ type: String,
90
+ default: 'default',
91
+ validator: (value) => {
92
+ return sliderLayout.indexOf(value) !== -1
93
+ }
94
+ },
32
95
  min: {
33
96
  type: Number,
34
97
  default: 0,
35
98
  },
36
99
  max: {
37
100
  type: Number,
101
+ default: 100,
102
+ required: true,
103
+ },
104
+ maxValue: {
105
+ type: Number,
106
+ default: 100,
38
107
  required: true,
39
108
  },
40
109
  label: {
@@ -42,6 +111,7 @@ export default {
42
111
  },
43
112
  value: {
44
113
  type: [Number, String],
114
+ default: 0,
45
115
  },
46
116
  baseValue: {
47
117
  type: [Number, Boolean]
@@ -55,81 +125,131 @@ export default {
55
125
  },
56
126
  bubble: {
57
127
  type: Boolean
128
+ },
129
+ dividers: {
130
+ type: Boolean,
131
+ default: false
132
+ },
133
+ modelValue: {
134
+ type: Number,
135
+ default: 50,
136
+ },
137
+ gridData: {
138
+ type: Array,
139
+ default() {
140
+ return [0, 30, 55, 80, 100]
141
+ }
142
+ },
143
+ sliderColors: {
144
+ type: Array,
145
+ default() {
146
+ return [
147
+ { start: 140, end: 170, bgColor: '#FF906D' },
148
+ { start: 170, end: 200, bgColor: '#D65C5A' },
149
+ ]
150
+ }
151
+ },
152
+ bgColors: {
153
+ type: Array,
154
+ default() {
155
+ return [
156
+ { start: 0, end: 170, bgColor: '#D6DDE5' },
157
+ { start: 170, end: 200, bgColor: '#A2ACB7' },
158
+ ]
159
+ }
58
160
  }
59
161
  },
60
- data: () => {
61
- return {
62
- isMounted: false
63
- }
64
- },
162
+ data: () => ({
163
+ isMounted: false,
164
+ sliderWidth: 0,
165
+ sliderValue: 0,
166
+ progress: 0,
167
+ barStyles: [],
168
+ bgBarStyles: []
169
+ }),
65
170
  computed: {
66
171
  positionBubble() {
67
- if (this.bubble && this.isMounted) {
68
- return (this.$refs.slider.offsetWidth / this.max) * this.value - 15
172
+ const val = this.sliderValue
173
+ const width = this.sliderWidth - 24
174
+ const percent = (val - this.min) / (this.max - this.min)
175
+ const offset = -2
176
+ const position = width * percent + offset
177
+
178
+ return `${position}px`
179
+ },
180
+ progressWidth() {
181
+ if (this.sliderValue > this.maxValue) {
182
+ return '100%'
69
183
  }
70
- return 0
71
- }
184
+ return ((this.sliderValue - this.min) / (this.max - this.min)) * 100 + '%'
185
+ },
186
+ },
187
+ watch: {
188
+ sliderValue(newValue) {
189
+ this.updateProgress()
190
+ this.$emit('input', newValue)
191
+ this.updateBarStyles()
192
+ },
193
+ value(newValue) {
194
+ this.sliderValue = newValue
195
+ },
196
+ sliderColors: {
197
+ immediate: true,
198
+ handler() {
199
+ this.updateBarStyles()
200
+ },
201
+ },
202
+ },
203
+ created() {
204
+ window.addEventListener('resize', this.resizeHandler)
205
+ },
206
+ destroyed() {
207
+ window.removeEventListener('resize', this.resizeHandler)
72
208
  },
73
209
  mounted() {
74
210
  this.isMounted = true
211
+ this.updateProgress()
212
+ this.updateBarStyles()
75
213
  },
76
214
  methods: {
77
- getValueToScale(value) {
78
- return 100 * value / this.rangeDistanceMax
79
- }
80
- }
81
- }
82
- </script>
83
-
84
- <style scoped>
85
- .slider {
86
- width: 100%;
87
- -webkit-appearance: none;
88
- height: 6px;
89
- border-radius: 5px;
90
- background: #d3d3d3;
91
- outline: none;
92
- opacity: 0.7;
93
- -webkit-transition: .2s;
94
- transition: opacity .2s;
95
- }
96
-
97
- .slider:hover {
98
- opacity: 1;
99
- }
100
-
101
- .slider::-webkit-slider-thumb {
102
- border: none;
103
- -webkit-appearance: none;
104
- appearance: none;
105
- width: 20px;
106
- height: 20px;
107
- border-radius: 50%;
108
- background: #64B5CE;
109
- cursor: pointer;
110
- }
111
-
112
- .slider::-moz-range-thumb {
113
- border: none;
114
- width: 20px;
115
- height: 20px;
116
- border-radius: 50%;
117
- background: #64B5CE;
118
- cursor: pointer;
119
- }
120
- .slider::-webkit-slider-progress, .slider::-moz-range-progress {
121
- border-top-left-radius: 5px;
122
- border-bottom-left-radius: 5px;
123
- -webkit-appearance: none;
124
- box-shadow: none;
125
- border: none;
126
- background: #64B5CE;
127
- height: 6px;
128
- }
215
+ updateProgress() {
216
+ this.progress = Math.round(
217
+ ((this.sliderValue - this.min) / (this.max - this.min)) * 100
218
+ )
219
+ },
220
+ updateSlider(event) {
221
+ this.sliderValue = Math.min(event.target.value, this.maxValue)
222
+ },
223
+ updateBarStyles() {
224
+ this.barStyles = this.sliderColors.map((bar) => this.getBarStyle(bar))
225
+ this.bgBarStyles = this.bgColors.map((bar) => this.getBarStyle(bar, false))
226
+ },
227
+ resizeHandler() {
228
+ this.sliderWidth = this.$refs.slider.clientWidth
229
+ },
230
+ getBarStyle(bar, normalizeByProgress = true) {
231
+ const start = (bar.start / this.max) * 100
232
+ const end = (bar.end / this.max) * 100
233
+ const progress = parseFloat(this.progressWidth)
234
+ let barWidth = end - start
235
+ let barLeft = start
129
236
 
130
- .range-value{
131
- margin-bottom: 10px;
132
- padding: 2px 4px;
237
+ if (normalizeByProgress) {
238
+ if (progress < start) {
239
+ barWidth = 0
240
+ } else if (progress < end) {
241
+ barWidth = progress - start
242
+ }
243
+ }
133
244
 
245
+ return {
246
+ left: `${barLeft}%`,
247
+ backgroundColor: bar.bgColor,
248
+ width: `${barWidth}%`
249
+ }
250
+ }
251
+ },
252
+
134
253
  }
135
- </style>
254
+ </script>
255
+ <style> /* Please, use the file src/assets/scss/components/PsSlider.scss */ </style>
@@ -41,6 +41,7 @@ export default {
41
41
  },
42
42
  computed:{
43
43
  getToggleClass(){
44
+ if(this.disabled) return 'toggle-false'
44
45
  return this.value ? 'toggle-true' : 'toggle-false'
45
46
  },
46
47
  getComponentClass(){
@@ -1,17 +1,20 @@
1
1
  <template>
2
2
  <div
3
+ ref="tableWrapper"
3
4
  class="psui-el-table-results-wrapper"
5
+ :class="`table-${layout}`"
4
6
  :style="{ maxHeight: tableMaxHeight }"
5
7
  >
6
8
  <table
7
9
  ref="table"
8
10
  class="psui-el-table-results"
9
- :class="`layout-${layout}`"
11
+ :class="[
12
+ `layout-${layout}`,
13
+ { 'is-sticky': isSticky }
14
+ ]"
10
15
  >
11
16
  <slot name="header"></slot>
12
17
 
13
- <!-- <slot name="body"></slot> -->
14
-
15
18
  <tbody>
16
19
  <tr
17
20
  v-for="(item, index) in getRows"
@@ -32,8 +35,19 @@
32
35
  @mouseenter="onRowHover(index)"
33
36
  @mouseleave="onRowHover(false)"
34
37
  >
35
- <td :style="`background-color:${getBackgroundColor(item.background_color)};`">
36
- <div class="psui-flex psui-justify-between psui-relative">
38
+ <td
39
+ :style="item.background_color ? `background-color: ${getBackgroundColor(item.background_color)};` : ''"
40
+ class="psui-transition"
41
+ >
42
+ <div class="psui-flex psui-relative">
43
+ <PsIcon
44
+ v-if="item.remove_add_button"
45
+ :icon="item.remove_add_button_icon"
46
+ class="psui-flex psui-text-gray-40 pl-8 psui-cursor-pointer leading-none hover:psui-text-blue-60 transition-all"
47
+ size="15"
48
+ display="flex"
49
+ @click.native="$emit('removeOrAddButtonChange',item)"
50
+ />
37
51
  <div
38
52
  class="psui-flex psui-items-center actions psui-space-x-3"
39
53
  :style="{paddingLeft: `${item.deep * 16}px`}"
@@ -171,15 +185,19 @@
171
185
 
172
186
  <template v-for="(columnGroup, indexColumn) of columnGroups">
173
187
  <td
174
- :style="`background-color:${getBackgroundColor(item.background_color)};`"
188
+ :style="item.background_color ? `background-color: ${getBackgroundColor(item.background_color)};` : ''"
175
189
  v-for="column of columnGroup.columns"
190
+ class="psui-transition"
176
191
  :key="indexColumn + '-' + columnGroup.key + '-' + column.key"
177
192
  :data-test-id="column.key"
193
+ :class="`column-${column.key}`"
178
194
  >
179
195
  <div
180
196
  v-if="layout != 'comparison'"
181
197
  class="psui-space-x-2 psui-show-childrens-on-hover"
198
+ :class="column.isCenteredContent ? 'psui-justify-center' : 'psui-justify-end'"
182
199
  >
200
+
183
201
  <PsTooltip v-if="isSelectedRow(column,item)">
184
202
  <template v-slot:trigger>
185
203
  <PsIcon
@@ -212,48 +230,64 @@
212
230
  </template>
213
231
  </PsTooltip>
214
232
 
215
- <p v-if="formatFunction && !item.is_disabled">
216
- {{ formatFunction(column.key, item.data[column.key], item.data, item.study_id) }}
217
- </p>
233
+ <p v-if="!column.isSwitch">
234
+ <template v-if="column.hasScoreHelper">
235
+ <PsTooltip
236
+ layout="blue"
237
+ >
238
+ <template v-slot:trigger>
239
+ {{ getItemContent(column, item) }}
240
+ </template>
241
+ <template v-slot:content>
242
+ <p class="psui-text-xsmall psui-font-bold">{{ column.hasScoreHelper.title }}</p>
243
+ {{ column.hasScoreHelper.description }}
244
+ </template>
245
+ </PsTooltip>
246
+ </template>
218
247
 
219
- <p
220
- v-else-if="item.is_disabled"
221
- class="psui-text-gray-50"
222
- >
223
- --
224
- </p>
225
- <p v-else>
226
- {{ item.data[column.key] }}
248
+ <template v-else>
249
+ {{ getItemContent(column, item) }}
250
+ </template>
227
251
  </p>
228
252
 
229
253
  <PsProgressBar
230
- v-if="column.isChart && formatFunction"
254
+ v-if="column.isChart && formatFunction && item.data[column.key] != null"
231
255
  :value="formatFunction(column.key, item.data[column.key], item.data) == '--' ? 0 : item.data[column.key]"
232
256
  :force-break-even="item.is_disabled || formatFunction(column.key, item.data[column.key], item.data) === '--' ? true : false"
233
257
  />
234
258
 
235
259
  <PsProgressBar
236
- v-else-if="column.isChart"
260
+ v-else-if="column.isChart && item.data[column.key] != null"
237
261
  :value="item.data[column.key] == '--' ? 0 : item.data[column.key]"
238
262
  :force-break-even="item.is_disabled || item.data[column.key] === '--' ? true : false"
239
263
  />
264
+
265
+ <PsSwitch
266
+ v-if="column.isSwitch && item.data[column.key] != null"
267
+ :disabled="item.data.is_disabled"
268
+ :value="item.data[column.key]"
269
+ size="small"
270
+ @change="$emit('switchButtonItemChanged', item)"
271
+ />
240
272
  </div>
273
+
274
+ <!-- only comparison layout -->
241
275
  <div v-else>
242
276
  <div class="psui-py-4 psui-px-6">
243
277
  <PsTagScope
244
- v-if="item.data[column.key] != 0 && column.renderType && column.renderType == 'tag_scope'"
245
- :included="item.data[column.key] != 0 ? true : false"
278
+ v-if="column.renderType && column.renderType == 'tag_scope'"
279
+ :included="item.data[column.key] == true"
246
280
  />
247
281
 
248
282
  <PsBarChart
249
283
  v-else-if="item.data[column.key] != 0 && column.renderType && column.renderType == 'bar_chart'"
250
284
  :show-number="item.data[column.key] != 0 ? true : false"
251
- :total="item.data[column.key]"
252
- :fill-width="Math.floor(Math.random() * 100)"
285
+ :total="formatFunction ? formatFunction(column.key, item.data[column.key], item.data) == '--' ? 0 : formatFunction(column.key, item.data[column.key], item.data) : item.data[column.key]"
286
+ :fill-width="getPsBarChartWidth(column.key, item.data[column.key])"
253
287
  />
254
288
 
255
289
  <p v-else-if="item.data[column.key] != 0">
256
- {{ item.data[column.key] }}
290
+ {{ formatFunction(column.key, item.data[column.key], item.data) }}
257
291
  </p>
258
292
  </div>
259
293
  </div>
@@ -268,15 +302,17 @@
268
302
  </template>
269
303
 
270
304
  <script>
305
+ import { eventBus } from '../../../.storybook/eventBus'
271
306
  import PsRichTooltip from '../tooltip/PsRichTooltip.vue'
272
307
  import PsIcon from '../ui/PsIcon.vue'
273
308
  import PsProgressBar from '../badges-and-tags/PsProgressBar.vue'
274
309
  import PsTagScope from '../badges-and-tags/PsTagScope.vue'
275
310
  import PsBarChart from '../data-graphics/PsBarChart.vue'
276
311
  import PsTooltip from '../tooltip/PsTooltip.vue'
312
+ import PsSwitch from '../controls/PsSwitch.vue'
277
313
  import tailwindConfig from '../../../tailwind.config'
278
314
 
279
- export const tableLayout = ['results', 'comparison']
315
+ export const tableLayout = ['results', 'comparison', 'flexible']
280
316
 
281
317
  export default {
282
318
  name: 'PsTableResults',
@@ -286,7 +322,7 @@ export default {
286
322
  PsRichTooltip,
287
323
  PsTagScope,
288
324
  PsBarChart,
289
- PsTooltip
325
+ PsTooltip, PsSwitch
290
326
  },
291
327
  props: {
292
328
  /**
@@ -406,7 +442,8 @@ export default {
406
442
  isHoveringRow : false,
407
443
  selectedRow : null,
408
444
  policyItemSelected: null,
409
- columnSelectedKey : null
445
+ columnSelectedKey : null,
446
+ isSticky: false
410
447
  }),
411
448
  computed: {
412
449
  getRows() {
@@ -436,13 +473,15 @@ export default {
436
473
  },
437
474
  mounted () {
438
475
  this.setCollapsedRows()
439
- this.$eventBus.$on('resetPolicyImpactItemSelected', (item, columnKey = 'forecast_emissions_savings') => {
440
- this.$eventBus.$emit('setPolicyItemSelected', { item, columnSelectedKey: columnKey })
476
+ this.$refs.tableWrapper.addEventListener('scroll', this.handleTableScroll)
477
+ eventBus.$on('resetPolicyImpactItemSelected', (item, columnKey = 'forecast_emissions_savings') => {
478
+ eventBus.$emit('setPolicyItemSelected', { item, columnSelectedKey: columnKey })
441
479
  this.selectedRow = null
442
480
  })
443
481
  },
444
482
  beforeDestroy() {
445
- this.$eventBus.$off('resetPolicyImpactItemSelected')
483
+ eventBus.$off('resetPolicyImpactItemSelected')
484
+ this.$refs.tableWrapper.removeEventListener('scroll', this.handleTableScroll)
446
485
  },
447
486
  methods: {
448
487
  setCollapsedRows() {
@@ -519,6 +558,17 @@ export default {
519
558
  getText(item) {
520
559
  if(item?.actions?.length === 1) return item.actions[0].text
521
560
  },
561
+ getItemContent(column, item) {
562
+ if ( column.isSwitch ) return
563
+
564
+ if (this.formatFunction && !item.is_disabled) {
565
+ return this.formatFunction(column.key, item.data[column.key], item.data, item.study_id)
566
+ } else if (item.is_disabled) {
567
+ return '--'
568
+ } else {
569
+ return item.data[column.key]
570
+ }
571
+ },
522
572
  executeCallback(item, action) {
523
573
  if(item?.actions?.length === 1) item.actions[0].callback(item)
524
574
  else action?.callback(item)
@@ -539,7 +589,7 @@ export default {
539
589
  this.$emit('policy-selected', { item: item, column: column })
540
590
  },
541
591
  onCloseSelectRow(item, column) {
542
- this.$eventBus.$emit('resetPolicyImpactItemSelected', item, column)
592
+ eventBus.$emit('resetPolicyImpactItemSelected', item, column)
543
593
  },
544
594
  isSelectedRow(column,item) {
545
595
  if(!item.id){
@@ -561,6 +611,20 @@ export default {
561
611
  content = `<p class="psui-font-bold psui-text-gray-80 psui-text-xsmall">${item.title} buildings are <br>not allowed.</p>`
562
612
  }
563
613
  return content
614
+ },
615
+
616
+ getPsBarChartWidth(key, value) {
617
+ const max = Math.max(...this.getRows.map(item => item.data[key]))
618
+ return (value * 100) / max
619
+ },
620
+
621
+ handleTableScroll() {
622
+ const tableWrapper = this.$refs.tableWrapper
623
+ if (tableWrapper.scrollLeft > 0) {
624
+ this.isSticky = true
625
+ } else {
626
+ this.isSticky = false
627
+ }
564
628
  }
565
629
  },
566
630
  }