@energysage/es-ds-components 3.0.0-alpha.7 → 3.0.0-alpha.8

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,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- defineProps<{
2
+ const props = defineProps<{
3
3
  cols?: string;
4
4
  sm?: string;
5
5
  md?: string;
@@ -7,12 +7,14 @@ defineProps<{
7
7
  xl?: string;
8
8
  xxl?: string;
9
9
  }>();
10
+
11
+ const undefinedSize = !(props.cols || props.sm || props.md || props.lg || props.xl || props.xxl);
10
12
  </script>
11
13
 
12
14
  <template>
13
15
  <div
14
- class="col"
15
16
  :class="{
17
+ col: undefinedSize,
16
18
  [`col-${cols}`]: cols,
17
19
  [`col-sm-${sm}`]: sm,
18
20
  [`col-md-${md}`]: md,
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps({
3
+ aspect: {
4
+ type: String,
5
+ default: '16:9',
6
+ },
7
+ noAspect: {
8
+ type: Boolean,
9
+ default: false,
10
+ },
11
+ });
12
+
13
+ const aspects = props.aspect.split(':');
14
+ const aspectRatio = computed(() => `${(parseFloat(aspects[1]) / parseFloat(aspects[0])) * 100}%`);
15
+ </script>
16
+
17
+ <template>
18
+ <!-- if noAspect is set, pass along to esSkeleton with all other props -->
19
+ <es-skeleton
20
+ v-if="noAspect"
21
+ v-bind="$attrs" />
22
+ <!-- if setting the aspect ratio, wrap skeleton in containers to determine responsive size -->
23
+ <!-- based on Bootstrap Aspect -->
24
+ <div
25
+ v-else
26
+ class="d-flex">
27
+ <div
28
+ class="flex-grow-1"
29
+ :style="{ 'padding-bottom': aspectRatio, height: '0px' }" />
30
+ <div
31
+ class="flex-grow-1 w-100 mw-100"
32
+ style="margin-left: -100%">
33
+ <es-skeleton
34
+ v-bind="$attrs"
35
+ class="h-100 w-100" />
36
+ </div>
37
+ </div>
38
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ defineProps({
3
+ loading: {
4
+ type: Boolean,
5
+ default: false,
6
+ },
7
+ });
8
+ </script>
9
+
10
+ <template>
11
+ <div>
12
+ <slot
13
+ v-if="loading"
14
+ name="loading" />
15
+ <slot v-else />
16
+ </div>
17
+ </template>
@@ -0,0 +1,97 @@
1
+ <script setup lang="ts">
2
+ import Skeleton from 'primevue/skeleton';
3
+
4
+ defineProps({
5
+ animation: {
6
+ type: String,
7
+ default: 'wave',
8
+ validator: (val: string) => ['wave', 'fade', 'none'].includes(val as string),
9
+ },
10
+ height: {
11
+ type: String,
12
+ default: '1rem',
13
+ },
14
+ width: {
15
+ type: String,
16
+ default: 'auto',
17
+ },
18
+ size: {
19
+ type: String,
20
+ default: null,
21
+ },
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <skeleton
27
+ :height="size ? size : height"
28
+ :width="size ? size : width"
29
+ class="es-skeleton rounded-xs mb-25 bg-gray-200"
30
+ :class="{
31
+ 'es-skeleton-wave': animation == 'wave',
32
+ 'es-skeleton-fade': animation == 'fade',
33
+ }" />
34
+ </template>
35
+
36
+ <style lang="scss" scoped>
37
+ @use '@energysage/es-ds-styles/scss/variables' as variables;
38
+
39
+ @keyframes skeleton-animate-wave {
40
+ 0% {
41
+ transform: translateX(-100%);
42
+ }
43
+ 100% {
44
+ transform: translateX(100%);
45
+ }
46
+ }
47
+
48
+ @keyframes skeleton-animate-fade {
49
+ 0% {
50
+ opacity: 1;
51
+ }
52
+ 100% {
53
+ opacity: 0.4;
54
+ }
55
+ }
56
+
57
+ .es-skeleton {
58
+ overflow: hidden;
59
+
60
+ &-wave:after {
61
+ content: '';
62
+ height: 100%;
63
+ position: absolute;
64
+ left: 0;
65
+ right: 0;
66
+ top: 0;
67
+ z-index: 1;
68
+ // wave animation from PrimeVue Skeleton
69
+ background-image: linear-gradient(
70
+ 90deg,
71
+ rgba(variables.$white, 0),
72
+ rgba(variables.$white, 0.4),
73
+ rgba(variables.$white, 0)
74
+ );
75
+ animation-name: skeleton-animate-wave;
76
+ animation-duration: 1.2s;
77
+ animation-timing-function: ease;
78
+ animation-delay: 0s;
79
+ animation-iteration-count: infinite;
80
+ animation-direction: normal;
81
+ animation-fill-mode: none;
82
+ animation-play-state: running;
83
+ }
84
+
85
+ &-fade {
86
+ // fade animation from Bootstrap Skeleton
87
+ animation-name: skeleton-animate-fade;
88
+ animation-duration: 0.875s;
89
+ animation-timing-function: ease-in-out;
90
+ animation-delay: 0s;
91
+ animation-iteration-count: infinite;
92
+ animation-direction: alternate;
93
+ animation-fill-mode: none;
94
+ animation-play-state: running;
95
+ }
96
+ }
97
+ </style>
@@ -3,20 +3,33 @@ import Slider from 'primevue/slider';
3
3
 
4
4
  const props = defineProps({
5
5
  /**
6
- * Starting value of slider thumb
6
+ * Aria label for slider handle
7
7
  */
8
- startingValue: {
9
- type: Number,
10
- required: true,
11
- default: 1,
8
+ ariaLabel: {
9
+ type: String,
10
+ default: null,
12
11
  },
13
12
  /**
14
- * Minimum value of slider
13
+ * An id of the element whose contents describe this slider
15
14
  */
16
- min: {
17
- type: Number,
18
- required: true,
19
- default: 1,
15
+ ariaLabelledby: {
16
+ type: String,
17
+ default: null,
18
+ },
19
+ /**
20
+ * Indicates whether or not the slider should be disabled
21
+ */
22
+ disabled: {
23
+ type: Boolean,
24
+ default: false,
25
+ },
26
+ /**
27
+ * Function that modifies label value
28
+ */
29
+ labelFormatter: {
30
+ type: Function,
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ default: (val: any) => val,
20
33
  },
21
34
  /**
22
35
  * Maximum value of slider
@@ -27,67 +40,73 @@ const props = defineProps({
27
40
  default: 100,
28
41
  },
29
42
  /**
30
- * Step increment
43
+ * Minimum value of slider
31
44
  */
32
- step: {
45
+ min: {
33
46
  type: Number,
47
+ required: true,
34
48
  default: 1,
35
49
  },
36
50
  /**
37
- * Aria label for slider handle
51
+ * Starting value of slider thumb
38
52
  */
39
- ariaLabel: {
40
- type: String,
41
- required: false,
42
- default: 'Select a number',
53
+ startingValue: {
54
+ type: Number,
55
+ default: null,
43
56
  },
44
57
  /**
45
- * Function that modifies label value
58
+ * Step increment
46
59
  */
47
- labelFormatter: {
48
- type: Function,
49
- required: false,
50
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
- default: (val: any) => val,
60
+ step: {
61
+ type: Number,
62
+ default: 1,
52
63
  },
53
64
  /**
54
65
  * Function that modifies tooltip value
55
66
  */
56
67
  tooltipFormatter: {
57
68
  type: Function,
58
- required: false,
59
69
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
70
  default: (val: any) => val,
61
71
  },
62
72
  });
63
73
 
64
74
  // allow use of v-model on this component
75
+ // and only use props.startingValue if it's provided, otherwise use the value from the v-model
65
76
  const model = defineModel<number | number[]>();
66
- model.value = props.startingValue;
77
+ model.value = props.startingValue !== null ? props.startingValue : model.value;
67
78
  </script>
68
79
 
69
80
  <template>
70
- <div>
81
+ <div :class="{ 'es-slider--disabled': props.disabled }">
71
82
  <slider
72
83
  v-model="model"
73
84
  v-bind="$attrs"
85
+ :aria-disabled="disabled"
86
+ :disabled="disabled"
74
87
  :min="min"
75
88
  :max="max"
76
89
  :step="step"
90
+ :aria-labelledby="ariaLabelledby"
77
91
  :aria-label="ariaLabel"
78
92
  :pt="{
79
93
  root: {
80
- class: 'slider-root',
94
+ class: 'es-slider-root',
81
95
  },
82
96
  range: {
83
- class: 'slider-range',
97
+ class: 'es-slider-range',
84
98
  },
85
99
  handle: {
86
- class: 'slider-handle',
100
+ class: 'es-slider-handle',
87
101
  },
88
102
  }" />
89
-
90
- <div class="d-flex flex-row justify-content-between">
103
+ <!--
104
+ this is aria-hidden because the slider already uses the aria-valuemin and aria-value-max
105
+ attributes to indicate the minimum and maximum value, so these labels are just for visual users
106
+ -->
107
+ <div
108
+ aria-hidden
109
+ class="es-slider-labels d-flex flex-row font-weight-bold justify-content-between text-gray-700">
91
110
  <span>{{ labelFormatter(min) }}</span>
92
111
  <span>{{ labelFormatter(max) }}</span>
93
112
  </div>
@@ -96,47 +115,74 @@ model.value = props.startingValue;
96
115
 
97
116
  <style lang="scss" scoped>
98
117
  @use '@energysage/es-ds-styles/scss/variables' as variables;
99
- :deep(.slider-root) {
100
- background-color: variables.$gray-300;
118
+
119
+ $hover-focus-animation-duration: 0.15s;
120
+ $slide-animation-duration: 0.05s;
121
+
122
+ :deep(.es-slider-root) {
123
+ background-color: variables.$gray-200;
101
124
  border-radius: 15px;
102
125
  height: 10px;
103
- margin-bottom: 0.75rem;
126
+ margin-bottom: 1rem;
104
127
  position: relative;
128
+
129
+ &[data-p-sliding='true'] .slider-handle {
130
+ box-shadow: 0 0 0 9px variables.$blue-50;
131
+ }
105
132
  }
106
133
 
107
- :deep(.slider-range) {
108
- background: linear-gradient(90deg, #688aea, #162676);
134
+ :deep(.es-slider-range) {
135
+ background: linear-gradient(90deg, variables.$blue-400, variables.$blue-800);
109
136
  border-radius: 15px;
110
137
  height: 10px;
138
+
139
+ @media not (prefers-reduced-motion) {
140
+ transition: width $slide-animation-duration ease-in-out;
141
+ }
111
142
  }
112
143
 
113
- :deep(.slider-handle) {
144
+ :deep(.es-slider-handle) {
114
145
  background-color: variables.$white;
115
- border: 9px solid variables.$blue-600;
146
+ border: 9px solid variables.$blue-700;
116
147
  border-radius: 50%;
117
- box-shadow: 0.5px 0.5px 2px 1px rgba(0, 0, 0, 0.32);
148
+ box-shadow: 0 1px 6px 0 rgba(34, 38, 51, 0.25);
118
149
  cursor: pointer;
119
- filter: drop-shadow(0 1px 6px rgba(34, 38, 51, 0.25));
120
150
  height: 28px;
151
+ /* important to prevent sliding from attempting to horizontally scroll on mobile */
152
+ touch-action: none;
121
153
  transform: translate(-54%, -32%);
122
154
  width: 28px;
123
155
 
156
+ @media not (prefers-reduced-motion) {
157
+ transition:
158
+ box-shadow $hover-focus-animation-duration ease-in-out,
159
+ left $slide-animation-duration ease-in-out;
160
+ }
161
+
124
162
  &:focus-visible {
125
163
  outline: none;
126
164
  }
165
+
166
+ &:hover {
167
+ box-shadow: 0 0 0 9px variables.$blue-50;
168
+ }
169
+
170
+ &:focus {
171
+ box-shadow: 0 0 0 9px variables.$blue-300;
172
+ }
127
173
  }
128
174
 
129
175
  // "tooltip"
130
- :deep(.slider-handle::before) {
176
+ :deep(.es-slider-handle::before) {
131
177
  align-items: center;
132
178
  background-color: variables.$blue-600;
133
179
  border-radius: 50%;
134
- bottom: 27px;
180
+ bottom: 32px;
135
181
  box-shadow: 0 1px 6px 0 rgba(34, 38, 51, 0.25);
136
182
  color: variables.$white;
137
183
  content: v-bind("`'${tooltipFormatter(model)}'`");
138
184
  display: flex;
139
- font-weight: variables.$font-weight-boldest;
185
+ font-weight: variables.$font-weight-bold;
140
186
  height: 52px;
141
187
  justify-content: center;
142
188
  padding-bottom: 2px;
@@ -144,4 +190,29 @@ model.value = props.startingValue;
144
190
  right: -22px;
145
191
  width: 52px;
146
192
  }
193
+
194
+ .es-slider--disabled {
195
+ /* at least prevents click/tap from giving it focus, if not keyboard */
196
+ pointer-events: none;
197
+
198
+ :deep(.es-slider-range) {
199
+ /* use background instead of background-color here to clear out the linear-gradient */
200
+ background: variables.$gray-400;
201
+ }
202
+
203
+ :deep(.es-slider-handle) {
204
+ border-color: variables.$gray-400;
205
+ box-shadow: none;
206
+
207
+ &::before {
208
+ background-color: variables.$gray-300;
209
+ color: variables.$gray-500;
210
+ }
211
+ }
212
+
213
+ .es-slider-labels {
214
+ /* use important to override the utility class */
215
+ color: variables.$gray-500 !important;
216
+ }
217
+ }
147
218
  </style>
@@ -11,7 +11,7 @@ const panels = useSlots().default?.() || [];
11
11
  // keep track of the active tab index
12
12
  // - from above, if v-model is used
13
13
  // - from below, if the tab component receives a click
14
- const activeIndex = ref(0);
14
+ const activeIndex = ref(model.value || 0);
15
15
 
16
16
  // watch the v-model for changes and update the active index if it does change
17
17
  watch(model, (newVal) => {
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ // Props
3
+ interface IProps {
4
+ altText: string;
5
+ coverImageUrl: string;
6
+ embedUrl: string;
7
+ }
8
+ const props = defineProps<IProps>();
9
+ const showVideo = ref(false);
10
+
11
+ // Methods
12
+ function getVideoIdFromUrl(url: string) {
13
+ const embedPattern = /https:\/\/www\.youtube\.com\/embed\/(?<videoId>[A-Za-z0-9-]+)/;
14
+ const parsedUrl = embedPattern.exec(url);
15
+ if (parsedUrl) {
16
+ return parsedUrl?.groups?.videoId;
17
+ }
18
+ return '';
19
+ }
20
+
21
+ // Computed
22
+ const videoId = computed(() => getVideoIdFromUrl(props.embedUrl));
23
+ const embedUrlWithParams = computed(
24
+ () => `https://www.youtube.com/embed/${videoId.value}?rel=0&autoplay=1&cc_load_policy=1&cc_lang_pref=en`,
25
+ );
26
+ </script>
27
+
28
+ <template>
29
+ <div>
30
+ <div
31
+ v-if="showVideo"
32
+ class="embed-responsive embed-responsive-16by9">
33
+ <iframe
34
+ :id="`youtube-video-${videoId}`"
35
+ :src="embedUrlWithParams"
36
+ class="embed-responsive-item"
37
+ frameborder="0"></iframe>
38
+ </div>
39
+ <es-card
40
+ v-else
41
+ :id="`youtube-placeholder-${videoId}`"
42
+ class="EsVideo-button bg-transparent overflow-hidden p-0 position-relative w-100"
43
+ variant="interactive"
44
+ @click="showVideo = true">
45
+ <slot v-if="$slots.default" />
46
+ <nuxt-img
47
+ v-else
48
+ class="EsVideo-image d-block w-100"
49
+ sizes="md:530px sm:275px"
50
+ :src="props.coverImageUrl"
51
+ :alt="props.altText" />
52
+ <icon-video-play
53
+ class="EsVideo-icon position-absolute abs-center"
54
+ width="74px"
55
+ height="54px" />
56
+ </es-card>
57
+ </div>
58
+ </template>
package/nuxt.config.ts CHANGED
@@ -66,6 +66,7 @@ export default defineNuxtConfig({
66
66
  'primevue/progressbar',
67
67
  'primevue/radiobutton',
68
68
  'primevue/rating',
69
+ 'primevue/skeleton',
69
70
  'primevue/slider',
70
71
  'primevue/tabpanel',
71
72
  'primevue/tabview',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energysage/es-ds-components",
3
- "version": "3.0.0-alpha.7",
3
+ "version": "3.0.0-alpha.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "An EnergySage Vue component library",
@@ -306,6 +306,10 @@ export default (
306
306
  },
307
307
  {
308
308
  name: 'Financing',
309
+ link: `${ES_DOMAIN}/financing/`,
310
+ },
311
+ {
312
+ name: 'Solar loan providers',
309
313
  link: `${ES_DOMAIN}/solar/financing/loan-providers/`,
310
314
  },
311
315
  {
@@ -398,6 +402,10 @@ export default (
398
402
  name: 'Heat pump incentives',
399
403
  link: `${ES_DOMAIN}/heat-pumps/heat-pump-incentives/`,
400
404
  },
405
+ {
406
+ name: 'Financing',
407
+ link: `${ES_DOMAIN}/financing/`,
408
+ },
401
409
  {
402
410
  name: 'Get personalized quotes',
403
411
  link: `${ES_DOMAIN}/shop/heat-pumps/`,