@energie360/ui-library 0.1.9 → 0.1.11

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.
Files changed (38) hide show
  1. package/base/abstracts/_variables.scss +1 -0
  2. package/components/card/u-card.vue +10 -3
  3. package/components/card-cta-header/u-card-cta-header.vue +87 -0
  4. package/components/circular-progress/circular-progress.scss +34 -0
  5. package/components/circular-progress/u-circular-progress.vue +25 -0
  6. package/components/context-menu/context-menu.scss +28 -0
  7. package/components/context-menu/u-context-menu.vue +113 -0
  8. package/components/context-menu-divider/context-menu-divider.scss +6 -0
  9. package/components/context-menu-divider/u-context-menu-divider.vue +5 -0
  10. package/components/context-menu-link/context-menu-link.scss +26 -0
  11. package/components/context-menu-link/u-context-menu-link.vue +27 -0
  12. package/components/index.js +8 -0
  13. package/components/navigation-toolbar-link/navigation-toolbar-link.scss +177 -0
  14. package/components/navigation-toolbar-link/u-navigation-toolbar-link.vue +108 -0
  15. package/components/progress-avatar/progress-avatar.scss +27 -0
  16. package/components/progress-avatar/u-progress-avatar.vue +25 -0
  17. package/components/text-block/text-block.scss +58 -0
  18. package/components/text-block/u-text-block.vue +26 -0
  19. package/components/tooltip/popover.ts +74 -12
  20. package/dist/base-style.css +1 -0
  21. package/dist/base-style.css.map +1 -1
  22. package/dist/elements/text-link.css +1 -0
  23. package/dist/elements/text-link.css.map +1 -1
  24. package/dist/layout/split.css +1 -0
  25. package/dist/layout/split.css.map +1 -1
  26. package/elements/button/u-button.vue +12 -5
  27. package/elements/button-chip/button-chip.scss +2 -2
  28. package/elements/form/form.scss +174 -0
  29. package/elements/text-field/u-text-field.vue +9 -1
  30. package/i18n/i18n.ts +8 -0
  31. package/layout/form-grid/form-grid.scss +42 -0
  32. package/modules/index.js +2 -0
  33. package/modules/navigation-toolbar-side/navigation-toolbar-side.scss +89 -0
  34. package/modules/navigation-toolbar-side/u-navigation-toolbar-side.vue +93 -0
  35. package/modules/navigation-toolbar-top/navigation-toolbar-top.scss +89 -0
  36. package/modules/navigation-toolbar-top/u-navigation-toolbar-top.vue +130 -0
  37. package/package.json +4 -2
  38. package/utils/a11y/focus-trap.js +128 -0
@@ -0,0 +1,89 @@
1
+ @use '../../base/abstracts/' as a;
2
+
3
+ .navigation-toolbar-side {
4
+ --nav-width-expanded: #{a.rem(280)};
5
+ --transition-ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); /* easeOutCubic */
6
+
7
+ display: flex;
8
+ flex-direction: column;
9
+ padding: var(--e-space-6);
10
+ width: var(--nav-width-expanded);
11
+ height: 100%;
12
+ background-color: var(--e-c-secondary-01-1000);
13
+
14
+ &.collapsed {
15
+ width: a.rem(88);
16
+
17
+ .navigation-toolbar-side__logo {
18
+ width: a.rem(40);
19
+ }
20
+
21
+ .navigation-toolbar-side__nav-links,
22
+ .navigation-toolbar-side__menu-cta {
23
+ padding: 0 var(--e-space-0_5);
24
+ }
25
+ }
26
+ }
27
+
28
+ .navigation-toolbar-side__logo {
29
+ width: a.rem(196);
30
+ height: a.rem(40);
31
+ margin-bottom: var(--e-space-12);
32
+ }
33
+
34
+ .navigation-toolbar-side__nav-links {
35
+ display: flex;
36
+ flex-direction: column;
37
+ row-gap: var(--e-space-2);
38
+ }
39
+
40
+ .navigation-toolbar-side__menu-ctas {
41
+ display: flex;
42
+ flex-direction: column;
43
+ row-gap: var(--e-space-2);
44
+ margin-top: auto;
45
+ }
46
+
47
+ .navigation-toolbar-side__menu-cta {
48
+ display: block;
49
+ width: 100%;
50
+ }
51
+
52
+ .navigation-toolbar-side__top-bar-logo {
53
+ img {
54
+ height: a.rem(24);
55
+ }
56
+ }
57
+
58
+ .navigation-toolbar-side__top-bar-ctas {
59
+ display: flex;
60
+ column-gap: var(--e-space-4);
61
+ }
62
+
63
+ // Animation
64
+ .navigation-toolbar-side {
65
+ &.is-collapsing {
66
+ will-change: width;
67
+ transition: width var(--e-trs-duration-faster) var(--transition-ease-out);
68
+ width: a.rem(88);
69
+ overflow: hidden;
70
+
71
+ .navigation-toolbar-side__nav-links,
72
+ .navigation-toolbar-side__menu-ctas {
73
+ transition: padding var(--e-trs-duration-faster) var(--transition-ease-out);
74
+ padding: 0 var(--e-space-0_5);
75
+ }
76
+ }
77
+
78
+ &.is-expanding {
79
+ will-change: width;
80
+ transition: all var(--e-trs-duration-faster) var(--transition-ease-out);
81
+ width: var(--nav-width-expanded);
82
+
83
+ .navigation-toolbar-side__nav-links,
84
+ .navigation-toolbar-side__menu-ctas {
85
+ transition: padding var(--e-trs-duration-faster) var(--transition-ease-out);
86
+ padding: 0;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ import { UNavigationToolbarLink } from '../../components'
3
+ import { UContextMenu } from '../../components'
4
+ import { Image } from '../../elements/types'
5
+ import { ref, watch, useTemplateRef } from 'vue'
6
+
7
+ interface MenuButton {
8
+ icon: string
9
+ label: string
10
+ }
11
+
12
+ interface Props {
13
+ logoLink: {
14
+ href: string
15
+ target: string
16
+ }
17
+ menuButton: MenuButton
18
+ collapsed?: boolean
19
+ logoImage: Image
20
+ logoMinifiedImage: Image
21
+ }
22
+
23
+ const { collapsed = false } = defineProps<Props>()
24
+
25
+ const el = useTemplateRef('el')
26
+ const isCollapsed = ref(collapsed)
27
+ const isCollapsing = ref(false)
28
+ const isExpanding = ref(false)
29
+
30
+ const onTransitionEnd = () => {
31
+ el.value?.removeEventListener('transitionend', onTransitionEnd)
32
+ isCollapsed.value = !isCollapsed.value
33
+
34
+ isCollapsing.value = false
35
+ isExpanding.value = false
36
+ }
37
+
38
+ watch(
39
+ () => collapsed,
40
+ (newV) => {
41
+ el.value?.addEventListener('transitionend', onTransitionEnd)
42
+
43
+ if (newV) {
44
+ isCollapsing.value = true
45
+ } else {
46
+ isExpanding.value = true
47
+ }
48
+ },
49
+ )
50
+ </script>
51
+
52
+ <template>
53
+ <div
54
+ ref="el"
55
+ :class="[
56
+ 'navigation-toolbar-side',
57
+ {
58
+ collapsed: isCollapsed,
59
+ 'is-collapsing': isCollapsing,
60
+ 'is-expanding': isExpanding,
61
+ },
62
+ ]"
63
+ >
64
+ <a class="navigation-toolbar-side__logo" :href="logoLink.href" :target="logoLink.target">
65
+ <img
66
+ v-if="isCollapsed || isExpanding"
67
+ :src="logoMinifiedImage.src"
68
+ :alt="logoMinifiedImage.alt"
69
+ />
70
+ <img v-else :src="logoImage.src" :alt="logoImage.alt" />
71
+ </a>
72
+
73
+ <nav ref="mobile-panel" class="navigation-toolbar-side__nav-panel">
74
+ <div class="navigation-toolbar-side__nav-links">
75
+ <slot name="navLinks"></slot>
76
+ </div>
77
+ </nav>
78
+
79
+ <div class="navigation-toolbar-side__menu-ctas">
80
+ <UContextMenu placement="right-bottom">
81
+ <template #trigger>
82
+ <button class="navigation-toolbar-side__menu-cta" type="button">
83
+ <UNavigationToolbarLink v-bind="menuButton" :collapsed />
84
+ </button>
85
+ </template>
86
+
87
+ <slot name="contextMenuLinks"></slot>
88
+ </UContextMenu>
89
+ </div>
90
+ </div>
91
+ </template>
92
+
93
+ <style lang="scss" src="./navigation-toolbar-side.scss" scoped></style>
@@ -0,0 +1,89 @@
1
+ @use '../../base/abstracts/' as a;
2
+
3
+ .navigation-toolbar-top {
4
+ --transition-ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); /* easeOutCubic */
5
+
6
+ display: block;
7
+ width: 100%;
8
+ padding: 0 var(--e-space-5);
9
+ height: a.rem(56);
10
+ background-color: var(--e-c-secondary-01-1000);
11
+ }
12
+
13
+ .navigation-toolbar-top__top-bar {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ height: 100%;
18
+ }
19
+
20
+ .navigation-toolbar-top__nav-panel-logo {
21
+ display: block;
22
+ height: a.rem(48);
23
+ padding-bottom: a.rem(24);
24
+ margin-bottom: var(--e-space-14);
25
+
26
+ img {
27
+ height: 100%;
28
+ }
29
+ }
30
+
31
+ .navigation-toolbar-top__nav-panel-close {
32
+ display: block;
33
+ position: absolute;
34
+ top: var(--e-space-4);
35
+ right: var(--e-space-5);
36
+ z-index: 1;
37
+ }
38
+
39
+ .navigation-toolbar-top__nav-panel {
40
+ position: fixed;
41
+ padding: var(--e-space-4) var(--e-space-5);
42
+ top: 0;
43
+ left: 0;
44
+ background-color: var(--e-c-secondary-01-1000);
45
+ width: 100%;
46
+ height: 100%;
47
+ transform: translate3d(100%, 0, 0);
48
+
49
+ .navigation-toolbar-mobile-open & {
50
+ transform: translate3d(0, 0, 0);
51
+ }
52
+
53
+ // Animation
54
+ &.is-opening {
55
+ transition: transform var(--e-trs-duration-faster) var(--transition-ease-out);
56
+ transform: translate3d(0, 0, 0);
57
+ }
58
+
59
+ &.is-closing {
60
+ transition: transform var(--e-trs-duration-faster) var(--transition-ease-out);
61
+ transform: translate3d(100%, 0, 0);
62
+ }
63
+ }
64
+
65
+ .navigation-toolbar-top__nav-links {
66
+ row-gap: var(--e-space-4);
67
+ }
68
+
69
+ .navigation-toolbar-top__nav-links {
70
+ display: flex;
71
+ flex-direction: column;
72
+ row-gap: var(--e-space-2);
73
+ }
74
+
75
+ .navigation-toolbar-top__top-bar-logo {
76
+ img {
77
+ height: a.rem(24);
78
+ }
79
+ }
80
+
81
+ .navigation-toolbar-top__top-bar-ctas {
82
+ display: flex;
83
+ column-gap: var(--e-space-4);
84
+ }
85
+
86
+ // Global
87
+ .navigation-toolbar-mobile-open {
88
+ overflow: hidden;
89
+ }
@@ -0,0 +1,130 @@
1
+ <script setup lang="ts">
2
+ import { UNavigationToolbarLink } from '../../components'
3
+ import { UIconButton } from '../../elements'
4
+ import { UContextMenu } from '../../components'
5
+ import { Image } from '../../elements/types'
6
+ import { getTranslation } from '../../utils/translations/translate'
7
+ import { ref, watch, useTemplateRef } from 'vue'
8
+
9
+ interface MenuButton {
10
+ icon: string
11
+ label: string
12
+ }
13
+
14
+ interface Props {
15
+ logoLink: {
16
+ href: string
17
+ target: string
18
+ }
19
+ menuButton: MenuButton
20
+ logoImage: Image
21
+ logoMinifiedImage: Image
22
+ }
23
+
24
+ defineProps<Props>()
25
+
26
+ const mobilePanelEl = useTemplateRef('mobile-panel')
27
+ const mobileOpen = ref(false)
28
+ const isMobilePanelOpening = ref(false)
29
+ const isMobilePanelClosing = ref(false)
30
+
31
+ const onToggleMenu = () => {
32
+ mobilePanelEl.value?.addEventListener('transitionend', onMobilePanelTransitionEnd)
33
+
34
+ document.documentElement.classList.toggle('navigation-toolbar-mobile-open', true)
35
+ isMobilePanelOpening.value = true
36
+ }
37
+
38
+ const onNavClose = () => {
39
+ mobilePanelEl.value?.addEventListener('transitionend', onMobilePanelTransitionEnd)
40
+
41
+ isMobilePanelClosing.value = true
42
+ }
43
+
44
+ const onMobilePanelTransitionEnd = (e) => {
45
+ if (e.target !== mobilePanelEl.value) {
46
+ return
47
+ }
48
+
49
+ mobilePanelEl.value?.removeEventListener('transitionend', onMobilePanelTransitionEnd)
50
+
51
+ mobileOpen.value = !mobileOpen.value
52
+ isMobilePanelOpening.value = false
53
+ isMobilePanelClosing.value = false
54
+
55
+ document.documentElement.classList.toggle('navigation-toolbar-mobile-open', !mobileOpen.value)
56
+ }
57
+
58
+ watch(mobileOpen, (newV) => {
59
+ document.documentElement.classList.toggle('navigation-toolbar-mobile-open', newV)
60
+ })
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ ref="el"
66
+ :class="[
67
+ 'navigation-toolbar-top',
68
+ {
69
+ 'mobile-open': mobileOpen,
70
+ },
71
+ ]"
72
+ >
73
+ <div class="navigation-toolbar-top__top-bar">
74
+ <a
75
+ class="navigation-toolbar-top__top-bar-logo"
76
+ :href="logoLink.href"
77
+ :target="logoLink.target"
78
+ >
79
+ <img :src="logoImage.src" :alt="logoImage.alt" />
80
+ </a>
81
+
82
+ <div class="navigation-toolbar-top__top-bar-ctas">
83
+ <UContextMenu placement="bottom-full">
84
+ <template #trigger>
85
+ <UNavigationToolbarLink v-bind="menuButton" collapsed label-hidden />
86
+ </template>
87
+
88
+ <slot name="contextMenuLinks"></slot>
89
+ </UContextMenu>
90
+
91
+ <UNavigationToolbarLink
92
+ icon="menu"
93
+ :label="getTranslation('openMenu')"
94
+ collapsed
95
+ label-hidden
96
+ @click="onToggleMenu"
97
+ />
98
+ </div>
99
+ </div>
100
+
101
+ <nav
102
+ ref="mobile-panel"
103
+ class="navigation-toolbar-top__nav-panel"
104
+ :class="{ 'is-opening': isMobilePanelOpening, 'is-closing': isMobilePanelClosing }"
105
+ >
106
+ <a
107
+ class="navigation-toolbar-top__nav-panel-logo"
108
+ :href="logoLink.href"
109
+ :target="logoLink.target"
110
+ >
111
+ <img :src="logoImage.src" :alt="logoImage.alt" />
112
+ </a>
113
+
114
+ <div class="navigation-toolbar-top__nav-panel-close">
115
+ <UIconButton
116
+ icon="close"
117
+ variant="outlined-inverted"
118
+ :label="getTranslation('closeMenu')"
119
+ @click="onNavClose"
120
+ />
121
+ </div>
122
+
123
+ <div class="navigation-toolbar-top__nav-links">
124
+ <slot name="navLinks"></slot>
125
+ </div>
126
+ </nav>
127
+ </div>
128
+ </template>
129
+
130
+ <style lang="scss" src="./navigation-toolbar-top.scss" scoped></style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energie360/ui-library",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,9 @@
14
14
  "./wizard": "./wizard/index.js",
15
15
  "./utility/elements/*": "./dist/elements/*",
16
16
  "./utility/layout/*": "./dist/layout/*",
17
- "./abstracts": "./base/abstracts/index.scss"
17
+ "./base/abstracts": "./base/abstracts/index.scss",
18
+ "./styles/form": "./elements/form/form.scss",
19
+ "./styles/form-grid": "./layout/form-grid.scss"
18
20
  },
19
21
  "keywords": [],
20
22
  "author": "",
@@ -0,0 +1,128 @@
1
+ export const getFocusableElements = (parent) => {
2
+ const focusableElements = [
3
+ 'button:not([disabled]):not([tabindex="-1"])',
4
+ '[href]',
5
+ 'input:not([disabled]):not([type="hidden"])',
6
+ 'select:not([disabled])',
7
+ 'textarea:not([disabled])',
8
+ '[tabindex]:not([tabindex="-1"])',
9
+ 'iframe',
10
+ 'details',
11
+ ]
12
+
13
+ return Array.from(parent.querySelectorAll(focusableElements.join(', ')))
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param parent
19
+ * @param {Object} options
20
+ * @param {Boolean} options.focusFirstElement
21
+ * @param {Boolean} options.allowArrowUpDown
22
+ * @param {Boolean} options.allowArrowLeftRight
23
+ * @returns {{release: release}}
24
+ */
25
+ export const focusTrap = (parent, options = {}) => {
26
+ const TAB = 'Tab'
27
+ const ARROW_DOWN = 'ArrowDown'
28
+ const ARROW_UP = 'ArrowUp'
29
+ const ARROW_LEFT = 'ArrowLeft'
30
+ const ARROW_RIGHT = 'ArrowRight'
31
+
32
+ const allFocusable = getFocusableElements(parent)
33
+ const firstFocusable = allFocusable[0]
34
+ const lastFocusable = allFocusable[allFocusable.length - 1]
35
+
36
+ const focusNext = () => {
37
+ let currentIdx = allFocusable.findIndex((el) => el === document.activeElement)
38
+
39
+ if (currentIdx === allFocusable.length - 1) {
40
+ currentIdx = 0
41
+ } else {
42
+ currentIdx++
43
+ }
44
+
45
+ allFocusable[currentIdx].focus()
46
+ }
47
+
48
+ const focusPrevious = () => {
49
+ let currentIdx = allFocusable.findIndex((el) => el === document.activeElement)
50
+
51
+ if (currentIdx === 0) {
52
+ currentIdx = allFocusable.length - 1
53
+ } else {
54
+ currentIdx--
55
+ }
56
+
57
+ allFocusable[currentIdx].focus()
58
+ }
59
+
60
+ const onKeydown = (e) => {
61
+ // Tab
62
+ if (e.code === TAB && !e.shiftKey) {
63
+ if (document.activeElement === lastFocusable) {
64
+ firstFocusable.focus()
65
+ e.preventDefault()
66
+ }
67
+
68
+ return
69
+ }
70
+
71
+ // Shift + Tab
72
+ if (e.code === TAB && e.shiftKey) {
73
+ if (document.activeElement === firstFocusable) {
74
+ lastFocusable.focus()
75
+ e.preventDefault()
76
+ }
77
+
78
+ return
79
+ }
80
+
81
+ if (options.allowArrowUpDown) {
82
+ if (e.code === ARROW_DOWN) {
83
+ focusNext()
84
+ return
85
+ }
86
+
87
+ if (e.code === ARROW_UP) {
88
+ focusPrevious()
89
+ return
90
+ }
91
+ }
92
+
93
+ if (options.allowArrowLeftRight) {
94
+ if (e.code === ARROW_RIGHT) {
95
+ focusNext()
96
+ return
97
+ }
98
+
99
+ if (e.code === ARROW_LEFT) {
100
+ focusPrevious()
101
+ }
102
+ }
103
+ }
104
+
105
+ const release = () => {
106
+ parent.removeEventListener('keydown', onKeydown)
107
+
108
+ // Is this necessary?
109
+ document.activeElement.blur()
110
+ }
111
+
112
+ // Set initial focus
113
+ if (options.focusFirstElement) {
114
+ firstFocusable.focus()
115
+ } else {
116
+ if (parent.tabIndex < 0) {
117
+ parent.tabIndex = 0
118
+ }
119
+
120
+ parent.focus()
121
+ }
122
+
123
+ parent.addEventListener('keydown', onKeydown)
124
+
125
+ return {
126
+ release,
127
+ }
128
+ }