@energysage/es-ds-components 5.1.0 → 5.3.0
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/CHANGELOG.md +28 -0
- package/app/components/es-logo.vue +50 -0
- package/app/components/es-mobile-nav-content.vue +308 -0
- package/app/components/es-mobile-nav-custom-item.vue +9 -0
- package/app/components/es-mobile-nav-link.vue +62 -0
- package/app/components/es-mobile-nav-trigger.vue +29 -0
- package/app/components/es-mobile-nav.vue +219 -0
- package/app/components/es-mobile-sub-nav.vue +243 -0
- package/app/components/es-nav-cta-card.vue +113 -0
- package/app/components/es-sticky-bar.vue +3 -1
- package/app/components/icon/arrow-clockwise.vue +13 -48
- package/app/components/icon/arrow-counter-clockwise.vue +13 -34
- package/app/components/icon/arrow-cycle.vue +13 -40
- package/app/components/icon/arrow-down.vue +16 -0
- package/app/components/icon/arrow-left.vue +13 -36
- package/app/components/icon/arrow-right.vue +13 -36
- package/app/components/icon/arrow-up.vue +13 -36
- package/app/components/icon/battery-charging-vertical.vue +16 -0
- package/app/components/icon/bell.vue +13 -39
- package/app/components/icon/block.vue +13 -43
- package/app/components/icon/bolt.vue +12 -34
- package/app/components/icon/calculator.vue +13 -34
- package/app/components/icon/calendar.vue +13 -43
- package/app/components/icon/cart.vue +13 -43
- package/app/components/icon/charging-station.vue +16 -0
- package/app/components/icon/chat-bubble.vue +13 -37
- package/app/components/icon/chat-dots.vue +13 -39
- package/app/components/icon/check.vue +13 -36
- package/app/components/icon/chevron-down.vue +13 -36
- package/app/components/icon/chevron-horizontal.vue +13 -36
- package/app/components/icon/chevron-left.vue +13 -36
- package/app/components/icon/chevron-right.vue +13 -36
- package/app/components/icon/chevron-up.vue +13 -36
- package/app/components/icon/circle-alert.vue +13 -43
- package/app/components/icon/circle-check.vue +13 -43
- package/app/components/icon/circle-chevron-down.vue +12 -43
- package/app/components/icon/circle-chevron-left.vue +13 -43
- package/app/components/icon/circle-chevron-right.vue +13 -43
- package/app/components/icon/circle-chevron-up.vue +13 -43
- package/app/components/icon/circle-minus.vue +13 -43
- package/app/components/icon/circle-plus.vue +13 -43
- package/app/components/icon/circle-x.vue +13 -43
- package/app/components/icon/circle.vue +13 -39
- package/app/components/icon/clock.vue +13 -43
- package/app/components/icon/copy.vue +13 -42
- package/app/components/icon/envelope.vue +13 -42
- package/app/components/icon/eye.vue +13 -38
- package/app/components/icon/flag.vue +13 -34
- package/app/components/icon/folder.vue +13 -38
- package/app/components/icon/gear.vue +12 -33
- package/app/components/icon/hamburger.vue +13 -35
- package/app/components/icon/heart.vue +13 -36
- package/app/components/icon/house-line.vue +16 -0
- package/app/components/icon/image.vue +13 -42
- package/app/components/icon/info.vue +13 -42
- package/app/components/icon/light-bulb.vue +13 -33
- package/app/components/icon/link-out.vue +13 -35
- package/app/components/icon/location.vue +13 -42
- package/app/components/icon/lock-off.vue +13 -42
- package/app/components/icon/lock-on.vue +13 -45
- package/app/components/icon/minus.vue +13 -35
- package/app/components/icon/money-bag.vue +12 -33
- package/app/components/icon/open-envelope.vue +13 -42
- package/app/components/icon/paper.vue +13 -42
- package/app/components/icon/pencil.vue +13 -42
- package/app/components/icon/person-add.vue +13 -38
- package/app/components/icon/person-check.vue +13 -38
- package/app/components/icon/person-cross.vue +13 -38
- package/app/components/icon/person.vue +13 -38
- package/app/components/icon/phone.vue +13 -38
- package/app/components/icon/play.vue +13 -38
- package/app/components/icon/plus.vue +13 -35
- package/app/components/icon/roof.vue +12 -33
- package/app/components/icon/search.vue +12 -39
- package/app/components/icon/settings-horizontal.vue +13 -54
- package/app/components/icon/settings-vertical.vue +13 -54
- package/app/components/icon/shield-with-check.vue +12 -34
- package/app/components/icon/shield.vue +13 -38
- package/app/components/icon/shopping-bag.vue +13 -36
- package/app/components/icon/solar.vue +12 -33
- package/app/components/icon/sound-down.vue +13 -42
- package/app/components/icon/sound-off.vue +13 -38
- package/app/components/icon/sound-on.vue +13 -38
- package/app/components/icon/sound-up.vue +13 -38
- package/app/components/icon/star-empty.vue +13 -35
- package/app/components/icon/star-full.vue +15 -33
- package/app/components/icon/star-half.vue +15 -52
- package/app/components/icon/thumbs-up.vue +13 -36
- package/app/components/icon/trash-can.vue +13 -38
- package/app/components/icon/tree.vue +12 -33
- package/app/components/icon/upload.vue +12 -37
- package/app/components/icon/verified.vue +13 -37
- package/app/components/icon/wifi.vue +13 -35
- package/app/components/icon/x.vue +13 -35
- package/app/composables/breakpoints.ts +75 -0
- package/nuxt.config.ts +1 -1
- package/package.json +3 -2
- package/app/components/icon/arrow.vue +0 -68
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useScrollLock } from '@vueuse/core';
|
|
3
|
+
import { type NavigationMenuContent, NavigationMenuItem, NavigationMenuList, NavigationMenuRoot } from 'reka-ui';
|
|
4
|
+
import type { ShallowRef } from 'vue';
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
from?: 'left' | 'right';
|
|
8
|
+
width?: number;
|
|
9
|
+
}
|
|
10
|
+
const props = withDefaults(defineProps<IProps>(), {
|
|
11
|
+
from: 'left',
|
|
12
|
+
width: 400,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ANIMATION_DURATION = 200;
|
|
16
|
+
const ANIMATION_DURATION_MS = `${ANIMATION_DURATION}ms`;
|
|
17
|
+
|
|
18
|
+
const activeMenuId = ref('');
|
|
19
|
+
const navTriggerElement = ref<HTMLElement | null>(null);
|
|
20
|
+
const nameStack: Ref<string[]> = ref([]);
|
|
21
|
+
const scrollableContentAreaTemplateRef: Ref<Readonly<
|
|
22
|
+
ShallowRef<InstanceType<typeof NavigationMenuContent> | null>
|
|
23
|
+
> | null> = ref(null);
|
|
24
|
+
const subNavCloseHandlers: Ref<Function[]> = ref([]);
|
|
25
|
+
|
|
26
|
+
const displayedName = computed(() => nameStack.value.at(-1) ?? '');
|
|
27
|
+
const isScrollLocked = useScrollLock(import.meta.client ? document.body : null);
|
|
28
|
+
const widthPx = computed(() => `${props.width}px`);
|
|
29
|
+
|
|
30
|
+
// closes the top-level menu
|
|
31
|
+
const closeMenu = () => {
|
|
32
|
+
// focus the trigger before closing so assistive technologies (e.g. VoiceOver)
|
|
33
|
+
// don't lose focus when the menu content is removed from the DOM.
|
|
34
|
+
navTriggerElement.value?.focus();
|
|
35
|
+
|
|
36
|
+
// close the menu
|
|
37
|
+
activeMenuId.value = '';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// provides a read-only variable indicating the current depth
|
|
41
|
+
const currentDepth = computed(() => nameStack.value.length);
|
|
42
|
+
|
|
43
|
+
// closes the currently-visible menu
|
|
44
|
+
const goBack = () => {
|
|
45
|
+
const closeCurrentMenu = subNavCloseHandlers.value.pop();
|
|
46
|
+
if (closeCurrentMenu) {
|
|
47
|
+
closeCurrentMenu();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// animates up/back one level
|
|
52
|
+
const decreaseDepth = () => {
|
|
53
|
+
if (currentDepth.value > 0) {
|
|
54
|
+
nameStack.value = nameStack.value.slice(0, nameStack.value.length - 1);
|
|
55
|
+
|
|
56
|
+
// if we can, scroll to the top of the content area so the parent menu starts at the top
|
|
57
|
+
if (scrollableContentAreaTemplateRef.value?.value?.$el?.scrollTop) {
|
|
58
|
+
scrollableContentAreaTemplateRef.value.value.$el.scrollTop = 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// animates down/forward one level
|
|
64
|
+
const increaseDepth = (name: string) => {
|
|
65
|
+
nameStack.value.push(name);
|
|
66
|
+
|
|
67
|
+
// if we can, scroll to the top of the content area so the submenu starts at the top
|
|
68
|
+
if (scrollableContentAreaTemplateRef.value?.value?.$el?.scrollTop) {
|
|
69
|
+
scrollableContentAreaTemplateRef.value.value.$el.scrollTop = 0;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// allows the nav trigger to register itself so closeMenu can return focus to it
|
|
74
|
+
const registerNavTrigger = (el: HTMLElement) => {
|
|
75
|
+
navTriggerElement.value = el;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// allows us to scroll the content area up to the top when opening a submenu
|
|
79
|
+
const registerScrollableContentArea = (
|
|
80
|
+
areaTemplateRef: Readonly<ShallowRef<InstanceType<typeof NavigationMenuContent> | null>>,
|
|
81
|
+
) => {
|
|
82
|
+
scrollableContentAreaTemplateRef.value = areaTemplateRef;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// provides the back button with a function to call in order to close the active submenu
|
|
86
|
+
// (this is called by submenus when they are opened)
|
|
87
|
+
const registerSubNavCloseHandler = (callback: Function) => subNavCloseHandlers.value.push(callback);
|
|
88
|
+
|
|
89
|
+
// provides a way to wait for animation to complete prior to doing something
|
|
90
|
+
const waitForAnimationDuration = async () => {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
setTimeout(() => resolve(true), ANIMATION_DURATION);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
provide('animationDuration', ANIMATION_DURATION);
|
|
97
|
+
provide('closeMenu', closeMenu);
|
|
98
|
+
provide('currentDepth', currentDepth);
|
|
99
|
+
provide('decreaseDepth', decreaseDepth);
|
|
100
|
+
provide('displayedName', displayedName);
|
|
101
|
+
provide('from', toRef(props, 'from'));
|
|
102
|
+
provide('goBack', goBack);
|
|
103
|
+
provide('increaseDepth', increaseDepth);
|
|
104
|
+
provide('registerNavTrigger', registerNavTrigger);
|
|
105
|
+
provide('registerScrollableContentArea', registerScrollableContentArea);
|
|
106
|
+
provide('registerSubNavCloseHandler', registerSubNavCloseHandler);
|
|
107
|
+
provide('waitForAnimationDuration', waitForAnimationDuration);
|
|
108
|
+
provide('width', toRef(props, 'width'));
|
|
109
|
+
provide('widthPx', widthPx);
|
|
110
|
+
|
|
111
|
+
watch(activeMenuId, async (newVal: string, oldVal: string) => {
|
|
112
|
+
isScrollLocked.value = !!newVal;
|
|
113
|
+
|
|
114
|
+
// if the menu is closing
|
|
115
|
+
if (!newVal && oldVal) {
|
|
116
|
+
// wait until the mobile nav closing animation is complete
|
|
117
|
+
await waitForAnimationDuration();
|
|
118
|
+
|
|
119
|
+
// reset the state of all submenus by closing all open ones (without animation)
|
|
120
|
+
subNavCloseHandlers.value.forEach((callback: Function) => {
|
|
121
|
+
callback(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// clear out all subnav close handlers since all of them are closed
|
|
125
|
+
subNavCloseHandlers.value = [];
|
|
126
|
+
|
|
127
|
+
// clear nameStack to set the animation "window" to first position
|
|
128
|
+
nameStack.value = [];
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<template>
|
|
134
|
+
<navigation-menu-root
|
|
135
|
+
v-model="activeMenuId"
|
|
136
|
+
v-bind="$attrs"
|
|
137
|
+
class="es-mobile-nav d-flex"
|
|
138
|
+
disable-hover-trigger
|
|
139
|
+
disable-pointer-leave-close>
|
|
140
|
+
<!-- overlay -->
|
|
141
|
+
<teleport to="body">
|
|
142
|
+
<div
|
|
143
|
+
class="es-mobile-nav-overlay position-fixed"
|
|
144
|
+
:class="{
|
|
145
|
+
active: !!activeMenuId,
|
|
146
|
+
}"
|
|
147
|
+
@click="activeMenuId = ''" />
|
|
148
|
+
</teleport>
|
|
149
|
+
|
|
150
|
+
<!--
|
|
151
|
+
since the first level is a single item - the trigger to open the mobile nav -
|
|
152
|
+
prevent it from being a <ul> and <li>
|
|
153
|
+
-->
|
|
154
|
+
<navigation-menu-list
|
|
155
|
+
as="div"
|
|
156
|
+
class="es-mobile-nav-list d-flex">
|
|
157
|
+
<navigation-menu-item
|
|
158
|
+
as="div"
|
|
159
|
+
class="d-flex">
|
|
160
|
+
<slot />
|
|
161
|
+
</navigation-menu-item>
|
|
162
|
+
</navigation-menu-list>
|
|
163
|
+
</navigation-menu-root>
|
|
164
|
+
</template>
|
|
165
|
+
|
|
166
|
+
<style lang="scss" scoped>
|
|
167
|
+
@use '@energysage/es-ds-styles/scss/mixins/breakpoints' as breakpoints;
|
|
168
|
+
@use '@energysage/es-ds-styles/scss/variables';
|
|
169
|
+
|
|
170
|
+
$animation-duration: v-bind(ANIMATION_DURATION_MS);
|
|
171
|
+
|
|
172
|
+
.es-mobile-nav {
|
|
173
|
+
--mobile-nav-width: v-bind(widthPx);
|
|
174
|
+
|
|
175
|
+
&-overlay {
|
|
176
|
+
opacity: 0;
|
|
177
|
+
pointer-events: none;
|
|
178
|
+
|
|
179
|
+
&.active {
|
|
180
|
+
opacity: 0.3;
|
|
181
|
+
pointer-events: all;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@include breakpoints.media-breakpoint-up(sm) {
|
|
185
|
+
background-color: variables.$gray-900;
|
|
186
|
+
bottom: 0;
|
|
187
|
+
left: 0;
|
|
188
|
+
right: 0;
|
|
189
|
+
top: 0;
|
|
190
|
+
z-index: 1010;
|
|
191
|
+
|
|
192
|
+
@media not (prefers-reduced-motion) {
|
|
193
|
+
transition: opacity $animation-duration ease-in-out;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
:deep(> div) {
|
|
199
|
+
/* ensure the button is full-height with the rest of the nav bar items */
|
|
200
|
+
display: flex;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
:deep(.es-mobile-nav-list) {
|
|
204
|
+
display: flex;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
:deep(.es-mobile-nav-trigger) {
|
|
208
|
+
-webkit-appearance: none;
|
|
209
|
+
appearance: none;
|
|
210
|
+
background: transparent;
|
|
211
|
+
border: none;
|
|
212
|
+
color: variables.$dark-blue;
|
|
213
|
+
min-height: 40px;
|
|
214
|
+
margin: 0;
|
|
215
|
+
padding: 0 variables.$spacer;
|
|
216
|
+
position: relative;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
</style>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
NavigationMenuContent,
|
|
4
|
+
NavigationMenuItem,
|
|
5
|
+
NavigationMenuList,
|
|
6
|
+
NavigationMenuSub,
|
|
7
|
+
NavigationMenuTrigger,
|
|
8
|
+
} from 'reka-ui';
|
|
9
|
+
|
|
10
|
+
interface IProps {
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
const props = defineProps<IProps>();
|
|
14
|
+
|
|
15
|
+
const subNavParentElement = useTemplateRef('subNavParentElement');
|
|
16
|
+
const subNavTrigger = useTemplateRef('subNavTrigger');
|
|
17
|
+
|
|
18
|
+
const activeMenuId = ref('');
|
|
19
|
+
|
|
20
|
+
const backButtonSimulatedFocus = inject('backButtonSimulatedFocus', ref(false));
|
|
21
|
+
const closeButtonSimulatedFocus = inject('closeButtonSimulatedFocus', ref(false));
|
|
22
|
+
const closeMenu: (...args: any[]) => void = inject('closeMenu', () => {});
|
|
23
|
+
const decreaseDepth = inject('decreaseDepth', () => {});
|
|
24
|
+
const goBack = inject('goBack', () => {});
|
|
25
|
+
const increaseDepth: (...args: any[]) => void = inject('increaseDepth', () => {});
|
|
26
|
+
const isElementWithinMenu: (...args: any[]) => boolean = inject('isElementWithinMenu', () => true);
|
|
27
|
+
const registerSubNavCloseHandler: Function | undefined = inject('registerSubNavCloseHandler');
|
|
28
|
+
const waitForAnimationDuration = inject('waitForAnimationDuration', () => Promise.resolve(true));
|
|
29
|
+
const widthPx = inject(
|
|
30
|
+
'widthPx',
|
|
31
|
+
computed(() => '400px'),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const escapeKeyDown = (e: any) => {
|
|
35
|
+
// prevent the Esc key from immediately closing all submenus prior to the mobile nav animating closed
|
|
36
|
+
// (i.e. the visible submenu disappears, then the mobile nav animates closed, which is jarring)
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
|
|
39
|
+
// close the mobile nav in a controlled way
|
|
40
|
+
closeMenu();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleTriggerClick = () => {
|
|
44
|
+
// if the menu was open and is being closed by clicking the trigger
|
|
45
|
+
// (which should only be possible via keyboard), close the menu
|
|
46
|
+
if (activeMenuId.value) {
|
|
47
|
+
decreaseDepth();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let focusOutsideHandled = false;
|
|
52
|
+
|
|
53
|
+
const focusOutside = (e: any) => {
|
|
54
|
+
// some browsers can cycle focus through body when shifting focus, causing
|
|
55
|
+
// this handler to be fired twice. to prevent goBack() from being called
|
|
56
|
+
// more times than we want, we have this guard to prevent focusOutside
|
|
57
|
+
// from being called more than once within the same tick
|
|
58
|
+
if (focusOutsideHandled) return;
|
|
59
|
+
focusOutsideHandled = true;
|
|
60
|
+
nextTick(() => {
|
|
61
|
+
focusOutsideHandled = false;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// we only need to do anything with this submenu if it's actually open
|
|
65
|
+
if (activeMenuId.value) {
|
|
66
|
+
const classesOnTarget = [...e.target.classList];
|
|
67
|
+
|
|
68
|
+
if (classesOnTarget.includes('es-mobile-sub-nav-item-trigger')) {
|
|
69
|
+
// if the user has moved focus outside of the this submenu and onto
|
|
70
|
+
// another submenu trigger, close this submenu
|
|
71
|
+
const triggerSubNav = e.target.closest('.es-mobile-sub-nav');
|
|
72
|
+
if (triggerSubNav !== subNavParentElement?.value?.$el) {
|
|
73
|
+
goBack();
|
|
74
|
+
}
|
|
75
|
+
} else if (!isElementWithinMenu(e.target)) {
|
|
76
|
+
// if focus is on something outside of the overall menu, close it
|
|
77
|
+
closeMenu();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleSrBackClick = () => {
|
|
83
|
+
// explicitly focus the trigger before closing the submenu. without this,
|
|
84
|
+
// assistive technologies (e.g. VoiceOver) lose focus when the submenu content
|
|
85
|
+
// (which contains the sr-only button) is removed from the DOM.
|
|
86
|
+
(subNavTrigger.value as any)?.$el?.focus();
|
|
87
|
+
goBack();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const subNavCloseMenu = async (waitForAnimation: boolean = true) => {
|
|
91
|
+
// start animating up to the parent menu
|
|
92
|
+
decreaseDepth();
|
|
93
|
+
|
|
94
|
+
// wait for animation to complete
|
|
95
|
+
if (waitForAnimation) {
|
|
96
|
+
await waitForAnimationDuration();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// close the menu
|
|
100
|
+
activeMenuId.value = '';
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
watch(activeMenuId, (newVal: string, oldVal: string) => {
|
|
104
|
+
// if the menu is opening
|
|
105
|
+
if (newVal && !oldVal) {
|
|
106
|
+
increaseDepth(props.name);
|
|
107
|
+
|
|
108
|
+
if (registerSubNavCloseHandler) {
|
|
109
|
+
registerSubNavCloseHandler(subNavCloseMenu);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<navigation-menu-item>
|
|
117
|
+
<navigation-menu-sub
|
|
118
|
+
ref="subNavParentElement"
|
|
119
|
+
v-bind="$attrs"
|
|
120
|
+
v-model="activeMenuId"
|
|
121
|
+
class="es-mobile-sub-nav"
|
|
122
|
+
orientation="vertical">
|
|
123
|
+
<navigation-menu-trigger
|
|
124
|
+
ref="subNavTrigger"
|
|
125
|
+
class="es-mobile-sub-nav-item-trigger d-block font-size-75 font-weight-bolder m-0 p-0 text-left w-100"
|
|
126
|
+
@click="handleTriggerClick">
|
|
127
|
+
<span class="d-flex justify-content-between p-100 rounded-sm w-100">
|
|
128
|
+
{{ name }}
|
|
129
|
+
<icon-chevron-right
|
|
130
|
+
height="20px"
|
|
131
|
+
width="20px" />
|
|
132
|
+
</span>
|
|
133
|
+
</navigation-menu-trigger>
|
|
134
|
+
<navigation-menu-content
|
|
135
|
+
v-bind="$attrs"
|
|
136
|
+
class="es-mobile-sub-nav-item-content bg-white"
|
|
137
|
+
@escape-key-down="escapeKeyDown"
|
|
138
|
+
@focus-outside.prevent.stop="focusOutside"
|
|
139
|
+
@interact-outside.prevent.stop
|
|
140
|
+
@pointer-down-outside.prevent.stop>
|
|
141
|
+
<navigation-menu-list class="es-mobile-sub-nav-list list-unstyled m-0 p-0">
|
|
142
|
+
<slot />
|
|
143
|
+
</navigation-menu-list>
|
|
144
|
+
|
|
145
|
+
<!--
|
|
146
|
+
back button for keyboard / screen readers only,
|
|
147
|
+
so you can go back when in this subnav's focus trap.
|
|
148
|
+
when given focus, the visible back button in the
|
|
149
|
+
nav content header will get a visible focus outline.
|
|
150
|
+
-->
|
|
151
|
+
<es-button
|
|
152
|
+
class="sr-only"
|
|
153
|
+
inline
|
|
154
|
+
variant="link"
|
|
155
|
+
@blur="backButtonSimulatedFocus = false"
|
|
156
|
+
@click="handleSrBackClick"
|
|
157
|
+
@focus="backButtonSimulatedFocus = true">
|
|
158
|
+
Back
|
|
159
|
+
</es-button>
|
|
160
|
+
|
|
161
|
+
<!--
|
|
162
|
+
close button for keyboard / screen readers only,
|
|
163
|
+
so you can go back when in this subnav's focus trap.
|
|
164
|
+
when given focus, the visible close button in the
|
|
165
|
+
nav content header will get a visible focus outline.
|
|
166
|
+
-->
|
|
167
|
+
<es-button
|
|
168
|
+
class="sr-only"
|
|
169
|
+
inline
|
|
170
|
+
variant="link"
|
|
171
|
+
@blur="closeButtonSimulatedFocus = false"
|
|
172
|
+
@click="closeMenu"
|
|
173
|
+
@focus="closeButtonSimulatedFocus = true">
|
|
174
|
+
Close
|
|
175
|
+
</es-button>
|
|
176
|
+
</navigation-menu-content>
|
|
177
|
+
</navigation-menu-sub>
|
|
178
|
+
</navigation-menu-item>
|
|
179
|
+
</template>
|
|
180
|
+
|
|
181
|
+
<style lang="scss" scoped>
|
|
182
|
+
@use '@energysage/es-ds-styles/scss/mixins/breakpoints' as breakpoints;
|
|
183
|
+
@use '@energysage/es-ds-styles/scss/variables';
|
|
184
|
+
|
|
185
|
+
$transition-duration: 100ms;
|
|
186
|
+
|
|
187
|
+
.es-mobile-sub-nav {
|
|
188
|
+
:deep(.es-mobile-sub-nav-list) {
|
|
189
|
+
li {
|
|
190
|
+
margin: 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
:deep(.es-mobile-sub-nav-item-trigger) {
|
|
195
|
+
-webkit-appearance: none;
|
|
196
|
+
appearance: none;
|
|
197
|
+
background: transparent;
|
|
198
|
+
border: none;
|
|
199
|
+
border-bottom: 1px solid variables.$gray-100;
|
|
200
|
+
/* specify color here so it can be overridden in active state */
|
|
201
|
+
color: variables.$dark-blue;
|
|
202
|
+
|
|
203
|
+
@media not (prefers-reduced-motion) {
|
|
204
|
+
transition:
|
|
205
|
+
border-color $transition-duration ease-in-out,
|
|
206
|
+
color $transition-duration ease-in-out;
|
|
207
|
+
|
|
208
|
+
& > span {
|
|
209
|
+
transition: background-color $transition-duration ease-in-out;
|
|
210
|
+
|
|
211
|
+
& > svg {
|
|
212
|
+
transition: color $transition-duration ease-in-out;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&[data-state='open'] {
|
|
217
|
+
border-bottom-color: transparent;
|
|
218
|
+
color: variables.$white;
|
|
219
|
+
|
|
220
|
+
& > span {
|
|
221
|
+
background-color: variables.$dark-blue;
|
|
222
|
+
|
|
223
|
+
& > svg {
|
|
224
|
+
color: variables.$white;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
:deep(.es-mobile-sub-nav-item-content[data-state='open']) {
|
|
232
|
+
bottom: 0;
|
|
233
|
+
left: 100vw;
|
|
234
|
+
position: absolute;
|
|
235
|
+
top: 0;
|
|
236
|
+
width: 100%;
|
|
237
|
+
|
|
238
|
+
@include breakpoints.media-breakpoint-up(sm) {
|
|
239
|
+
left: v-bind(widthPx);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
heading: string;
|
|
4
|
+
href: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
}
|
|
8
|
+
withDefaults(defineProps<Props>(), {
|
|
9
|
+
subtitle: '',
|
|
10
|
+
target: '_self',
|
|
11
|
+
});
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<es-card
|
|
16
|
+
class="es-nav-cta-card d-flex p-100"
|
|
17
|
+
:class="{
|
|
18
|
+
'align-items-center': !subtitle,
|
|
19
|
+
}"
|
|
20
|
+
:href="href"
|
|
21
|
+
:target="target"
|
|
22
|
+
variant="interactive">
|
|
23
|
+
<div
|
|
24
|
+
v-if="$slots.icon"
|
|
25
|
+
class="mr-100">
|
|
26
|
+
<div class="es-nav-cta-card-icon bg-blue-50 p-50 position-relative rounded-xs text-blue-600">
|
|
27
|
+
<slot name="icon" />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div>
|
|
31
|
+
<p class="align-items-center d-flex font-weight-bold mb-0 text-blue-600">
|
|
32
|
+
{{ heading }}
|
|
33
|
+
<icon-arrow-right
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
class="es-nav-cta-card-arrow flex-shrink-0 ml-25" />
|
|
36
|
+
</p>
|
|
37
|
+
<p
|
|
38
|
+
v-if="subtitle"
|
|
39
|
+
class="font-size-75 mb-0">
|
|
40
|
+
{{ subtitle }}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</es-card>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<style lang="scss" scoped>
|
|
47
|
+
@use '@energysage/es-ds-styles/scss/variables' as variables;
|
|
48
|
+
@use '@energysage/es-ds-styles/scss/mixins/breakpoints' as breakpoints;
|
|
49
|
+
|
|
50
|
+
.es-nav-cta-card {
|
|
51
|
+
border-width: 1px;
|
|
52
|
+
|
|
53
|
+
/* only apply a hover state if the device supports mouse hover */
|
|
54
|
+
@media (hover) {
|
|
55
|
+
&:hover {
|
|
56
|
+
border-color: variables.$blue-600;
|
|
57
|
+
|
|
58
|
+
.es-nav-cta-card-icon {
|
|
59
|
+
color: variables.$white !important;
|
|
60
|
+
|
|
61
|
+
&::after {
|
|
62
|
+
opacity: 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.es-nav-cta-card-arrow {
|
|
67
|
+
margin-left: 0.75rem !important;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&-icon {
|
|
73
|
+
/* you cannot animate radial gradient backgrounds, so apply it to a pseudo element and fade it in on hover */
|
|
74
|
+
&::after {
|
|
75
|
+
background: radial-gradient(
|
|
76
|
+
156.04% 117.56% at 0% 0%,
|
|
77
|
+
variables.$blue-500 0%,
|
|
78
|
+
#5a4386 53.85%,
|
|
79
|
+
#1c1767 100%
|
|
80
|
+
);
|
|
81
|
+
border-radius: 0.25rem;
|
|
82
|
+
bottom: 0;
|
|
83
|
+
content: '';
|
|
84
|
+
left: 0;
|
|
85
|
+
opacity: 0;
|
|
86
|
+
position: absolute;
|
|
87
|
+
right: 0;
|
|
88
|
+
top: 0;
|
|
89
|
+
z-index: 2;
|
|
90
|
+
|
|
91
|
+
@media not (prefers-reduced-motion) {
|
|
92
|
+
transition: variables.$transition-base;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ensure the icon appears above the gradient pseudo element */
|
|
97
|
+
:deep(svg) {
|
|
98
|
+
position: relative;
|
|
99
|
+
z-index: 3;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media not (prefers-reduced-motion) {
|
|
103
|
+
transition: variables.$transition-base;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&-arrow {
|
|
108
|
+
@media not (prefers-reduced-motion) {
|
|
109
|
+
transition: variables.$transition-base;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -115,7 +115,8 @@ onUnmounted(() => {
|
|
|
115
115
|
<div
|
|
116
116
|
ref="bar"
|
|
117
117
|
class="es-sticky-bar bg-white"
|
|
118
|
-
:class="[`es-sticky-bar--${barState}`, { 'es-sticky-bar--no-transition': suppressTransition }]"
|
|
118
|
+
:class="[`es-sticky-bar--${barState}`, { 'es-sticky-bar--no-transition': suppressTransition }]"
|
|
119
|
+
v-bind="$attrs">
|
|
119
120
|
<slot />
|
|
120
121
|
</div>
|
|
121
122
|
<!-- holds the bar's space in normal flow when the bar is absolutely or fixed positioned -->
|
|
@@ -161,6 +162,7 @@ $shadow: 0 0 6px 0 rgba(34, 38, 51, 0.2);
|
|
|
161
162
|
|
|
162
163
|
// translated off screen above the viewport
|
|
163
164
|
&--fixed-hidden {
|
|
165
|
+
box-shadow: none;
|
|
164
166
|
transform: translateY(-100%);
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -1,51 +1,16 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
/**
|
|
14
|
-
* Height
|
|
15
|
-
*/
|
|
16
|
-
height: {
|
|
17
|
-
type: String,
|
|
18
|
-
default: '24px',
|
|
19
|
-
required: false,
|
|
20
|
-
},
|
|
21
|
-
orientation: {
|
|
22
|
-
type: String,
|
|
23
|
-
default: 'clockwise',
|
|
24
|
-
required: false,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
};
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PhArrowClockwise } from '@phosphor-icons/vue';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
height?: string;
|
|
6
|
+
width?: string;
|
|
7
|
+
}
|
|
8
|
+
withDefaults(defineProps<Props>(), {
|
|
9
|
+
height: '24px',
|
|
10
|
+
width: '24px',
|
|
11
|
+
});
|
|
28
12
|
</script>
|
|
13
|
+
|
|
29
14
|
<template>
|
|
30
|
-
<
|
|
31
|
-
:class="{
|
|
32
|
-
'counter-clockwise': orientation === 'counter-clockwise',
|
|
33
|
-
}"
|
|
34
|
-
:style="{
|
|
35
|
-
height: height,
|
|
36
|
-
width: width,
|
|
37
|
-
}"
|
|
38
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
39
|
-
viewBox="0 0 24 24"
|
|
40
|
-
fill="currentColor">
|
|
41
|
-
<!-- eslint-disable-next-line max-len -->
|
|
42
|
-
<path
|
|
43
|
-
d="M3.246 7.123c.997-1.81 2.591-3.418 4.485-4.222 1.894-.905 4.087-1.106 6.08-.704 2.193.403 3.788 1.408 5.183 3.016.2.201.4.503.598.704l.4-2.513c.099-.603.597-.905 1.195-.905.499.1.897.704.798 1.206l-.897 5.127c0 .301-.2.502-.4.603-.198.1-.398.201-.597.201h-.2l-4.983-.905a1.042 1.042 0 0 1-.798-1.206c.1-.503.598-.905 1.197-.804l2.79.603c-.1-.302-.299-.503-.598-.804-1.096-1.307-2.492-2.111-4.086-2.413-1.595-.402-3.39-.2-4.884.503A7.896 7.896 0 0 0 4.94 8.028c-.797 1.507-1.096 3.216-.897 4.925.2 1.709.897 3.317 2.093 4.524 1.097 1.206 2.692 2.11 4.286 2.311 1.595.302 3.39 0 4.785-.703a8.444 8.444 0 0 0 3.488-3.518c.2-.503.798-.704 1.296-.503.498.201.698.804.498 1.307-.797 2.11-2.292 3.619-4.186 4.624-1.395.703-2.89 1.005-4.485 1.005-.598 0-1.096 0-1.695-.1-2.093-.403-3.987-1.408-5.382-2.916a10.256 10.256 0 0 1-2.691-5.629c-.2-2.211.2-4.322 1.196-6.232Z" />
|
|
44
|
-
</svg>
|
|
15
|
+
<ph-arrow-clockwise :size="width" />
|
|
45
16
|
</template>
|
|
46
|
-
|
|
47
|
-
<style lang="scss" scoped>
|
|
48
|
-
.counter-clockwise {
|
|
49
|
-
transform: scale(-1, 1);
|
|
50
|
-
}
|
|
51
|
-
</style>
|