@energie360/ui-library 0.0.4 → 0.1.1

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 (96) hide show
  1. package/base/_resets.scss +2 -0
  2. package/base/abstracts/_mixins.scss +11 -5
  3. package/base/main-base.scss +1 -0
  4. package/components/icon-text-block/icon-text-block.scss +55 -0
  5. package/components/icon-text-block/u-icon-text-block.vue +32 -0
  6. package/components/icon-text-block-group/icon-text-block-group.scss +5 -0
  7. package/components/icon-text-block-group/u-icon-text-block-group.vue +9 -0
  8. package/components/index.js +14 -0
  9. package/components/panel/panel.scss +70 -0
  10. package/components/panel/u-panel.vue +37 -0
  11. package/components/progress-bar/progress-bar.scss +37 -0
  12. package/components/progress-bar/u-progress-bar.vue +21 -0
  13. package/components/table/cell-ctas.scss +12 -0
  14. package/components/table/cell-icon-group.scss +12 -0
  15. package/components/table/cell-icon-text.scss +22 -0
  16. package/components/table/cell-progress-bar.scss +23 -0
  17. package/components/table/table-cell.mixins.scss +60 -0
  18. package/components/table/table-cell.scss +24 -0
  19. package/components/table/table-header.scss +5 -0
  20. package/components/table/table-heading.scss +8 -0
  21. package/components/table/table-row.scss +20 -0
  22. package/components/table/table.scss +12 -0
  23. package/components/table/table.type.ts +31 -0
  24. package/components/table/u-cell-ctas.vue +33 -0
  25. package/components/table/u-cell-icon-group.vue +31 -0
  26. package/components/table/u-cell-icon-text.vue +23 -0
  27. package/components/table/u-cell-progress-bar.vue +22 -0
  28. package/components/table/u-table-cell.vue +47 -0
  29. package/components/table/u-table-header.vue +9 -0
  30. package/components/table/u-table-heading.vue +24 -0
  31. package/components/table/u-table-row.vue +17 -0
  32. package/components/table/u-table.vue +11 -0
  33. package/components/tooltip/dom.js +167 -0
  34. package/components/tooltip/popover.js +200 -0
  35. package/components/tooltip/tooltip.scss +75 -0
  36. package/components/tooltip/u-tooltip.vue +92 -0
  37. package/components/tooltip/viewport.js +21 -0
  38. package/custom-elements.js +1 -0
  39. package/dist/base-style.css +1 -1
  40. package/dist/custom-elements.css +1 -0
  41. package/dist/{index.js → custom-elements.js} +1591 -1600
  42. package/dist/custom-elements.js.map +1 -0
  43. package/elements/button/_button-base.scss +1 -1
  44. package/elements/button/_button-filled-inverted.scss +3 -3
  45. package/elements/button/_button-filled.scss +3 -3
  46. package/elements/button/_button-outlined-inverted.scss +3 -3
  47. package/elements/button/_button-outlined.scss +3 -3
  48. package/elements/button/_button-plain.scss +3 -3
  49. package/elements/button/_button-secondary-outlined.scss +3 -3
  50. package/elements/button/button.js +2 -2
  51. package/elements/button/button.scss +1 -1
  52. package/elements/button/u-button.vue +44 -0
  53. package/elements/elements.js +35 -0
  54. package/elements/form-field/form-field-base.scss +142 -0
  55. package/elements/form-field/form-field-error.scss +20 -0
  56. package/elements/form-field/form-field-prefix-suffix.scss +80 -0
  57. package/elements/form-field/form-field-states.scss +59 -0
  58. package/elements/form-field/index.scss +4 -0
  59. package/elements/icon/icon.js +2 -2
  60. package/elements/icon/{icon.vue → u-icon.vue} +11 -15
  61. package/elements/icon-button/icon-button.js +2 -2
  62. package/elements/icon-button/{icon-button.vue → u-icon-button.vue} +14 -15
  63. package/elements/image/image.scss +3 -0
  64. package/elements/image/u-image.vue +17 -0
  65. package/elements/index.js +6 -31
  66. package/elements/loader/loader.js +2 -2
  67. package/elements/loader/{loader.vue → u-loader.vue} +6 -7
  68. package/elements/spectro/spectro.scss +13 -0
  69. package/elements/spectro/u-spectro.vue +11 -0
  70. package/elements/text-field/text-field.scss +30 -0
  71. package/elements/text-field/text-field.types.ts +6 -0
  72. package/elements/text-field/u-text-field.vue +164 -0
  73. package/elements/types.ts +12 -0
  74. package/env.d.ts +1 -0
  75. package/layout/container/container.scss +30 -0
  76. package/layout/grid/grid-row.scss +10 -0
  77. package/layout/grid/grid.mixin.scss +90 -0
  78. package/layout/grid/grid.scss +121 -0
  79. package/package.json +20 -8
  80. package/tsconfig.app.json +12 -0
  81. package/tsconfig.json +11 -0
  82. package/tsconfig.node.json +19 -0
  83. package/{vite.config.js → vite.config.ts} +1 -1
  84. package/wizard/index.js +4 -0
  85. package/wizard/wizard-intro/u-wizard-intro.vue +40 -0
  86. package/wizard/wizard-intro/wizard-intro.scss +39 -0
  87. package/wizard/wizard-layout/u-wizard-layout-block.vue +21 -0
  88. package/wizard/wizard-layout/u-wizard-layout-element.vue +9 -0
  89. package/wizard/wizard-layout/u-wizard-layout.vue +11 -0
  90. package/wizard/wizard-layout/wizard-layout-block.scss +45 -0
  91. package/wizard/wizard-layout/wizard-layout-element.scss +3 -0
  92. package/wizard/wizard-layout/wizard-layout.scss +40 -0
  93. package/dist/index.css +0 -1
  94. package/dist/index.js.map +0 -1
  95. package/elements/button/button.vue +0 -42
  96. package/index.js +0 -1
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import UProgressBar from '../progress-bar/u-progress-bar.vue'
3
+
4
+ interface Props {
5
+ label?: string
6
+ value: number
7
+ labelPosition?: 'top' | 'right'
8
+ }
9
+
10
+ const { labelPosition = 'top' } = defineProps<Props>()
11
+ </script>
12
+
13
+ <template>
14
+ <div :class="['cell-progress-bar', `label-position-${labelPosition}`]">
15
+ <p class="progress-bar-label" v-if="label">{{ label }}</p>
16
+ <UProgressBar :value="value"></UProgressBar>
17
+ </div>
18
+ </template>
19
+
20
+ <style scoped lang="scss">
21
+ @use './cell-progress-bar.scss';
22
+ </style>
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ TableCellBase,
4
+ TableCellHAlign,
5
+ TableCellTextStyle,
6
+ TableCellVAlign,
7
+ } from './table.type'
8
+ import UIcon from '../../elements/icon/u-icon.vue'
9
+ import UTooltip from '../tooltip/u-tooltip.vue'
10
+
11
+ interface Props extends TableCellBase {
12
+ infoText?: string
13
+ textStyle?: TableCellTextStyle
14
+ }
15
+
16
+ const {
17
+ textStyle = TableCellTextStyle.normal,
18
+ hAlign = TableCellHAlign.left,
19
+ vAlign = TableCellVAlign.center,
20
+ } = defineProps<Props>()
21
+ </script>
22
+
23
+ <template>
24
+ <div
25
+ role="cell"
26
+ :class="[
27
+ 'table-cell',
28
+ `h-align-${hAlign}`,
29
+ `v-align-${vAlign}`,
30
+ { 'has-tooltip': infoText },
31
+ ]"
32
+ >
33
+ <div :class="['cell-content', `text-${textStyle}`]">
34
+ <slot>{{ text }}</slot>
35
+ </div>
36
+
37
+ <div class="info-tooltip" v-if="infoText">
38
+ <UTooltip :title="infoText">
39
+ <UIcon name="info"></UIcon>
40
+ </UTooltip>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <style scoped lang="scss">
46
+ @use './table-cell.scss';
47
+ </style>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <div class="table-header">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped lang="scss">
8
+ @use './table-header.scss';
9
+ </style>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ text?: string
4
+ hAlign?: 'left' | 'center' | 'right'
5
+ vAlign?: 'top' | 'center' | 'bottom'
6
+ }
7
+
8
+ const { hAlign = 'left', vAlign = 'top' } = defineProps<Props>()
9
+ </script>
10
+
11
+ <template>
12
+ <div
13
+ role="cell"
14
+ :class="['table-heading', `h-align-${hAlign}`, `v-align-${vAlign}`]"
15
+ >
16
+ <div class="cell-content">
17
+ <slot :text="text">{{ text }}</slot>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <style scoped lang="scss">
23
+ @use './table-heading.scss';
24
+ </style>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ highlight?: boolean
4
+ }
5
+
6
+ defineProps<Props>()
7
+ </script>
8
+
9
+ <template>
10
+ <div role="row" :class="['table-row', { highlight }]">
11
+ <slot></slot>
12
+ </div>
13
+ </template>
14
+
15
+ <style scoped lang="scss">
16
+ @use './table-row.scss';
17
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="table-scroll-wrapper">
3
+ <div role="table" class="table">
4
+ <slot></slot>
5
+ </div>
6
+ </div>
7
+ </template>
8
+
9
+ <style scoped lang="scss">
10
+ @use './table.scss';
11
+ </style>
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Wrap an element with another element
3
+ *
4
+ * @param {Node} el
5
+ * @param {string} wrapper - Tag name of wrapper element. Can also have a classname (like emmet notation)
6
+ *
7
+ * @returns {Node}
8
+ */
9
+ export const wrap = (el, wrapper) => {
10
+ const parsedWrapper = wrapper.split('.')
11
+ const wrapperEl = document.createElement(parsedWrapper[0])
12
+
13
+ if (parsedWrapper.length > 1) {
14
+ wrapperEl.className = parsedWrapper.slice(1).join(' ')
15
+ }
16
+
17
+ el.parentNode.insertBefore(wrapperEl, el)
18
+ wrapperEl.appendChild(el)
19
+
20
+ return wrapperEl
21
+ }
22
+
23
+ /**
24
+ * Get element position relative to document.
25
+ *
26
+ * @param {Node} el
27
+ * @returns {{top: number, left: number, width: number, height: number}}
28
+ */
29
+ export const getElementPosition = (el) => {
30
+ const elRect = el.getBoundingClientRect()
31
+ const scrollTop = window.scrollY
32
+ const scrollLeft = window.scrollX
33
+
34
+ return {
35
+ top: elRect.top + scrollTop,
36
+ left: elRect.left + scrollLeft,
37
+ width: elRect.width,
38
+ height: elRect.height,
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Decode HTML encoded string.
44
+ *
45
+ * @param {string} str
46
+ * @returns {string}
47
+ */
48
+ export const htmlDecode = (str) => {
49
+ const doc = new DOMParser().parseFromString(str, 'text/html')
50
+ return doc.documentElement.textContent
51
+ }
52
+
53
+ export const htmlToDocFragment = (str) => {
54
+ const tpl = document.createElement('template')
55
+ tpl.innerHTML = str
56
+ return tpl.content.cloneNode(true)
57
+ }
58
+
59
+ /**
60
+ * Reverse order of direct child nodes of given parent.
61
+ *
62
+ * @param {Node} parent
63
+ */
64
+ export const reverseChildren = (parent) => {
65
+ for (let i = 1; i < parent.childNodes.length; i++) {
66
+ parent.insertBefore(parent.childNodes[i], parent.firstChild)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if element is in a slotted element.
72
+ *
73
+ * @param {Element} el
74
+ * @returns {boolean}
75
+ */
76
+ const isInSlottedElement = (el) => {
77
+ const parentEl = el.parentElement
78
+
79
+ if (parentEl === document.body || !parentEl) {
80
+ return false
81
+ }
82
+
83
+ if (parentEl.assignedSlot) {
84
+ return true
85
+ } else {
86
+ return isInSlottedElement(parentEl)
87
+ }
88
+ }
89
+
90
+ /**
91
+ *
92
+ * @param {Element} el
93
+ * @returns {boolean}
94
+ */
95
+ const isOffsetParent = (el) => {
96
+ const style = getComputedStyle(el)
97
+ return style.position === 'relative' || style.position === 'absolute'
98
+ }
99
+
100
+ /**
101
+ * Get the expected offsetParent within nested shadow roots.
102
+ *
103
+ * Because elements are nested in web components or slotted in web components we can't get desired offsetParent.
104
+ *
105
+ * @param {Element} el
106
+ * @returns {Element}
107
+ */
108
+ export const getOffsetParent = (el) => {
109
+ if (el === document.body) {
110
+ // We arrived at the top.
111
+ // 'body' must be the offsetParent then.
112
+ return el
113
+ }
114
+
115
+ const offsetParent = el.offsetParent
116
+
117
+ if (offsetParent === document.body || offsetParent === null) {
118
+ // It's possible that we're a slotted element, in which case we'll always have the potentially 'wrong' offsetParent 'body'.
119
+ // Dig deeper...
120
+ if (el.assignedSlot) {
121
+ // A <slot> doesn't have an offsetParent. Let's use the parentNode.
122
+ const parentElement = el.assignedSlot.parentElement
123
+
124
+ // Handle case where the parent node actually is an offsetParent
125
+ if (isOffsetParent(parentElement)) {
126
+ return parentElement
127
+ }
128
+
129
+ return getOffsetParent(el.assignedSlot.parentElement)
130
+ }
131
+
132
+ if (isInSlottedElement(el)) {
133
+ return getOffsetParent(el.parentElement)
134
+ }
135
+
136
+ if (el.getRootNode() instanceof ShadowRoot) {
137
+ // If we're nested in another shadow root we'll also always get 'body' as offsetParent.
138
+ // Check the offsetParent of the current host element.
139
+ return getOffsetParent(el.getRootNode().host)
140
+ }
141
+ }
142
+
143
+ return offsetParent
144
+ }
145
+
146
+ /**
147
+ * Get offset positions of parent which is not 'static'.
148
+ *
149
+ * @param {Element} el
150
+ * @returns {Object}
151
+ */
152
+ export const getOffsetParentPosition = (el) => {
153
+ const offsetParent = getOffsetParent(el)
154
+ const elRect = el.getBoundingClientRect()
155
+ const offsetParentRect = offsetParent.getBoundingClientRect()
156
+
157
+ return {
158
+ top: elRect.top - offsetParentRect.top,
159
+ left: elRect.left - offsetParentRect.left,
160
+ }
161
+ }
162
+
163
+ export const moveChildren = (sourceParent, targetParent) => {
164
+ while (sourceParent.childNodes.length > 0) {
165
+ targetParent.appendChild(sourceParent.childNodes[0])
166
+ }
167
+ }
@@ -0,0 +1,200 @@
1
+ import { getViewportDimensions } from './viewport'
2
+ import { getOffsetParentPosition } from './dom'
3
+
4
+ const defaultOptions = {
5
+ offset: 0,
6
+ viewportPadding: 0,
7
+ placement: 'top-left',
8
+ }
9
+
10
+ /**
11
+ * This simply calculates the desired position of the popover and returns the coordinates.
12
+ * It won't change the DOM.
13
+ *
14
+ * @param {Element} refEl
15
+ * @param {Element} popoverEl
16
+ * @param {Object} options - Options object
17
+ * @param {number} options.offset - offset to refEl in px
18
+ * @param {number} options.viewportPadding - Padding to viewport in px
19
+ * @param {string} options.placement - One of the following keywords: top-right, top-left, top-center, bottom-right, bottom-left, bottom-center
20
+ */
21
+ export const getPopoverPosition = (refEl, popoverEl, options = {}) => {
22
+ options = Object.assign({}, defaultOptions, options)
23
+
24
+ const refRect = refEl.getBoundingClientRect()
25
+ let popoverRect = popoverEl.getBoundingClientRect()
26
+
27
+ const offsetParent = getOffsetParentPosition(refEl)
28
+
29
+ let top
30
+ let left
31
+ const offsetTop = offsetParent.top
32
+ const offsetLeft = offsetParent.left
33
+ const { vpWidth } = getViewportDimensions()
34
+ const docHeight = document.documentElement.scrollHeight
35
+ const scrollY = window.scrollY
36
+
37
+ if (popoverRect.width === 0 && popoverRect.height === 0) {
38
+ // Popover probably has display:none.
39
+ // Make it visible for a frame to get the correct dimensions.
40
+ popoverEl.style.display = 'block'
41
+ popoverRect = popoverEl.getBoundingClientRect()
42
+ popoverEl.style.display = ''
43
+ }
44
+
45
+ switch (options.placement) {
46
+ case 'top-right':
47
+ top = offsetTop - popoverRect.height - options.offset
48
+ left = offsetLeft + refRect.width - popoverRect.width
49
+ // console.log(left)
50
+ break
51
+
52
+ case 'top-left':
53
+ top = offsetTop - popoverRect.height - options.offset
54
+ left = offsetLeft
55
+ break
56
+
57
+ case 'bottom-right':
58
+ top = offsetTop + refRect.height + options.offset
59
+ left = offsetLeft + refRect.width - popoverRect.width
60
+ break
61
+
62
+ case 'bottom-left':
63
+ top = offsetTop + refRect.height + options.offset
64
+ left = offsetLeft
65
+ break
66
+
67
+ // TODO: add remaining placement options
68
+ case 'bottom-center':
69
+ break
70
+
71
+ case 'top-center':
72
+ }
73
+
74
+ // Add corrections to position if necessary
75
+
76
+ // Check left or right overflow
77
+ const leftDiff = refRect.left - offsetLeft
78
+ if (left + leftDiff < options.viewportPadding) {
79
+ left = 0 - leftDiff + options.viewportPadding
80
+ } else if (
81
+ left + leftDiff + popoverRect.width >
82
+ vpWidth - options.viewportPadding
83
+ ) {
84
+ left = vpWidth - leftDiff - popoverRect.width - options.viewportPadding
85
+ }
86
+
87
+ // Check top or bottom overflow
88
+ const topDiff = refRect.top - offsetTop
89
+ if (top + topDiff + scrollY < options.viewportPadding) {
90
+ // popover is too high.
91
+ // console.log('too high')
92
+ // Switch position to bottom
93
+ top = offsetTop + refRect.height + options.offset
94
+ } else if (
95
+ top + topDiff + scrollY + popoverRect.height >
96
+ docHeight - options.viewportPadding
97
+ ) {
98
+ // popover is too low.
99
+ // console.log('too low')
100
+ // Swith position to top.
101
+ top = offsetTop - popoverRect.height - options.offset
102
+ }
103
+
104
+ return {
105
+ top,
106
+ left,
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Calculates the desired position of the popover relative to <body>.
112
+ *
113
+ * @param {Element} refEl
114
+ * @param {Element} popoverEl
115
+ * @param {Object} options - Options object
116
+ * @param {number} options.offset - offset to refEl in px
117
+ * @param {number} options.viewportPadding - Padding to viewport in px
118
+ * @param {string} options.placement - One of the following keywords: top-right, top-left, top-center, bottom-right, bottom-left, bottom-center
119
+ */
120
+ export const getPopoverPositionBody = (refEl, popoverEl, options = {}) => {
121
+ options = Object.assign({}, defaultOptions, options)
122
+
123
+ const refRect = refEl.getBoundingClientRect()
124
+ let popoverRect = popoverEl.getBoundingClientRect()
125
+
126
+ let top
127
+ let left
128
+ const topCorrection = 0
129
+ let leftCorrection = 0
130
+ const scrollY = window.scrollY
131
+ const scrollX = window.scrollX
132
+ const { vpWidth } = getViewportDimensions()
133
+ // const docHeight = document.documentElement.scrollHeight
134
+ const refTop = refRect.top + scrollY
135
+ const refLeft = refRect.left + scrollX
136
+
137
+ if (popoverRect.width === 0 && popoverRect.height === 0) {
138
+ // Popover probably has display:none.
139
+ // Make it visible for a frame to get the correct dimensions.
140
+ popoverEl.style.display = 'block'
141
+ popoverRect = popoverEl.getBoundingClientRect()
142
+ popoverEl.style.display = ''
143
+ }
144
+
145
+ switch (options.placement) {
146
+ case 'top-right':
147
+ // top = offsetTop - popoverRect.height - options.offset
148
+ // left = offsetLeft + refRect.width - popoverRect.width
149
+ // console.log(left)
150
+ break
151
+
152
+ case 'top-left':
153
+ // top = offsetTop - popoverRect.height - options.offset
154
+ // left = offsetLeft
155
+ break
156
+
157
+ case 'bottom-right':
158
+ // top = offsetTop + refRect.height + options.offset
159
+ // left = offsetLeft + refRect.width - popoverRect.width
160
+ break
161
+
162
+ case 'bottom-left':
163
+ // top = offsetTop + refRect.height + options.offset
164
+ // left = offsetLeft
165
+ break
166
+
167
+ case 'bottom-center':
168
+ top = refTop + refRect.height + options.offset
169
+ left = refLeft + refRect.width / 2 - popoverRect.width / 2
170
+ break
171
+
172
+ case 'top-center':
173
+ top = refTop - options.offset - popoverRect.height
174
+ left = refLeft + refRect.width / 2 - popoverRect.width / 2
175
+ }
176
+
177
+ // Add corrections to position if necessary
178
+
179
+ // Check left or right overflow
180
+ if (left < options.viewportPadding) {
181
+ // too left
182
+ leftCorrection = Math.abs(left) + options.viewportPadding
183
+ left = options.viewportPadding
184
+ } else if (left + popoverRect.width > vpWidth - options.viewportPadding) {
185
+ // too right
186
+ leftCorrection =
187
+ vpWidth - options.viewportPadding - popoverRect.width - left
188
+ left = vpWidth - options.viewportPadding - popoverRect.width
189
+ }
190
+
191
+ // Check top or bottom overflow
192
+ // TODO: Add these corrections
193
+
194
+ return {
195
+ top,
196
+ left,
197
+ leftCorrection,
198
+ topCorrection,
199
+ }
200
+ }
@@ -0,0 +1,75 @@
1
+ @use '../../base/abstracts/' as a;
2
+
3
+ .tooltip {
4
+ pointer-events: none;
5
+ z-index: a.$layer-tooltip;
6
+ position: absolute;
7
+ top: 0;
8
+ left: 0;
9
+ max-width: a.rem(360);
10
+ min-width: a.rem(84);
11
+ opacity: 0;
12
+ transform: translateY(-5px);
13
+ transition:
14
+ opacity a.$trs-default,
15
+ transform a.$trs-default;
16
+
17
+ @include a.bp(lg) {
18
+ max-width: a.rem(280);
19
+ }
20
+
21
+ .tooltip__content {
22
+ @include a.type(100);
23
+
24
+ // This nesting here is only to make the new sass compiler happy.
25
+ & {
26
+ z-index: 0;
27
+ position: relative;
28
+ padding: var(--e-space-2);
29
+ background-color: var(--e-c-mono-00);
30
+ box-shadow: var(--e-elevation-sm);
31
+ border: 1px solid var(--e-c-mono-100);
32
+ border-radius: var(--e-brd-radius-1);
33
+ }
34
+
35
+ @include a.bp(lg) {
36
+ @include a.type(50);
37
+
38
+ padding: var(--e-space-3);
39
+ }
40
+ }
41
+
42
+ .tooltip__pointer {
43
+ position: absolute;
44
+ bottom: -4px;
45
+ left: calc(50% - 6px);
46
+
47
+ &::before {
48
+ content: '';
49
+ display: block;
50
+ width: 10px;
51
+ height: 9.6px;
52
+ transform: rotate(53.13deg) skewX(15.36deg);
53
+ box-shadow: var(--e-elevation-sm);
54
+ background-color: var(--e-c-mono-00);
55
+ border-right: 1px solid var(--e-c-mono-100);
56
+ border-bottom: 1px solid var(--e-c-mono-100);
57
+ }
58
+
59
+ &::after {
60
+ content: '';
61
+ position: absolute;
62
+ bottom: 5px;
63
+ left: -2px;
64
+ width: 14px;
65
+ height: 7px;
66
+ background: var(--e-c-mono-00);
67
+ }
68
+ }
69
+
70
+ // States
71
+ &.tooltip--open {
72
+ opacity: 1;
73
+ transform: translateY(0);
74
+ }
75
+ }
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { getPopoverPositionBody } from './popover'
4
+
5
+ interface Props {
6
+ title: string
7
+ placement?:
8
+ | 'top-right'
9
+ | 'top-left'
10
+ | 'top-center'
11
+ | 'bottom-right'
12
+ | 'bottom-left'
13
+ | 'bottom-center'
14
+ offset?: number
15
+ viewportPadding?: number
16
+ }
17
+
18
+ const {
19
+ placement = 'top-center',
20
+ offset = 12,
21
+ viewportPadding = 20,
22
+ } = defineProps<Props>()
23
+
24
+ const tooltip = ref(null)
25
+ const root = ref(null)
26
+ const pointer = ref(null)
27
+
28
+ const showTooltip = () => {
29
+ const popoverPosition = getPopoverPositionBody(root.value, tooltip.value, {
30
+ offset,
31
+ placement,
32
+ viewportPadding,
33
+ })
34
+
35
+ // Set position
36
+ tooltip.value.style.top = `${popoverPosition.top}px`
37
+ tooltip.value.style.left = `${popoverPosition.left}px`
38
+
39
+ // Fix pointer position if necessary
40
+ if (popoverPosition.leftCorrection) {
41
+ pointer.value.style.transform = `translateX(${popoverPosition.leftCorrection * -1}px)`
42
+ }
43
+
44
+ tooltip.value.ariaHidden = 'false'
45
+ tooltip.value.classList.toggle('tooltip--open', true)
46
+ }
47
+
48
+ const hideTooltip = () => {
49
+ const onTransitionend = () => {
50
+ tooltip.value.removeEventListener('transitionend', onTransitionend)
51
+
52
+ tooltip.value.style.top = ''
53
+ tooltip.value.style.left = ''
54
+ pointer.value.style.transform = ''
55
+ }
56
+
57
+ tooltip.value.addEventListener('transitionend', onTransitionend)
58
+
59
+ tooltip.value.ariaHidden = 'true'
60
+ tooltip.value.classList.toggle('tooltip--open', false)
61
+ }
62
+
63
+ // On desktop (or non-touch devices) mouse events will be fired before the pointer events.
64
+ // For mobile and touch devices it's th opposite, pointer events will be fired first.
65
+ // This way we can detect on what device we are and handle the behaviour accordingly.
66
+ const onMouseenter = () => {
67
+ showTooltip()
68
+ }
69
+
70
+ const onMouseleave = () => {
71
+ hideTooltip()
72
+ }
73
+ </script>
74
+
75
+ <template>
76
+ <span @mouseenter="onMouseenter" @mouseleave="onMouseleave" ref="root">
77
+ <slot></slot>
78
+ </span>
79
+
80
+ <Teleport to="body">
81
+ <div ref="tooltip" class="tooltip">
82
+ <div class="tooltip__content">
83
+ {{ title }}
84
+ </div>
85
+ <span class="tooltip__pointer" ref="pointer"></span>
86
+ </div>
87
+ </Teleport>
88
+ </template>
89
+
90
+ <style scoped lang="scss">
91
+ @use './tooltip.scss';
92
+ </style>
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Get element offset relative to document.
3
+ *
4
+ * @param {Element} el
5
+ * @returns {{top: *, left: *, width, height}}
6
+ */
7
+ export const getElOffset = (el) => {
8
+ const rect = el.getBoundingClientRect()
9
+
10
+ return {
11
+ top: window.scrollY + rect.top,
12
+ left: window.scrollX + rect.left,
13
+ width: rect.width,
14
+ height: rect.height,
15
+ }
16
+ }
17
+
18
+ export const getViewportDimensions = () => ({
19
+ vpWidth: window.innerWidth,
20
+ vpHeight: window.innerHeight,
21
+ })
@@ -0,0 +1 @@
1
+ export * from './elements/elements'