@energie360/ui-library 0.1.8 → 0.1.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/base/abstracts/_variables.scss +1 -0
- package/components/context-menu/context-menu.scss +28 -0
- package/components/context-menu/u-context-menu.vue +113 -0
- package/components/context-menu-divider/context-menu-divider.scss +6 -0
- package/components/context-menu-divider/u-context-menu-divider.vue +5 -0
- package/components/context-menu-link/context-menu-link.scss +26 -0
- package/components/context-menu-link/u-context-menu-link.vue +27 -0
- package/components/index.js +38 -5
- package/components/navigation-toolbar-link/navigation-toolbar-link.scss +177 -0
- package/components/navigation-toolbar-link/u-navigation-toolbar-link.vue +108 -0
- package/components/text-block/text-block.scss +58 -0
- package/components/text-block/u-text-block.vue +26 -0
- package/components/tooltip/popover.ts +74 -12
- package/dist/base-style.css +1 -0
- package/dist/base-style.css.map +1 -1
- package/dist/elements/text-link.css +1 -0
- package/dist/elements/text-link.css.map +1 -1
- package/dist/layout/split.css +1 -0
- package/dist/layout/split.css.map +1 -1
- package/elements/button-chip/button-chip.scss +2 -2
- package/elements/index.js +13 -0
- package/elements/text-field/u-text-field.vue +9 -1
- package/elements/types.ts +0 -2
- package/i18n/i18n.ts +8 -0
- package/modules/index.js +5 -0
- package/modules/navigation-toolbar-side/navigation-toolbar-side.scss +89 -0
- package/modules/navigation-toolbar-side/u-navigation-toolbar-side.vue +93 -0
- package/modules/navigation-toolbar-top/navigation-toolbar-top.scss +89 -0
- package/modules/navigation-toolbar-top/u-navigation-toolbar-top.vue +130 -0
- package/package.json +4 -7
- package/utils/a11y/focus-trap.js +128 -0
- package/elements/button/index.js +0 -1
- package/elements/button-chip/index.js +0 -1
- package/elements/icon/index.js +0 -1
- package/elements/icon-button/index.js +0 -1
- package/elements/image/index.js +0 -1
- package/elements/loader/index.js +0 -1
- package/elements/numeric-stepper/index.js +0 -1
- package/elements/password-progress/index.js +0 -1
- package/elements/radio/index.js +0 -1
- package/elements/radio-group/index.js +0 -1
- package/elements/select/index.js +0 -1
- package/elements/select-chip/index.js +0 -1
- package/elements/select-chips/index.js +0 -1
- package/elements/spectro/index.js +0 -1
- package/elements/text-field/index.js +0 -1
- package/elements/textarea/index.js +0 -1
- package/elements/toggle-switch/index.js +0 -1
|
@@ -35,6 +35,7 @@ $grid-gutter-s: math.div(dt.$space-5, $container-inner-s) * 100%;
|
|
|
35
35
|
|
|
36
36
|
// Transition
|
|
37
37
|
$trs-default: var(--e-trs-duration-faster) var(--e-trs-easing-default);
|
|
38
|
+
$trs-ease-out: var(--e-trs-duration-faster) cubic-bezier(0.215, 0.61, 0.355, 1); /* easeOutCubic */
|
|
38
39
|
|
|
39
40
|
// CMS-Section
|
|
40
41
|
$section-default-padding-2xl: dt.$space-28;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@use '../../base/abstracts/' as a;
|
|
2
|
+
|
|
3
|
+
// .context-menu {
|
|
4
|
+
// // position: relative;
|
|
5
|
+
// }
|
|
6
|
+
|
|
7
|
+
.context-menu__menu {
|
|
8
|
+
position: absolute;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
row-gap: var(--e-space-2);
|
|
12
|
+
padding: var(--e-space-2);
|
|
13
|
+
border-radius: var(--e-brd-radius-2);
|
|
14
|
+
border: 1px solid var(--e-c-mono-200);
|
|
15
|
+
box-shadow: var(--e-elevation-md);
|
|
16
|
+
background-color: var(--e-c-mono-00);
|
|
17
|
+
min-width: a.rem(240);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.menu-enter-active,
|
|
21
|
+
.menu-leave-active {
|
|
22
|
+
transition: opacity a.$trs-default;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.menu-enter-from,
|
|
26
|
+
.menu-leave-to {
|
|
27
|
+
opacity: 0;
|
|
28
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, useTemplateRef, watch } from 'vue'
|
|
3
|
+
import { PopoverPositionParams, getPopoverPosition } from '../tooltip/popover'
|
|
4
|
+
import { debounceRaf } from '../../utils/functions/debounce'
|
|
5
|
+
import { focusTrap } from '../../utils/a11y/focus-trap'
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
placement = 'top-center',
|
|
9
|
+
offset = 8,
|
|
10
|
+
viewportPadding = 20,
|
|
11
|
+
} = defineProps<PopoverPositionParams>()
|
|
12
|
+
|
|
13
|
+
// TODO: Key codes should be defined globally
|
|
14
|
+
const ESC = 'Escape'
|
|
15
|
+
|
|
16
|
+
const triggerEl = useTemplateRef('trigger')
|
|
17
|
+
const menuEl = useTemplateRef('menu')
|
|
18
|
+
const isOpen = ref(false)
|
|
19
|
+
let focusTrapInstance
|
|
20
|
+
|
|
21
|
+
const onToggleMenu = (e: Event) => {
|
|
22
|
+
e.stopPropagation()
|
|
23
|
+
e.preventDefault()
|
|
24
|
+
|
|
25
|
+
isOpen.value = !isOpen.value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const onResize = () => {
|
|
29
|
+
// Clear right position value, so we can get actual width of popover.
|
|
30
|
+
menuEl.value!.style.right = ''
|
|
31
|
+
|
|
32
|
+
const popoverPosition = getPopoverPosition(
|
|
33
|
+
triggerEl.value as HTMLElement,
|
|
34
|
+
menuEl.value as HTMLElement,
|
|
35
|
+
{
|
|
36
|
+
placement,
|
|
37
|
+
offset,
|
|
38
|
+
viewportPadding,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
menuEl.value!.style.top = `${popoverPosition.top}px`
|
|
43
|
+
menuEl.value!.style.left = `${popoverPosition.left}px`
|
|
44
|
+
if (popoverPosition.right) {
|
|
45
|
+
menuEl.value!.style.right = `${popoverPosition.right}px`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const onResizeDebounced = debounceRaf(onResize)
|
|
50
|
+
|
|
51
|
+
const onEnter = () => {
|
|
52
|
+
// 1. Position context-menu
|
|
53
|
+
onResize()
|
|
54
|
+
|
|
55
|
+
// 2. Set focus to first focusable element in menu
|
|
56
|
+
focusTrapInstance = focusTrap(menuEl.value, {
|
|
57
|
+
focusFirstElement: true,
|
|
58
|
+
allowArrowUpDown: true,
|
|
59
|
+
allowArrowLeftRight: true,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// 3. Bind events
|
|
63
|
+
menuEl.value.addEventListener('keydown', onEsc)
|
|
64
|
+
document.addEventListener('click', onDocumentClick)
|
|
65
|
+
window.addEventListener('resize', onResizeDebounced)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const onAfterLeave = () => {
|
|
69
|
+
focusTrapInstance.release()
|
|
70
|
+
window.removeEventListener('resize', onResizeDebounced)
|
|
71
|
+
document.removeEventListener('click', onDocumentClick)
|
|
72
|
+
menuEl.value?.removeEventListener('keydown', onEsc)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const onEsc = (e) => {
|
|
76
|
+
if (e.code === ESC) {
|
|
77
|
+
isOpen.value = false
|
|
78
|
+
|
|
79
|
+
// Set focus back to the trigger element
|
|
80
|
+
triggerEl.value.focus()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const onDocumentClick = (e: Event) => {
|
|
85
|
+
e.stopPropagation()
|
|
86
|
+
e.preventDefault()
|
|
87
|
+
|
|
88
|
+
// Close context menu when any click happens.
|
|
89
|
+
isOpen.value = false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
watch([() => placement, () => offset, () => viewportPadding], () => {
|
|
93
|
+
if (isOpen.value) {
|
|
94
|
+
onResize()
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<template>
|
|
100
|
+
<div class="context-menu" :class="{ 'is-open': isOpen, 'is-closed': !isOpen }">
|
|
101
|
+
<div ref="trigger" class="context-menu__trigger" @click="onToggleMenu">
|
|
102
|
+
<slot name="trigger"></slot>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<Transition name="menu" @enter="onEnter" @after-leave="onAfterLeave">
|
|
106
|
+
<div v-if="isOpen" ref="menu" class="context-menu__menu">
|
|
107
|
+
<slot></slot>
|
|
108
|
+
</div>
|
|
109
|
+
</Transition>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<style lang="scss" scoped src="./context-menu.scss"></style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@use '../../base/abstracts/' as a;
|
|
2
|
+
|
|
3
|
+
.context-menu-link {
|
|
4
|
+
@include a.type(200, strong);
|
|
5
|
+
|
|
6
|
+
display: flex;
|
|
7
|
+
column-gap: var(--e-space-2);
|
|
8
|
+
padding: var(--e-space-1_5) var(--e-space-3);
|
|
9
|
+
color: var(--e-c-mono-700);
|
|
10
|
+
border-radius: var(--e-brd-radius-2);
|
|
11
|
+
text-wrap: nowrap;
|
|
12
|
+
transition:
|
|
13
|
+
background-color a.$trs-ease-out,
|
|
14
|
+
color a.$trs-ease-out;
|
|
15
|
+
|
|
16
|
+
&:hover,
|
|
17
|
+
&:active {
|
|
18
|
+
background-color: var(--e-c-primary-01-50);
|
|
19
|
+
color: var(--e-c-primary-01-700);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.active {
|
|
23
|
+
background-color: var(--e-c-primary-01-100);
|
|
24
|
+
color: var(--e-c-primary-01-900);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { UIcon } from '../../elements'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
icon: string
|
|
7
|
+
label?: string
|
|
8
|
+
href?: string
|
|
9
|
+
target?: string
|
|
10
|
+
active?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { href, active = false } = defineProps<Props>()
|
|
14
|
+
|
|
15
|
+
const tag = computed(() => {
|
|
16
|
+
return href ? 'a' : 'span'
|
|
17
|
+
})
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<component :is="tag" class="context-menu-link" :class="{ active }">
|
|
22
|
+
<UIcon :name="icon" />
|
|
23
|
+
<slot>{{ label }}</slot>
|
|
24
|
+
</component>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<style scoped lang="scss" src="./context-menu-link.scss"></style>
|
package/components/index.js
CHANGED
|
@@ -2,13 +2,46 @@
|
|
|
2
2
|
* It's not recommended to import components from this file, because tree-shaking won't work then.
|
|
3
3
|
* -> https://vite.dev/guide/performance#avoid-barrel-files
|
|
4
4
|
*/
|
|
5
|
+
export { default as UAccordionItem } from './accordion-item/u-accordion-item.vue'
|
|
6
|
+
|
|
7
|
+
// Cards
|
|
8
|
+
export { default as UCard } from './card/u-card.vue'
|
|
9
|
+
export { default as UCardFooter } from './card-footer/u-card-footer.vue'
|
|
10
|
+
export { default as UCardGroup } from './card-group/u-card-group.vue'
|
|
11
|
+
export { default as UCardHeader } from './card-header/u-card-header.vue'
|
|
12
|
+
export { default as UCardHint } from './card-hint/u-card-hint.vue'
|
|
13
|
+
export { default as UCardPrice } from './card-price/u-card-price.vue'
|
|
14
|
+
export { default as UCardSection } from './card-section/u-card-section.vue'
|
|
15
|
+
export { default as UCardTable } from './card-table/u-card-table.vue'
|
|
16
|
+
export { default as UCardToggleSwitches } from './card-toggle-switches/u-card-toggle-switches.vue'
|
|
17
|
+
|
|
18
|
+
// Collapsible
|
|
19
|
+
export { default as UCollapsible } from './collapsible/u-collapsible.vue'
|
|
20
|
+
export { default as UCollapsibleGroup } from './collapsible-group/u-collapsible-group.vue'
|
|
21
|
+
|
|
5
22
|
export { default as UIconTextBlock } from './icon-text-block/u-icon-text-block.vue'
|
|
6
23
|
export { default as UIconTextBlockGroup } from './icon-text-block-group/u-icon-text-block-group.vue'
|
|
7
|
-
export { default as
|
|
8
|
-
export { default as
|
|
9
|
-
export { default as
|
|
24
|
+
export { default as UInlineEdit } from './inline-edit/u-inline-edit.vue'
|
|
25
|
+
export { default as ULanguageNav } from './language-nav/u-language-nav.vue'
|
|
26
|
+
export { default as UPanel } from './panel/u-panel.vue'
|
|
27
|
+
export { default as UProgressBar } from './progress-bar/u-progress-bar.vue'
|
|
28
|
+
export { default as URichtext } from './richtext/u-richtext.vue'
|
|
29
|
+
|
|
30
|
+
// Table
|
|
31
|
+
export { default as UCellCtas } from './table/u-cell-ctas.vue'
|
|
32
|
+
export { default as UCellIconGroup } from './table/u-cell-icon-group.vue'
|
|
33
|
+
export { default as UCellIconText } from './table/u-cell-icon-text.vue'
|
|
34
|
+
export { default as UCellProgressBar } from './table/u-cell-progress-bar.vue'
|
|
10
35
|
export { default as UTableCell } from './table/u-table-cell.vue'
|
|
36
|
+
export { default as UTableHeader } from './table/u-table-header.vue'
|
|
11
37
|
export { default as UTableHeading } from './table/u-table-heading.vue'
|
|
12
|
-
export { default as
|
|
13
|
-
export { default as
|
|
38
|
+
export { default as UTableRow } from './table/u-table-row.vue'
|
|
39
|
+
export { default as UTable } from './table/u-table.vue'
|
|
40
|
+
|
|
41
|
+
export { default as UTabs } from './tabs/u-tabs.vue'
|
|
42
|
+
export { default as UTextBlock } from './text-block/u-text-block.vue'
|
|
14
43
|
export { default as UTooltip } from './tooltip/u-tooltip.vue'
|
|
44
|
+
export { default as UNavigationToolbarLink } from './navigation-toolbar-link/u-navigation-toolbar-link.vue'
|
|
45
|
+
export { default as UContextMenu } from './context-menu/u-context-menu.vue'
|
|
46
|
+
export { default as UContextMenuLink } from './context-menu-link/u-context-menu-link.vue'
|
|
47
|
+
export { default as UContextMenuDivider } from './context-menu-divider/u-context-menu-divider.vue'
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// TODO: How to handle text overflow?
|
|
2
|
+
|
|
3
|
+
@use '../../base/abstracts/' as a;
|
|
4
|
+
|
|
5
|
+
@keyframes fadeInLabel {
|
|
6
|
+
0% {
|
|
7
|
+
opacity: 0;
|
|
8
|
+
transform: translateX(calc(100% + #{a.rem(8)}));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
100% {
|
|
12
|
+
opacity: 1;
|
|
13
|
+
transform: translateX(100%);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes fadeOutLabel {
|
|
18
|
+
0% {
|
|
19
|
+
opacity: 1;
|
|
20
|
+
transform: translateX(100%);
|
|
21
|
+
width: auto;
|
|
22
|
+
height: auto;
|
|
23
|
+
padding: var(--e-space-1_5) var(--e-space-2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
100% {
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transform: translateX(calc(100% + #{a.rem(8)}));
|
|
29
|
+
width: auto;
|
|
30
|
+
height: auto;
|
|
31
|
+
padding: var(--e-space-1_5) var(--e-space-2);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes collapse {
|
|
36
|
+
0% {
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
padding: var(--e-space-1_5) var(--e-space-3);
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
100% {
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
padding: var(--e-space-1_5);
|
|
45
|
+
width: a.rem(36);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes expand {
|
|
50
|
+
0% {
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
padding: var(--e-space-1_5);
|
|
53
|
+
width: a.rem(36);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
100% {
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
padding: var(--e-space-1_5) var(--e-space-3);
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.navigation-toolbar-link {
|
|
64
|
+
--transition-ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); /* easeOutCubic */
|
|
65
|
+
|
|
66
|
+
position: relative;
|
|
67
|
+
padding: var(--e-space-1_5) var(--e-space-3);
|
|
68
|
+
height: a.rem(36);
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: flex-start;
|
|
71
|
+
align-items: center;
|
|
72
|
+
flex-wrap: nowrap;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
width: 100%;
|
|
75
|
+
column-gap: var(--e-space-2);
|
|
76
|
+
background-color: var(--e-c-secondary-01-1000);
|
|
77
|
+
color: var(--e-c-mono-00);
|
|
78
|
+
border-radius: var(--e-brd-radius-2);
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
transition:
|
|
81
|
+
background-color a.$trs-default,
|
|
82
|
+
color a.$trs-default;
|
|
83
|
+
|
|
84
|
+
@include a.type(200, strong);
|
|
85
|
+
|
|
86
|
+
> * {
|
|
87
|
+
flex: 0 0 auto;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&:active,
|
|
91
|
+
&:hover:not(.is-collapsing, .is-expanding) {
|
|
92
|
+
background-color: var(--e-c-secondary-01-950);
|
|
93
|
+
color: var(--e-c-primary-01-100);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Active Page
|
|
97
|
+
&.active {
|
|
98
|
+
background-color: var(--e-c-secondary-01-900);
|
|
99
|
+
color: var(--e-c-primary-01-50);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Collapsed
|
|
103
|
+
&.collapsed {
|
|
104
|
+
padding: var(--e-space-1_5);
|
|
105
|
+
width: a.rem(36);
|
|
106
|
+
|
|
107
|
+
.navigation-toolbar-link__label {
|
|
108
|
+
position: absolute;
|
|
109
|
+
top: 0;
|
|
110
|
+
right: calc(var(--e-space-2) * -1);
|
|
111
|
+
transform: translateX(calc(100% + #{a.rem(8)}));
|
|
112
|
+
border-radius: var(--e-brd-radius-2);
|
|
113
|
+
background-color: var(--e-c-mono-900);
|
|
114
|
+
color: var(--e-c-mono-00);
|
|
115
|
+
|
|
116
|
+
// Hide visually
|
|
117
|
+
width: 0;
|
|
118
|
+
height: 0;
|
|
119
|
+
opacity: 0;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&.hover-out:not(.is-expanding, .is-collapsing, .label-hidden) {
|
|
125
|
+
.navigation-toolbar-link__label {
|
|
126
|
+
// TODO: Maybe just use transitions instead of keyframe animations
|
|
127
|
+
animation-name: fadeOutLabel;
|
|
128
|
+
animation-duration: var(--e-trs-duration-faster);
|
|
129
|
+
animation-timing-function: ease-out;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&:hover:not(.is-expanding, .is-collapsing, .label-hidden) {
|
|
134
|
+
.navigation-toolbar-link__label {
|
|
135
|
+
animation-name: fadeInLabel;
|
|
136
|
+
animation-duration: var(--e-trs-duration-faster);
|
|
137
|
+
animation-timing-function: ease-out;
|
|
138
|
+
|
|
139
|
+
padding: var(--e-space-1_5) var(--e-space-2);
|
|
140
|
+
transform: translateX(100%);
|
|
141
|
+
width: auto;
|
|
142
|
+
height: auto;
|
|
143
|
+
opacity: 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Animation
|
|
150
|
+
.navigation-toolbar-link {
|
|
151
|
+
&.is-collapsing,
|
|
152
|
+
&.is-expanding {
|
|
153
|
+
animation-duration: var(--e-trs-duration-faster);
|
|
154
|
+
|
|
155
|
+
.navigation-toolbar-link__label {
|
|
156
|
+
animation: none;
|
|
157
|
+
position: static;
|
|
158
|
+
width: auto;
|
|
159
|
+
height: auto;
|
|
160
|
+
opacity: 1;
|
|
161
|
+
background-color: transparent;
|
|
162
|
+
transform: none;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
&.is-collapsing {
|
|
167
|
+
// TODO: Maybe just use transitions instead of keyframe animations
|
|
168
|
+
animation-name: collapse;
|
|
169
|
+
animation-timing-function: var(--transition-ease-out);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
&.is-expanding {
|
|
173
|
+
// TODO: Maybe just use transitions instead of keyframe animations
|
|
174
|
+
animation-name: expand;
|
|
175
|
+
animation-timing-function: var(--transition-ease-out);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, computed, useTemplateRef } from 'vue'
|
|
3
|
+
import { UIcon } from '../../elements'
|
|
4
|
+
|
|
5
|
+
// TODO: Label animation when collapsed is a mess. Refactor it as soon as possbile!
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
label?: string
|
|
9
|
+
icon: string
|
|
10
|
+
href?: string
|
|
11
|
+
target?: string
|
|
12
|
+
active?: boolean
|
|
13
|
+
collapsed?: boolean
|
|
14
|
+
labelHidden?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
active = false,
|
|
19
|
+
collapsed = false,
|
|
20
|
+
labelHidden = false,
|
|
21
|
+
href = '',
|
|
22
|
+
target = '_self',
|
|
23
|
+
} = defineProps<Props>()
|
|
24
|
+
|
|
25
|
+
const isCollapsed = ref(collapsed)
|
|
26
|
+
const isCollapsing = ref(false)
|
|
27
|
+
const isExpanding = ref(false)
|
|
28
|
+
const isHovering = ref(false)
|
|
29
|
+
const isHoverout = ref(false)
|
|
30
|
+
const labelEl = useTemplateRef('label')
|
|
31
|
+
|
|
32
|
+
const onWrapperAnimationEnd = (e: Event) => {
|
|
33
|
+
if (e.target === labelEl.value) {
|
|
34
|
+
isHoverout.value = false
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isCollapsing.value = false
|
|
39
|
+
isExpanding.value = false
|
|
40
|
+
isHoverout.value = false
|
|
41
|
+
|
|
42
|
+
isCollapsed.value = !isCollapsed.value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const onHover = () => {
|
|
46
|
+
isHovering.value = true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const onHoverOut = () => {
|
|
50
|
+
isHovering.value = false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
watch(
|
|
54
|
+
() => collapsed,
|
|
55
|
+
(newV) => {
|
|
56
|
+
if (newV) {
|
|
57
|
+
// Start collapsing animation
|
|
58
|
+
isCollapsing.value = true
|
|
59
|
+
} else {
|
|
60
|
+
// Start expanding animation
|
|
61
|
+
isExpanding.value = true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
watch(isHovering, (newV) => {
|
|
67
|
+
if (!newV) {
|
|
68
|
+
isHoverout.value = true
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const tag = computed(() => {
|
|
73
|
+
if (href) {
|
|
74
|
+
return 'a'
|
|
75
|
+
} else {
|
|
76
|
+
return 'span'
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<component
|
|
83
|
+
:is="tag"
|
|
84
|
+
:href="tag === 'a' ? href : null"
|
|
85
|
+
:target="tag === 'a' ? target : null"
|
|
86
|
+
:class="[
|
|
87
|
+
'navigation-toolbar-link',
|
|
88
|
+
{
|
|
89
|
+
active,
|
|
90
|
+
collapsed: isCollapsed,
|
|
91
|
+
'hover-out': isHoverout,
|
|
92
|
+
'is-collapsing': isCollapsing,
|
|
93
|
+
'is-expanding': isExpanding,
|
|
94
|
+
'label-hidden': labelHidden,
|
|
95
|
+
},
|
|
96
|
+
]"
|
|
97
|
+
@animationend="onWrapperAnimationEnd"
|
|
98
|
+
@mouseenter="onHover"
|
|
99
|
+
@mouseleave="onHoverOut"
|
|
100
|
+
>
|
|
101
|
+
<UIcon :name="icon" />
|
|
102
|
+
<span ref="label" class="navigation-toolbar-link__label">
|
|
103
|
+
<slot>{{ label }}</slot>
|
|
104
|
+
</span>
|
|
105
|
+
</component>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<style lang="scss" src="./navigation-toolbar-link.scss" scoped></style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
@use '../../base/abstracts/' as a;
|
|
2
|
+
|
|
3
|
+
.wizard-text-block {
|
|
4
|
+
.wizard-text-block__title {
|
|
5
|
+
@include a.type(800, strong);
|
|
6
|
+
|
|
7
|
+
+ .wizard-text-block__text {
|
|
8
|
+
margin-top: var(--e-space-4);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.wizard-text-block__title--small {
|
|
13
|
+
@include a.type(500, strong);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.wizard-text-block__text {
|
|
17
|
+
@include a.type(300);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@include a.bp(lg) {
|
|
21
|
+
.wizard-text-block__title {
|
|
22
|
+
@include a.type(700, strong);
|
|
23
|
+
|
|
24
|
+
+ .wizard-text-block__text {
|
|
25
|
+
margin-top: var(--e-space-3);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Modifiers
|
|
31
|
+
&.wizard-text-block--sub {
|
|
32
|
+
.wizard-text-block__title {
|
|
33
|
+
@include a.type(300, strong);
|
|
34
|
+
|
|
35
|
+
+ .wizard-text-block__text {
|
|
36
|
+
margin-top: var(--e-space-2);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.wizard-text-block__text {
|
|
41
|
+
@include a.type(300);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@include a.bp(lg) {
|
|
45
|
+
.wizard-text-block__title {
|
|
46
|
+
@include a.type(200, strong);
|
|
47
|
+
|
|
48
|
+
+ .wizard-text-block__text {
|
|
49
|
+
margin-top: var(--e-space-2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.wizard-text-block__text {
|
|
54
|
+
@include a.type(200);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant: 'lead' | 'sub'
|
|
4
|
+
title?: string
|
|
5
|
+
text?: string
|
|
6
|
+
centered?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { centered = false } = defineProps<Props>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div :class="['wizard-text-block', `wizard-text-block--${variant}`, { centered }]">
|
|
14
|
+
<h2 v-if="title" class="wizard-text-block__title" v-html="title" />
|
|
15
|
+
|
|
16
|
+
<div v-if="text" class="wizard-text-block__text richtext" v-html="text" />
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<style scoped lang="scss">
|
|
21
|
+
@use './text-block.scss';
|
|
22
|
+
|
|
23
|
+
.centered {
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
</style>
|