@energysage/es-ds-components 0.0.1 → 3.0.0-alpha.10
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/.eslintignore +9 -0
- package/.eslintrc.json +3 -0
- package/.prettierrc.json +10 -0
- package/README.md +0 -3
- package/components/es-accordion-list.vue +117 -0
- package/components/es-accordion.vue +7 -0
- package/components/es-badge.vue +20 -0
- package/components/es-breadcrumbs.vue +73 -0
- package/components/es-button.vue +122 -0
- package/components/es-card.vue +64 -0
- package/components/es-carousel.vue +316 -0
- package/components/es-col.vue +27 -0
- package/components/es-collapse.vue +83 -0
- package/components/es-container.vue +14 -0
- package/components/es-cta-banner.vue +95 -0
- package/components/es-cta-card.vue +60 -0
- package/components/es-data-table-simple.vue +73 -0
- package/components/es-data-table.vue +128 -0
- package/components/es-error-page.vue +120 -0
- package/components/es-file-input.vue +334 -0
- package/components/es-file-preview-modal.vue +61 -0
- package/components/es-footer.vue +163 -0
- package/components/es-form-checkbox.vue +40 -0
- package/components/es-form-input.vue +130 -0
- package/components/es-form-msg.vue +81 -0
- package/components/es-form-radio-card.vue +57 -0
- package/components/es-form-radio-cards.vue +78 -0
- package/components/es-form-textarea.vue +124 -0
- package/components/es-form.vue +16 -0
- package/components/es-modal.vue +103 -0
- package/components/es-nav-bar-account-menu.vue +70 -0
- package/components/es-nav-bar-featured-article.vue +60 -0
- package/components/es-nav-bar-link.vue +27 -0
- package/components/es-nav-bar-product-menu.vue +219 -0
- package/components/es-nav-bar-top-level-menu.vue +194 -0
- package/components/es-nav-bar-topic-menu.vue +197 -0
- package/components/es-nav-bar.vue +411 -0
- package/components/es-pagination.vue +67 -0
- package/components/es-popover.vue +113 -0
- package/components/es-progress-circle.vue +81 -0
- package/components/es-progress.vue +36 -0
- package/components/es-radio-button-group.vue +53 -0
- package/components/es-radio-button.vue +57 -0
- package/components/es-rating.vue +120 -0
- package/components/es-reviews-io-card-carousel.vue +279 -0
- package/components/es-row.vue +5 -0
- package/components/es-skeleton-img.vue +38 -0
- package/components/es-skeleton-wrapper.vue +17 -0
- package/components/es-skeleton.vue +97 -0
- package/components/es-slider.vue +218 -0
- package/components/es-spinner.vue +22 -0
- package/components/es-support-card.vue +150 -0
- package/components/es-support.vue +66 -0
- package/components/es-tab.vue +7 -0
- package/components/es-tabs.vue +70 -0
- package/components/es-verification-code.vue +94 -0
- package/components/es-video.vue +60 -0
- package/components/es-view-more.vue +64 -0
- package/components/es-zip-code-form.vue +204 -0
- package/components/icon/arrow-clockwise.vue +51 -0
- package/components/icon/arrow-counter-clockwise.vue +37 -0
- package/components/icon/arrow-cycle.vue +43 -0
- package/components/icon/arrow-left.vue +39 -0
- package/components/icon/arrow-right.vue +39 -0
- package/components/icon/arrow-up.vue +39 -0
- package/components/icon/arrow.vue +68 -0
- package/components/icon/bank.vue +38 -0
- package/components/icon/battery.vue +40 -0
- package/components/icon/bell.vue +42 -0
- package/components/icon/block.vue +46 -0
- package/components/icon/bolt.vue +38 -0
- package/components/icon/business.vue +42 -0
- package/components/icon/calculator.vue +37 -0
- package/components/icon/calendar.vue +46 -0
- package/components/icon/cart.vue +46 -0
- package/components/icon/chat-add.vue +44 -0
- package/components/icon/chat-approve.vue +42 -0
- package/components/icon/chat-bubble.vue +40 -0
- package/components/icon/chat-dots.vue +42 -0
- package/components/icon/check.vue +39 -0
- package/components/icon/chevron-down.vue +39 -0
- package/components/icon/chevron-horizontal.vue +39 -0
- package/components/icon/chevron-left.vue +39 -0
- package/components/icon/chevron-right.vue +39 -0
- package/components/icon/chevron-up.vue +39 -0
- package/components/icon/circle-alert.vue +46 -0
- package/components/icon/circle-check.vue +46 -0
- package/components/icon/circle-chevron-down.vue +47 -0
- package/components/icon/circle-chevron-left.vue +46 -0
- package/components/icon/circle-chevron-right.vue +46 -0
- package/components/icon/circle-chevron-up.vue +46 -0
- package/components/icon/circle-minus.vue +46 -0
- package/components/icon/circle-plus.vue +46 -0
- package/components/icon/circle-x.vue +46 -0
- package/components/icon/circle.vue +42 -0
- package/components/icon/clock.vue +46 -0
- package/components/icon/community-solar.vue +37 -0
- package/components/icon/companies.vue +43 -0
- package/components/icon/contact-us.vue +37 -0
- package/components/icon/copy.vue +45 -0
- package/components/icon/credit-card.vue +37 -0
- package/components/icon/document.vue +45 -0
- package/components/icon/electric-vehicle.vue +37 -0
- package/components/icon/electricity-circle.vue +39 -0
- package/components/icon/envelope.vue +45 -0
- package/components/icon/es-leaf.vue +39 -0
- package/components/icon/eye.vue +41 -0
- package/components/icon/facebook.vue +41 -0
- package/components/icon/file-doc.vue +72 -0
- package/components/icon/file-docx.vue +72 -0
- package/components/icon/file-pdf.vue +72 -0
- package/components/icon/flag.vue +37 -0
- package/components/icon/folder.vue +41 -0
- package/components/icon/gear.vue +37 -0
- package/components/icon/gift.vue +37 -0
- package/components/icon/hamburger.vue +38 -0
- package/components/icon/heart.vue +39 -0
- package/components/icon/heat-pump.vue +41 -0
- package/components/icon/image.vue +45 -0
- package/components/icon/info-blog-post.vue +47 -0
- package/components/icon/info.vue +45 -0
- package/components/icon/instagram.vue +49 -0
- package/components/icon/light-bulb.vue +36 -0
- package/components/icon/link-out.vue +38 -0
- package/components/icon/linkedin.vue +41 -0
- package/components/icon/location.vue +45 -0
- package/components/icon/lock-off.vue +45 -0
- package/components/icon/lock-on.vue +48 -0
- package/components/icon/marketplace.vue +43 -0
- package/components/icon/minus.vue +38 -0
- package/components/icon/money-bag.vue +37 -0
- package/components/icon/money.vue +35 -0
- package/components/icon/open-envelope.vue +45 -0
- package/components/icon/paper.vue +45 -0
- package/components/icon/payback.vue +37 -0
- package/components/icon/pencil.vue +45 -0
- package/components/icon/person-add.vue +41 -0
- package/components/icon/person-check.vue +41 -0
- package/components/icon/person-cross.vue +41 -0
- package/components/icon/person.vue +41 -0
- package/components/icon/phone.vue +41 -0
- package/components/icon/play.vue +41 -0
- package/components/icon/plus.vue +38 -0
- package/components/icon/roof.vue +37 -0
- package/components/icon/rooftop-solar.vue +37 -0
- package/components/icon/saving.vue +37 -0
- package/components/icon/search.vue +43 -0
- package/components/icon/settings-horizontal.vue +57 -0
- package/components/icon/settings-vertical.vue +57 -0
- package/components/icon/shield-with-check.vue +38 -0
- package/components/icon/shield.vue +41 -0
- package/components/icon/shopping-bag.vue +39 -0
- package/components/icon/solar.vue +37 -0
- package/components/icon/sound-down.vue +45 -0
- package/components/icon/sound-off.vue +41 -0
- package/components/icon/sound-on.vue +41 -0
- package/components/icon/sound-up.vue +41 -0
- package/components/icon/star-empty.vue +38 -0
- package/components/icon/star-full.vue +36 -0
- package/components/icon/star-half.vue +55 -0
- package/components/icon/state-ak.vue +53 -0
- package/components/icon/state-al.vue +38 -0
- package/components/icon/state-ar.vue +38 -0
- package/components/icon/state-az.vue +38 -0
- package/components/icon/state-ca.vue +38 -0
- package/components/icon/state-co.vue +38 -0
- package/components/icon/state-ct.vue +38 -0
- package/components/icon/state-dc.vue +52 -0
- package/components/icon/state-de.vue +38 -0
- package/components/icon/state-fl.vue +38 -0
- package/components/icon/state-ga.vue +38 -0
- package/components/icon/state-hi.vue +59 -0
- package/components/icon/state-ia.vue +38 -0
- package/components/icon/state-id.vue +38 -0
- package/components/icon/state-il.vue +38 -0
- package/components/icon/state-in.vue +38 -0
- package/components/icon/state-ks.vue +38 -0
- package/components/icon/state-ky.vue +38 -0
- package/components/icon/state-la.vue +38 -0
- package/components/icon/state-ma.vue +38 -0
- package/components/icon/state-md.vue +39 -0
- package/components/icon/state-me.vue +38 -0
- package/components/icon/state-mi.vue +38 -0
- package/components/icon/state-mn.vue +38 -0
- package/components/icon/state-mo.vue +38 -0
- package/components/icon/state-ms.vue +38 -0
- package/components/icon/state-mt.vue +38 -0
- package/components/icon/state-nc.vue +38 -0
- package/components/icon/state-nd.vue +38 -0
- package/components/icon/state-ne.vue +38 -0
- package/components/icon/state-nh.vue +38 -0
- package/components/icon/state-nj.vue +37 -0
- package/components/icon/state-nm.vue +38 -0
- package/components/icon/state-nv.vue +38 -0
- package/components/icon/state-ny.vue +41 -0
- package/components/icon/state-oh.vue +38 -0
- package/components/icon/state-ok.vue +38 -0
- package/components/icon/state-or.vue +38 -0
- package/components/icon/state-pa.vue +38 -0
- package/components/icon/state-ri.vue +38 -0
- package/components/icon/state-sc.vue +38 -0
- package/components/icon/state-sd.vue +38 -0
- package/components/icon/state-tn.vue +38 -0
- package/components/icon/state-tx.vue +38 -0
- package/components/icon/state-ut.vue +38 -0
- package/components/icon/state-va.vue +38 -0
- package/components/icon/state-vt.vue +38 -0
- package/components/icon/state-wa.vue +38 -0
- package/components/icon/state-wi.vue +38 -0
- package/components/icon/state-wv.vue +38 -0
- package/components/icon/state-wy.vue +38 -0
- package/components/icon/thumbs-up.vue +39 -0
- package/components/icon/trash-can.vue +41 -0
- package/components/icon/tree.vue +37 -0
- package/components/icon/twitter.vue +43 -0
- package/components/icon/upload.vue +41 -0
- package/components/icon/verified.vue +40 -0
- package/components/icon/video-play.vue +41 -0
- package/components/icon/wifi.vue +38 -0
- package/components/icon/x.vue +38 -0
- package/components/icon/youtube.vue +45 -0
- package/composables/forms.ts +124 -0
- package/nuxt.config.ts +83 -1
- package/package.json +47 -13
- package/tsconfig.json +4 -0
- package/types/es-carousel.ts +12 -0
- package/types/index.ts +1 -0
- package/utils/forms.ts +120 -0
- package/utils/get-es-footer-content.ts +90 -0
- package/utils/get-es-nav-bar-account-content.ts +55 -0
- package/utils/get-es-nav-bar-global-content.ts +482 -0
- package/utils/mime-type-finder.ts +48 -0
- package/utils/vuelidate-keys.ts +39 -0
- package/components/TestComponent.vue +0 -5
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
TODO:
|
|
4
|
+
- responsive carousels briefly show mobile amount of dots on desktop, until hydration
|
|
5
|
+
- i think we may be able to fix this with CSS, because we know how many dots there should be
|
|
6
|
+
- (something PrimeVue Carousel should really be doing)
|
|
7
|
+
- circular has quirky behavior when numVisible doesn't match numScroll
|
|
8
|
+
- you can see this in the circular autoplay example
|
|
9
|
+
- i'm not sure if this is fixable
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import Carousel from 'primevue/carousel';
|
|
13
|
+
import sassBreakpoints from '@energysage/es-ds-styles/scss/modules/breakpoints.module.scss';
|
|
14
|
+
import type { EsCarouselBreakpointsInterface } from '../types';
|
|
15
|
+
|
|
16
|
+
interface IProps {
|
|
17
|
+
autoPlay?: boolean;
|
|
18
|
+
autoPlayInterval?: number;
|
|
19
|
+
breakpoints?: EsCarouselBreakpointsInterface;
|
|
20
|
+
circular?: boolean;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
items: Array<any>;
|
|
23
|
+
numScroll?: number;
|
|
24
|
+
numVisible?: number;
|
|
25
|
+
showArrows?: boolean;
|
|
26
|
+
showDots?: boolean;
|
|
27
|
+
}
|
|
28
|
+
const props = withDefaults(defineProps<IProps>(), {
|
|
29
|
+
autoPlay: false,
|
|
30
|
+
autoPlayInterval: 2000,
|
|
31
|
+
breakpoints: () => ({}),
|
|
32
|
+
circular: false,
|
|
33
|
+
items: () => [],
|
|
34
|
+
numScroll: 1,
|
|
35
|
+
numVisible: 1,
|
|
36
|
+
showArrows: true,
|
|
37
|
+
showDots: true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits<{
|
|
41
|
+
update: [value: number];
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
// get the breakpoint pixel numbers from the SASS variables in es-ds-styles
|
|
45
|
+
const parseSassBreakpoint = (breakpoint: string) => parseInt(breakpoint.replace('px', ''), 10);
|
|
46
|
+
const BREAKPOINTS = {
|
|
47
|
+
SM: parseSassBreakpoint(sassBreakpoints.sm),
|
|
48
|
+
MD: parseSassBreakpoint(sassBreakpoints.md),
|
|
49
|
+
LG: parseSassBreakpoint(sassBreakpoints.lg),
|
|
50
|
+
XL: parseSassBreakpoint(sassBreakpoints.xl),
|
|
51
|
+
XXL: parseSassBreakpoint(sassBreakpoints.xxl),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// lower breakpoint values propagate to higher breakpoints unless overridden
|
|
55
|
+
const numVisibleXs = computed(() => props.numVisible);
|
|
56
|
+
const numVisibleSm = computed(() => props.breakpoints?.sm?.numVisible || numVisibleXs.value);
|
|
57
|
+
const numVisibleMd = computed(() => props.breakpoints?.md?.numVisible || numVisibleSm.value);
|
|
58
|
+
const numVisibleLg = computed(() => props.breakpoints?.lg?.numVisible || numVisibleMd.value);
|
|
59
|
+
const numVisibleXl = computed(() => props.breakpoints?.xl?.numVisible || numVisibleLg.value);
|
|
60
|
+
const numVisibleXxl = computed(() => props.breakpoints?.xxl?.numVisible || numVisibleXl.value);
|
|
61
|
+
|
|
62
|
+
// lower breakpoint values propagate to higher breakpoints unless overridden
|
|
63
|
+
const numScrollXs = computed(() => props.numScroll);
|
|
64
|
+
const numScrollSm = computed(() => props.breakpoints?.sm?.numScroll || numScrollXs.value);
|
|
65
|
+
const numScrollMd = computed(() => props.breakpoints?.md?.numScroll || numScrollSm.value);
|
|
66
|
+
const numScrollLg = computed(() => props.breakpoints?.lg?.numScroll || numScrollMd.value);
|
|
67
|
+
const numScrollXl = computed(() => props.breakpoints?.xl?.numScroll || numScrollLg.value);
|
|
68
|
+
const numScrollXxl = computed(() => props.breakpoints?.xxl?.numScroll || numScrollXl.value);
|
|
69
|
+
|
|
70
|
+
// in order to enable full-width carousels with one item
|
|
71
|
+
// but also ensure there is some space between items when more than one is visible
|
|
72
|
+
// only apply horizontal padding to carousel items on breakpoints where more than one item is visible
|
|
73
|
+
const DEFAULT_PADDING = '0.5rem';
|
|
74
|
+
const paddingXs = computed(() => (numVisibleXs.value === 1 ? '0' : DEFAULT_PADDING));
|
|
75
|
+
const paddingSm = computed(() => (numVisibleSm.value === 1 ? '0' : DEFAULT_PADDING));
|
|
76
|
+
const paddingMd = computed(() => (numVisibleMd.value === 1 ? '0' : DEFAULT_PADDING));
|
|
77
|
+
const paddingLg = computed(() => (numVisibleLg.value === 1 ? '0' : DEFAULT_PADDING));
|
|
78
|
+
const paddingXl = computed(() => (numVisibleXl.value === 1 ? '0' : DEFAULT_PADDING));
|
|
79
|
+
const paddingXxl = computed(() => (numVisibleXxl.value === 1 ? '0' : DEFAULT_PADDING));
|
|
80
|
+
|
|
81
|
+
const responsiveOptions = computed(() => {
|
|
82
|
+
// if no special breakpoints are defined, don't pass in any responsive options
|
|
83
|
+
if (!Object.keys(props.breakpoints).length) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
// XXL breakpoint
|
|
89
|
+
{
|
|
90
|
+
// max width of XXL is infinite, so let's use 9999px
|
|
91
|
+
breakpoint: '9999px',
|
|
92
|
+
numScroll: numScrollXxl.value,
|
|
93
|
+
numVisible: numVisibleXxl.value,
|
|
94
|
+
},
|
|
95
|
+
// XL breakpoint
|
|
96
|
+
{
|
|
97
|
+
// max width of XL is XXL minus one
|
|
98
|
+
breakpoint: `${BREAKPOINTS.XXL - 1}px`,
|
|
99
|
+
numScroll: numScrollXl.value,
|
|
100
|
+
numVisible: numVisibleXl.value,
|
|
101
|
+
},
|
|
102
|
+
// LG breakpoint
|
|
103
|
+
{
|
|
104
|
+
// max width of LG is XL minus one
|
|
105
|
+
breakpoint: `${BREAKPOINTS.XL - 1}px`,
|
|
106
|
+
numScroll: numScrollLg.value,
|
|
107
|
+
numVisible: numVisibleLg.value,
|
|
108
|
+
},
|
|
109
|
+
// MD breakpoint
|
|
110
|
+
{
|
|
111
|
+
// max width of MD is LG minus one
|
|
112
|
+
breakpoint: `${BREAKPOINTS.LG - 1}px`,
|
|
113
|
+
numScroll: numScrollMd.value,
|
|
114
|
+
numVisible: numVisibleMd.value,
|
|
115
|
+
},
|
|
116
|
+
// SM breakpoint
|
|
117
|
+
{
|
|
118
|
+
// max width of SM is MD minus one
|
|
119
|
+
breakpoint: `${BREAKPOINTS.MD - 1}px`,
|
|
120
|
+
numScroll: numScrollSm.value,
|
|
121
|
+
numVisible: numVisibleSm.value,
|
|
122
|
+
},
|
|
123
|
+
// XS breakpoint
|
|
124
|
+
// (this is necessary to avoid weird behavior on mobile)
|
|
125
|
+
{
|
|
126
|
+
// max width of XS is SM minus one
|
|
127
|
+
breakpoint: `${BREAKPOINTS.SM - 1}px`,
|
|
128
|
+
numScroll: props.numScroll,
|
|
129
|
+
numVisible: props.numVisible,
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const autoplayInterval = ref(props.autoPlay ? props.autoPlayInterval : 0);
|
|
135
|
+
const isMounted = ref(false);
|
|
136
|
+
const key = ref('');
|
|
137
|
+
|
|
138
|
+
const stopAutoplay = () => {
|
|
139
|
+
if (autoplayInterval.value > 0) {
|
|
140
|
+
autoplayInterval.value = 0;
|
|
141
|
+
key.value = 'stopAutoplay';
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
onMounted(() => {
|
|
146
|
+
/**
|
|
147
|
+
* avoids an unavoidable SSR issue with responsive carousels (SSR can't know the breakpoint
|
|
148
|
+
* and therefore how many items need to be displayed per page) where circular carousels have
|
|
149
|
+
* their last few items cloned and inserted before the first item in the list, meaning on initial
|
|
150
|
+
* page render, you see those cloned last items listed first rather than the first item.
|
|
151
|
+
*
|
|
152
|
+
* then, upon hydration, the items change and the first item is then listed first.
|
|
153
|
+
*
|
|
154
|
+
* this workaround disables circular functionality for SSR, and swaps to the user-provided
|
|
155
|
+
* setting for it on mount.
|
|
156
|
+
*/
|
|
157
|
+
isMounted.value = true;
|
|
158
|
+
|
|
159
|
+
document.addEventListener('keyup', (e) => {
|
|
160
|
+
if (e.key === 'Escape') {
|
|
161
|
+
// Stop carousel when user presses Escape key, in lieu of pause button
|
|
162
|
+
// https://www.w3.org/WAI/WCAG22/Techniques/general/G187.html
|
|
163
|
+
stopAutoplay();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<carousel
|
|
171
|
+
:key="key"
|
|
172
|
+
:autoplay-interval="autoplayInterval"
|
|
173
|
+
:circular="isMounted && props.circular"
|
|
174
|
+
:num-scroll="props.numScroll"
|
|
175
|
+
:num-visible="props.numVisible"
|
|
176
|
+
:responsive-options="responsiveOptions"
|
|
177
|
+
:show-indicators="props.showDots"
|
|
178
|
+
:show-navigators="props.showArrows"
|
|
179
|
+
:value="props.items"
|
|
180
|
+
:pt="{
|
|
181
|
+
container: {
|
|
182
|
+
class: 'd-flex',
|
|
183
|
+
},
|
|
184
|
+
indicator: {
|
|
185
|
+
class: 'es-carousel-dot',
|
|
186
|
+
},
|
|
187
|
+
indicators: {
|
|
188
|
+
class: 'es-carousel-dots d-flex justify-content-center mt-100',
|
|
189
|
+
},
|
|
190
|
+
itemsContent: {
|
|
191
|
+
class: 'w-100 overflow-hidden',
|
|
192
|
+
},
|
|
193
|
+
itemsContainer: {
|
|
194
|
+
class: 'd-flex',
|
|
195
|
+
},
|
|
196
|
+
item: {
|
|
197
|
+
class: 'es-carousel-item',
|
|
198
|
+
},
|
|
199
|
+
itemCloned: {
|
|
200
|
+
class: 'es-carousel-item',
|
|
201
|
+
},
|
|
202
|
+
previousButton: {
|
|
203
|
+
class: 'es-carousel-arrow es-carousel-prev-arrow btn btn-outline-primary px-sm-50',
|
|
204
|
+
},
|
|
205
|
+
nextButton: {
|
|
206
|
+
class: 'es-carousel-arrow es-carousel-next-arrow btn btn-outline-primary px-sm-50',
|
|
207
|
+
},
|
|
208
|
+
}"
|
|
209
|
+
@update:page="(value: number) => emit('update', value)">
|
|
210
|
+
<template #item="item">
|
|
211
|
+
<slot
|
|
212
|
+
name="item"
|
|
213
|
+
:item="item.data" />
|
|
214
|
+
</template>
|
|
215
|
+
<template #previousicon>
|
|
216
|
+
<icon-chevron-left />
|
|
217
|
+
</template>
|
|
218
|
+
<template #nexticon>
|
|
219
|
+
<icon-chevron-right />
|
|
220
|
+
</template>
|
|
221
|
+
</carousel>
|
|
222
|
+
</template>
|
|
223
|
+
|
|
224
|
+
<style lang="scss" scoped>
|
|
225
|
+
@use '@energysage/es-ds-styles/scss/variables' as variables;
|
|
226
|
+
@use '@energysage/es-ds-styles/scss/mixins/breakpoints' as breakpoints;
|
|
227
|
+
|
|
228
|
+
:deep(.es-carousel-item) {
|
|
229
|
+
flex: 1 0 calc(100% / v-bind(numVisibleXs));
|
|
230
|
+
padding: 0 v-bind(paddingXs);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@include breakpoints.media-breakpoint-up(sm) {
|
|
234
|
+
:deep(.es-carousel-item) {
|
|
235
|
+
flex: 1 0 calc(100% / v-bind(numVisibleSm));
|
|
236
|
+
padding: 0 v-bind(paddingSm);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@include breakpoints.media-breakpoint-up(md) {
|
|
241
|
+
:deep(.es-carousel-item) {
|
|
242
|
+
flex: 1 0 calc(100% / v-bind(numVisibleMd));
|
|
243
|
+
padding: 0 v-bind(paddingMd);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@include breakpoints.media-breakpoint-up(lg) {
|
|
248
|
+
:deep(.es-carousel-item) {
|
|
249
|
+
flex: 1 0 calc(100% / v-bind(numVisibleLg));
|
|
250
|
+
padding: 0 v-bind(paddingLg);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@include breakpoints.media-breakpoint-up(xl) {
|
|
255
|
+
:deep(.es-carousel-item) {
|
|
256
|
+
flex: 1 0 calc(100% / v-bind(numVisibleXl));
|
|
257
|
+
padding: 0 v-bind(paddingXl);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@include breakpoints.media-breakpoint-up(xxl) {
|
|
262
|
+
:deep(.es-carousel-item) {
|
|
263
|
+
flex: 1 0 calc(100% / v-bind(numVisibleXxl));
|
|
264
|
+
padding: 0 v-bind(paddingXxl);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
:deep(.es-carousel-arrow) {
|
|
269
|
+
background: unset;
|
|
270
|
+
border: unset;
|
|
271
|
+
box-shadow: none;
|
|
272
|
+
color: variables.$gray-900;
|
|
273
|
+
height: auto;
|
|
274
|
+
|
|
275
|
+
&:hover {
|
|
276
|
+
color: variables.$gray-700;
|
|
277
|
+
}
|
|
278
|
+
&:focus {
|
|
279
|
+
color: variables.$gray-900;
|
|
280
|
+
}
|
|
281
|
+
&:not(:disabled):not(.disabled):active {
|
|
282
|
+
background: unset;
|
|
283
|
+
box-shadow: none;
|
|
284
|
+
color: variables.$gray-700;
|
|
285
|
+
}
|
|
286
|
+
&:disabled {
|
|
287
|
+
color: variables.$gray-400;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
:deep(.es-carousel-dots) {
|
|
292
|
+
gap: 0.7rem;
|
|
293
|
+
padding-left: 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
:deep(.es-carousel-dot) {
|
|
297
|
+
list-style-type: none;
|
|
298
|
+
margin-bottom: 0;
|
|
299
|
+
|
|
300
|
+
&[data-p-highlight='true'] button {
|
|
301
|
+
background-color: variables.$orange-800;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
button {
|
|
305
|
+
background-color: variables.$gray-100;
|
|
306
|
+
border: none;
|
|
307
|
+
border-radius: 50%;
|
|
308
|
+
height: 0.875rem;
|
|
309
|
+
width: 0.875rem;
|
|
310
|
+
|
|
311
|
+
&:hover {
|
|
312
|
+
opacity: 0.8;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
cols?: string;
|
|
4
|
+
sm?: string;
|
|
5
|
+
md?: string;
|
|
6
|
+
lg?: string;
|
|
7
|
+
xl?: string;
|
|
8
|
+
xxl?: string;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
const undefinedSize = !(props.cols || props.sm || props.md || props.lg || props.xl || props.xxl);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div
|
|
16
|
+
:class="{
|
|
17
|
+
col: undefinedSize,
|
|
18
|
+
[`col-${cols}`]: cols,
|
|
19
|
+
[`col-sm${sm ? `-${sm}` : ''}`]: sm !== undefined,
|
|
20
|
+
[`col-md${md ? `-${md}` : ''}`]: md !== undefined,
|
|
21
|
+
[`col-lg${lg ? `-${lg}` : ''}`]: lg !== undefined,
|
|
22
|
+
[`col-xl${xl ? `-${xl}` : ''}`]: xl !== undefined,
|
|
23
|
+
[`col-xxl${xxl ? `-${xxl}` : ''}`]: xxl !== undefined,
|
|
24
|
+
}">
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import Panel from 'primevue/panel';
|
|
3
|
+
|
|
4
|
+
const emit = defineEmits(['toggled', 'userClick']);
|
|
5
|
+
|
|
6
|
+
const model = defineModel<boolean>();
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
border: {
|
|
9
|
+
type: Boolean,
|
|
10
|
+
default: true,
|
|
11
|
+
},
|
|
12
|
+
isProgrammaticUntilUserInput: {
|
|
13
|
+
type: Boolean,
|
|
14
|
+
default: true,
|
|
15
|
+
},
|
|
16
|
+
visible: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: false,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const userDeterminedState: Ref<boolean | null> = ref(null);
|
|
23
|
+
|
|
24
|
+
// directly determines the expanded/collapsed state of the panel
|
|
25
|
+
const expanded = computed(() => {
|
|
26
|
+
// if we are meant to start ignoring the programmatic incoming state as soon as the
|
|
27
|
+
// user interacts with the collapse, and if the user has interacted with the collapse,
|
|
28
|
+
// use the value from the user's interaction
|
|
29
|
+
if (props.isProgrammaticUntilUserInput && userDeterminedState.value !== null) {
|
|
30
|
+
return userDeterminedState.value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// otherwise, use the one-way visible prop or the two-way v-model value
|
|
34
|
+
return props.visible || model.value;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const onClick = ({ value }: { value: boolean }) => {
|
|
38
|
+
// save the state toggled by the user
|
|
39
|
+
// convert from "collapsed" semantics internal to the primevue panel
|
|
40
|
+
// to the "expanded" semantics exposed by this component
|
|
41
|
+
userDeterminedState.value = !value;
|
|
42
|
+
|
|
43
|
+
// inform the v-model (if any) that the state has changed
|
|
44
|
+
model.value = !value;
|
|
45
|
+
|
|
46
|
+
// emit events indicating the state has changed
|
|
47
|
+
emit('toggled', !value);
|
|
48
|
+
emit('userClick', !value);
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<panel
|
|
54
|
+
:collapsed="!expanded"
|
|
55
|
+
:pt="{
|
|
56
|
+
header: 'align-items-center d-flex justify-content-between position-relative py-100',
|
|
57
|
+
icons: 'h-100 position-absolute w-100',
|
|
58
|
+
root: border ? 'border-bottom border-top' : '',
|
|
59
|
+
toggler: {
|
|
60
|
+
// eslint-disable-next-line max-len
|
|
61
|
+
class: 'es-collapse-toggler align-items-center btn btn-link d-flex h-100 inline justify-content-end rounded-0 p-0 position-absolute text-body w-100',
|
|
62
|
+
},
|
|
63
|
+
// https://vuejs.org/guide/built-ins/transition
|
|
64
|
+
transition: {
|
|
65
|
+
enterActiveClass: 'es-collapse-enter-active',
|
|
66
|
+
enterFromClass: 'es-collapse-enter-from',
|
|
67
|
+
enterToClass: 'es-collapse-enter-to',
|
|
68
|
+
leaveActiveClass: 'es-collapse-leave-active',
|
|
69
|
+
leaveFromClass: 'es-collapse-leave-from',
|
|
70
|
+
leaveToClass: 'es-collapse-leave-to',
|
|
71
|
+
},
|
|
72
|
+
}"
|
|
73
|
+
toggleable
|
|
74
|
+
@toggle="onClick">
|
|
75
|
+
<template #header>
|
|
76
|
+
<slot name="title" />
|
|
77
|
+
</template>
|
|
78
|
+
<template #togglericon>
|
|
79
|
+
<icon-chevron-down />
|
|
80
|
+
</template>
|
|
81
|
+
<slot />
|
|
82
|
+
</panel>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface IProps {
|
|
3
|
+
fluid?: boolean;
|
|
4
|
+
}
|
|
5
|
+
const props = withDefaults(defineProps<IProps>(), {
|
|
6
|
+
fluid: false,
|
|
7
|
+
});
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div :class="props.fluid ? 'container-fluid' : 'container'">
|
|
12
|
+
<slot />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
dark: {
|
|
4
|
+
type: Boolean,
|
|
5
|
+
default: false,
|
|
6
|
+
},
|
|
7
|
+
hasButton: {
|
|
8
|
+
type: Boolean,
|
|
9
|
+
default: false,
|
|
10
|
+
},
|
|
11
|
+
// default or wide
|
|
12
|
+
variant: {
|
|
13
|
+
type: String,
|
|
14
|
+
default: 'default',
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const lgFirst = computed(() => {
|
|
19
|
+
if (props.variant === 'wide') {
|
|
20
|
+
return props.hasButton ? '8' : '6';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return props.hasButton ? '7' : '4';
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const lgSecond = computed(() => {
|
|
27
|
+
if (props.variant === 'wide') {
|
|
28
|
+
return props.hasButton ? '4' : '6';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return props.hasButton ? '5' : '8';
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const xxlFirst = computed(() => {
|
|
35
|
+
if (props.variant === 'wide') {
|
|
36
|
+
return props.hasButton ? '8' : '6';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return props.hasButton ? '8' : '5';
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const xxlSecond = computed(() => {
|
|
43
|
+
if (props.variant === 'wide') {
|
|
44
|
+
return props.hasButton ? '4' : '6';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return props.hasButton ? '4' : '7';
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<es-card
|
|
53
|
+
:class="[
|
|
54
|
+
{
|
|
55
|
+
'bg-dark-blue border-dark-blue text-white': dark,
|
|
56
|
+
'px-100 px-lg-200': variant === 'default',
|
|
57
|
+
'px-100 px-lg-300': variant === 'wide',
|
|
58
|
+
},
|
|
59
|
+
]"
|
|
60
|
+
v-bind="$attrs">
|
|
61
|
+
<es-row>
|
|
62
|
+
<es-col
|
|
63
|
+
class="mb-200 my-lg-auto text-center text-lg-left"
|
|
64
|
+
:lg="lgFirst"
|
|
65
|
+
:xxl="xxlFirst">
|
|
66
|
+
<!-- avoiding use of an <h2> tag here for long-form content SEO reasons,
|
|
67
|
+
but preserving heading semantics for screen readers -->
|
|
68
|
+
<div
|
|
69
|
+
role="heading"
|
|
70
|
+
aria-level="2"
|
|
71
|
+
class="font-weight-semibold"
|
|
72
|
+
:class="{
|
|
73
|
+
'font-size-300': variant === 'default',
|
|
74
|
+
'mb-50': $slots.subtitle,
|
|
75
|
+
'mb-0': !$slots.subtitle,
|
|
76
|
+
'text-white': dark,
|
|
77
|
+
h2: variant === 'wide',
|
|
78
|
+
}">
|
|
79
|
+
<slot name="heading"> Easily find what solar costs in your area </slot>
|
|
80
|
+
</div>
|
|
81
|
+
<p
|
|
82
|
+
v-if="$slots.subtitle"
|
|
83
|
+
class="mb-0">
|
|
84
|
+
<slot name="subtitle" />
|
|
85
|
+
</p>
|
|
86
|
+
</es-col>
|
|
87
|
+
<es-col
|
|
88
|
+
class="d-flex justify-content-center justify-content-lg-end my-auto"
|
|
89
|
+
:lg="lgSecond"
|
|
90
|
+
:xxl="xxlSecond">
|
|
91
|
+
<slot name="cta" />
|
|
92
|
+
</es-col>
|
|
93
|
+
</es-row>
|
|
94
|
+
</es-card>
|
|
95
|
+
</template>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
imageFirst: {
|
|
4
|
+
type: Boolean,
|
|
5
|
+
default: false,
|
|
6
|
+
},
|
|
7
|
+
horizontalPadding: {
|
|
8
|
+
type: String,
|
|
9
|
+
default: 'px-100',
|
|
10
|
+
},
|
|
11
|
+
verticalSpacing: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: 'mb-150',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<es-card
|
|
20
|
+
class="align-items-center d-flex flex-column py-200 text-center"
|
|
21
|
+
:class="props.horizontalPadding"
|
|
22
|
+
v-bind="$attrs">
|
|
23
|
+
<!-- avoiding use of an <h2> tag here for long-form content SEO reasons,
|
|
24
|
+
but preserving heading semantics for screen readers -->
|
|
25
|
+
<div
|
|
26
|
+
role="heading"
|
|
27
|
+
aria-level="2"
|
|
28
|
+
class="font-weight-semibold font-size-300"
|
|
29
|
+
:class="[{ 'order-1': props.imageFirst }, props.verticalSpacing]">
|
|
30
|
+
<slot name="heading"> Easily find what solar costs in your area </slot>
|
|
31
|
+
</div>
|
|
32
|
+
<div
|
|
33
|
+
v-if="$slots.image"
|
|
34
|
+
class="mx-auto w-100"
|
|
35
|
+
:class="props.verticalSpacing">
|
|
36
|
+
<slot name="image" />
|
|
37
|
+
</div>
|
|
38
|
+
<div
|
|
39
|
+
class="w-100"
|
|
40
|
+
:class="{ 'order-2': props.imageFirst }">
|
|
41
|
+
<div
|
|
42
|
+
v-if="$slots.subtitle"
|
|
43
|
+
class="es-cta-card-subtitle"
|
|
44
|
+
:class="props.verticalSpacing">
|
|
45
|
+
<slot name="subtitle" />
|
|
46
|
+
</div>
|
|
47
|
+
<slot name="cta" />
|
|
48
|
+
</div>
|
|
49
|
+
</es-card>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style lang="scss" scoped>
|
|
53
|
+
.es-cta-card {
|
|
54
|
+
&-subtitle {
|
|
55
|
+
:deep(p:last-child) {
|
|
56
|
+
margin-bottom: 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
fields: {
|
|
4
|
+
type: Array,
|
|
5
|
+
default: null,
|
|
6
|
+
},
|
|
7
|
+
items: {
|
|
8
|
+
type: Array,
|
|
9
|
+
default: () => [],
|
|
10
|
+
},
|
|
11
|
+
tableClass: {
|
|
12
|
+
type: [Array, String],
|
|
13
|
+
default: null,
|
|
14
|
+
},
|
|
15
|
+
striped: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const isSingleCol = computed(() => !props.fields);
|
|
22
|
+
const computedItems: ComputedRef<any[]> = computed(() => {
|
|
23
|
+
if (!props.items.length && !useSlots().default) {
|
|
24
|
+
throw new Error('Neither items prop nor slot content is defined.');
|
|
25
|
+
}
|
|
26
|
+
// If there are no fields, just return the items as a single-column array
|
|
27
|
+
if (!props.fields) {
|
|
28
|
+
return props.items;
|
|
29
|
+
}
|
|
30
|
+
if (props.fields.length !== props.items.length) {
|
|
31
|
+
throw new Error('The number of fields and items must be the same.');
|
|
32
|
+
}
|
|
33
|
+
// If fields and items are both present, map them together
|
|
34
|
+
return props.fields.map((field, index) => [field, props.items[index]]);
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
<template>
|
|
38
|
+
<div
|
|
39
|
+
:class="tableClass"
|
|
40
|
+
class="table-responsive">
|
|
41
|
+
<table
|
|
42
|
+
v-if="$slots.default || items.length"
|
|
43
|
+
class="table table-borderless"
|
|
44
|
+
:class="{ 'table-striped': striped }">
|
|
45
|
+
<template v-if="$slots.default">
|
|
46
|
+
<slot />
|
|
47
|
+
</template>
|
|
48
|
+
<tbody v-else>
|
|
49
|
+
<template v-if="isSingleCol">
|
|
50
|
+
<tr
|
|
51
|
+
v-for="(item, index) in computedItems"
|
|
52
|
+
:key="`${index}${item}`">
|
|
53
|
+
<td>
|
|
54
|
+
{{ item }}
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
</template>
|
|
58
|
+
<template v-else>
|
|
59
|
+
<tr
|
|
60
|
+
v-for="(item, index) in computedItems"
|
|
61
|
+
:key="`${index}${item[0]}`">
|
|
62
|
+
<td class="col-sm-7">
|
|
63
|
+
{{ item[0] }}
|
|
64
|
+
</td>
|
|
65
|
+
<td class="col-sm-4">
|
|
66
|
+
<b>{{ item[1] }}</b>
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
</template>
|
|
70
|
+
</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|