@eturnity/eturnity_reusable_components 7.39.5-qa-elisee-7.42.1 → 7.45.0-EPDM-4900.0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "7.39.5-qa-elisee-7.42.1",
3
+ "version": "7.45.0-EPDM-4900.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -35,9 +35,6 @@ const theme = {
35
35
  tablet: '768px',
36
36
  tabletLarge: '950px',
37
37
  },
38
- fonts: {
39
- mainFont: '"Figtree", sans-serif',
40
- },
41
38
  borderRadius: '4px',
42
39
  }
43
40
 
@@ -111,10 +111,10 @@
111
111
  props.backgroundColor ? props.backgroundColor : 'transparent'};
112
112
  padding: ${(props) => (props.backgroundColor ? '3px' : '0')};
113
113
  }
114
- svg path:not(.fix) {
114
+ svg path {
115
115
  ${({ theme, color }) => color && `fill: ${theme.colors[color] || color};`}
116
116
  }
117
- &:hover svg path:not(.fix) {
117
+ &:hover svg path {
118
118
  ${({ theme, hoveredColor }) =>
119
119
  hoveredColor && `fill: ${theme.colors[hoveredColor] || hoveredColor};`}
120
120
  }
@@ -1,10 +1,6 @@
1
1
  <template>
2
- <InfoContainer
3
- background-color="backgrounColor"
4
- :border-color="borderColor"
5
- :has-dashed-border="hasDashedBorder"
6
- >
7
- <RCIcon :color="color" name="info" size="24px" />
2
+ <InfoContainer>
3
+ <RCIcon name="info" size="24px" :color="color" />
8
4
  <TextContainer>
9
5
  <slot></slot>
10
6
  </TextContainer>
@@ -14,24 +10,12 @@
14
10
  <script>
15
11
  import styled from 'vue3-styled-components'
16
12
  import RCIcon from '../icon'
17
- const propsContainer = {
18
- backgroundColor: String,
19
- hasDashedBorder: Boolean,
20
- borderColor: String,
21
- }
22
- const InfoContainer = styled('div', propsContainer)`
13
+
14
+ const InfoContainer = styled.div`
23
15
  display: flex;
24
16
  gap: 15px;
25
- padding: 15px;
26
- border: 1px ${(props) => (props.hasDashedBorder ? 'dashed' : 'solid')}
27
- ${(props) =>
28
- props.theme.colors[props.borderColor]
29
- ? props.theme.colors[props.borderColor]
30
- : props.borderColor};
31
- background-color: ${(props) =>
32
- props.theme.colors[props.backgroundColor]
33
- ? props.theme.colors[props.backgroundColor]
34
- : props.backgroundColor};
17
+ padding: 20px;
18
+ border: 1px dashed ${(props) => props.theme.colors.grey4};
35
19
  border-radius: 4px;
36
20
  `
37
21
 
@@ -49,19 +33,8 @@
49
33
  },
50
34
  props: {
51
35
  color: {
52
- required: false,
53
- },
54
- backgrounColor: {
55
- required: false,
56
- },
57
- hasDashedBorder: {
58
- required: false,
59
- default: true,
60
- },
61
- borderColor: {
62
- required: false,
63
- default: 'grey4',
64
- },
65
- },
36
+ required: false
37
+ }
38
+ }
66
39
  }
67
40
  </script>
@@ -1,31 +1,34 @@
1
1
  <template>
2
- <ComponentWrapper>
3
- <IconWrapper :size="size">
4
- <IconImg
5
- @click.prevent="toggleShowInfo()"
6
- @mouseenter="openTrigger == 'onHover' ? toggleShowInfo() : ''"
7
- @mouseleave="openTrigger == 'onHover' ? toggleShowInfo() : ''"
8
- >
9
- <IconComponent
10
- :color="iconColor"
11
- cursor="pointer"
12
- name="info"
13
- :size="size"
14
- />
15
- </IconImg>
16
- <TextOverlay
17
- v-if="showInfo"
18
- :align-arrow="alignArrow"
19
- :half-computed-text-info-width="halfComputedTextInfoWidth"
20
- :icon-size="size"
21
- :info-position="infoPosition"
22
- :max-width="maxWidth"
23
- :width="width"
24
- ><slot></slot>
25
- <span v-if="text.length > 0" v-html="text"></span>
26
- </TextOverlay>
27
- </IconWrapper>
28
- </ComponentWrapper>
2
+ <PageContainer ref="container">
3
+ <div
4
+ ref="icon"
5
+ @click="openTrigger === 'onClick' && toggleInfo()"
6
+ @mouseenter="openTrigger === 'onHover' && showInfo()"
7
+ @mouseleave="openTrigger === 'onHover' && hideInfo()"
8
+ >
9
+ <IconComponent
10
+ :color="iconColor"
11
+ cursor="pointer"
12
+ name="info"
13
+ :size="size"
14
+ />
15
+ </div>
16
+ <Teleport v-if="isVisible" to="body">
17
+ <TextWrapper :style="wrapperStyle">
18
+ <TextOverlay ref="infoBox" :image="image" :style="boxStyle">
19
+ <OverlayImage
20
+ v-if="image"
21
+ ref="infoImage"
22
+ alt="Info Image"
23
+ :src="image"
24
+ @load="onImageLoad"
25
+ />
26
+ <span ref="textContent" :style="textStyle" v-html="text"></span>
27
+ </TextOverlay>
28
+ <Arrow :image="image" :style="arrowStyle" />
29
+ </TextWrapper>
30
+ </Teleport>
31
+ </PageContainer>
29
32
  </template>
30
33
 
31
34
  <script>
@@ -34,155 +37,309 @@
34
37
  // <info-text
35
38
  // text="Veritatis et quasi architecto beatae vitae"
36
39
  // size="20px"
37
- // alignArrow="right" // which side the arrow should be on
40
+ // openTrigger="onClick"
41
+ // buttonType="error"
42
+ // image="path/to/image.jpg"
38
43
  // />
39
- import theme from '../../assets/theme.js'
40
- import styled from 'vue3-styled-components'
44
+ import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'
41
45
  import IconComponent from '../icon'
46
+ import styled from 'vue3-styled-components'
47
+ import theme from '../../assets/theme.js'
42
48
 
43
- const textAttrs = {
44
- iconSize: String,
45
- alignArrow: String,
46
- width: String,
47
- halfComputedTextInfoWidth: Number,
48
- infoPosition: String,
49
- }
50
- const TextOverlay = styled('div', textAttrs)`
51
- position: absolute;
52
- ${(props) =>
53
- props.infoPosition == 'top'
54
- ? 'bottom : calc(' + props.iconSize + ' + 15px)'
55
- : 'top : calc(' + props.iconSize + ' + 15px)'};
56
- ${(props) =>
57
- props.alignArrow === 'left'
58
- ? 'left: calc(' + props.iconSize + ' /2 - 18px)'
59
- : props.alignArrow === 'center'
60
- ? 'left: calc((-' + props.width + ' + ' + props.iconSize + ') /2 + 2px)'
61
- : 'right: calc(' + props.iconSize + ' /2 - 17px)'};
62
- text-align: left;
63
- background: ${(props) => props.theme.colors.black};
64
- padding: 10px;
65
- width: ${(props) => props.width};
66
- max-width: ${(props) => props.maxWidth};
67
- font-size: 13px;
68
- font-weight: 400;
69
- line-height: normal;
49
+ const TextOverlay = styled('div')`
50
+ background-color: ${(props) =>
51
+ props.image ? props.theme.colors.white : props.theme.colors.black};
52
+ color: ${(props) =>
53
+ props.image ? props.theme.colors.grey1 : props.theme.colors.white};
54
+ font-size: ${(props) => (props.image ? '12px' : '13px')};
70
55
  border-radius: 4px;
71
- z-index: 999;
72
- color: ${(props) => props.theme.colors.white};
73
-
74
- :before {
75
- content: '';
76
- background-color: ${(props) => props.theme.colors.black};
77
- position: absolute;
78
-
79
- ${(props) =>
80
- props.infoPosition == 'top' ? 'bottom : -10px' : 'top: 2px'};
56
+ padding: 10px;
57
+ word-wrap: break-word;
58
+ overflow-wrap: break-word;
59
+ white-space: normal;
60
+ width: 100%; // Ensure the TextOverlay takes full width of its parent
61
+ box-shadow: ${(props) =>
62
+ props.image ? '0 2px 10px rgba(0, 0, 0, 0.1)' : 'none'};
81
63
 
82
- ${(props) =>
83
- props.alignArrow === 'left'
84
- ? 'left:40px;'
85
- : props.alignArrow == 'center'
86
- ? 'left: calc(50% + 19px);'
87
- : 'right:-13px;'};
88
- height: 8px;
89
- width: 8px;
90
- transform-origin: center center;
91
- transform: translate(-2em, -0.5em) rotate(45deg);
64
+ a {
65
+ color: ${(props) => props.theme.colors.blue};
92
66
  }
93
67
 
94
- span a {
95
- color: #2cc0eb;
68
+ img + span {
69
+ margin-top: 10px;
70
+ display: block;
96
71
  }
97
72
  `
98
73
 
99
- const iconAttrs = { size: String }
100
- const IconWrapper = styled('div', iconAttrs)`
74
+ const Arrow = styled('div')`
75
+ position: absolute;
76
+ width: 0;
77
+ height: 0;
78
+ border-left: 8px solid transparent;
79
+ border-right: 8px solid transparent;
80
+ border-top: 8px solid
81
+ ${(props) =>
82
+ props.image ? props.theme.colors.white : props.theme.colors.black};
83
+ filter: ${(props) =>
84
+ props.image ? 'drop-shadow(0 2px 2px rgba(0, 0, 0, 0.1))' : 'none'};
85
+ `
86
+
87
+ const PageContainer = styled('div')`
88
+ display: inline-block;
101
89
  position: relative;
102
- height: ${(props) => props.size};
103
90
  `
104
91
 
105
- const IconImg = styled.div`
106
- line-height: 0;
92
+ const TextWrapper = styled('div')`
93
+ z-index: 99999;
94
+ position: absolute;
107
95
  `
108
96
 
109
- const ComponentWrapper = styled.div`
110
- display: inline-block;
97
+ const OverlayImage = styled('img')`
98
+ width: 100%;
99
+ height: auto;
111
100
  `
112
101
 
113
102
  export default {
114
103
  name: 'InfoText',
115
104
  components: {
116
- IconWrapper,
117
- TextOverlay,
118
- ComponentWrapper,
119
- IconImg,
120
105
  IconComponent,
106
+ TextOverlay,
107
+ Arrow,
108
+ PageContainer,
109
+ TextWrapper,
110
+ OverlayImage,
121
111
  },
122
112
  props: {
123
113
  text: {
124
- required: false,
125
- default: '',
126
114
  type: String,
115
+ default: '',
127
116
  },
128
117
  size: {
129
- required: false,
130
- default: '14px',
131
- type: String,
132
- },
133
- infoPosition: {
134
- required: false,
135
- default: 'bottom',
136
118
  type: String,
119
+ default: '14px',
137
120
  },
138
- alignArrow: {
139
- required: false,
140
- default: 'center',
121
+ maxWidth: {
141
122
  type: String,
123
+ default: '400px',
142
124
  },
143
125
  openTrigger: {
144
- required: false,
145
- default: 'onHover', // onHover, onClick
146
126
  type: String,
127
+ default: 'onHover',
128
+ validator: (value) => ['onHover', 'onClick'].includes(value),
147
129
  },
148
- width: {
149
- required: false,
150
- default: '200px',
130
+ buttonType: {
151
131
  type: String,
132
+ default: 'regular',
133
+ validator: (value) => ['regular', 'error'].includes(value),
152
134
  },
153
- maxWidth: {
154
- default: '400px',
135
+ image: {
155
136
  type: String,
137
+ default: '',
156
138
  },
157
139
  },
158
- data() {
140
+ setup(props) {
141
+ const isVisible = ref(false)
142
+ const container = ref(null)
143
+ const icon = ref(null)
144
+ const infoBox = ref(null)
145
+ const textContent = ref(null)
146
+ const infoImage = ref(null)
147
+ const infoBoxWidth = ref(0)
148
+ const infoBoxHeight = ref(0)
149
+ const boxStyle = ref({})
150
+ const arrowStyle = ref({})
151
+ const wrapperStyle = ref({})
152
+
153
+ const textStyle = computed(() => ({
154
+ fontSize: props.image ? '12px' : '13px',
155
+ color: props.image ? theme.colors.grey1 : theme.colors.white,
156
+ textAlign: props.image ? 'right' : 'left',
157
+ }))
158
+
159
+ const calculatePosition = (width) => {
160
+ if (!icon.value) return {}
161
+
162
+ const iconRect = icon.value.getBoundingClientRect()
163
+ const windowHeight = window.innerHeight
164
+ const windowWidth = window.innerWidth
165
+
166
+ let top = iconRect.bottom + 10
167
+ let left = iconRect.left
168
+
169
+ // Adjust left based on the infoBoxWidth
170
+ if (left + width > windowWidth) {
171
+ left = windowWidth - width - 10
172
+ }
173
+
174
+ // Ensure there's enough space below or place above if not
175
+ const isAbove = top + infoBoxHeight.value > windowHeight
176
+ if (isAbove) {
177
+ top = iconRect.top - infoBoxHeight.value - 10
178
+ }
179
+
180
+ // Calculate arrow position
181
+ let arrowLeft = iconRect.left - left + iconRect.width / 2 - 8
182
+
183
+ // Adjust arrow position if it's too close to the edges
184
+ const edgeThreshold = 2 // pixels from the edge to start adjusting
185
+ if (arrowLeft < edgeThreshold) {
186
+ arrowLeft = edgeThreshold
187
+ } else if (arrowLeft > width - 16 - edgeThreshold) {
188
+ arrowLeft = width - 16 - edgeThreshold
189
+ }
190
+
191
+ const arrowTop = isAbove ? 'auto' : '-7px'
192
+ const arrowBottom = isAbove ? '-7px' : 'auto'
193
+
194
+ arrowStyle.value = {
195
+ left: `${arrowLeft}px`,
196
+ top: arrowTop,
197
+ bottom: arrowBottom,
198
+ transform: isAbove ? 'none' : 'rotate(180deg)',
199
+ }
200
+
201
+ wrapperStyle.value = {
202
+ position: 'fixed',
203
+ top: `${top}px`,
204
+ left: `${left}px`,
205
+ transform: 'none',
206
+ width: `${width}px`, // Set a fixed width for the wrapper
207
+ }
208
+
209
+ return {
210
+ width: '100%', // Always use 100% width for the TextOverlay
211
+ maxWidth: props.maxWidth,
212
+ overflowY: 'auto',
213
+ backgroundColor: props.image
214
+ ? theme.colors.white
215
+ : theme.colors.black,
216
+ }
217
+ }
218
+
219
+ const showInfo = async () => {
220
+ isVisible.value = true
221
+ await nextTick()
222
+ updatePosition()
223
+ }
224
+
225
+ const hideInfo = () => {
226
+ isVisible.value = false
227
+ }
228
+
229
+ const toggleInfo = () => {
230
+ isVisible.value ? hideInfo() : showInfo()
231
+ }
232
+
233
+ const handleScroll = () => {
234
+ if (isVisible.value) {
235
+ hideInfo()
236
+ }
237
+ updatePosition()
238
+ }
239
+
240
+ const isIconInView = () => {
241
+ if (!icon.value) return false
242
+ const rect = icon.value.getBoundingClientRect()
243
+ return (
244
+ rect.top >= 0 &&
245
+ rect.left >= 0 &&
246
+ rect.bottom <=
247
+ (window.innerHeight || document.documentElement.clientHeight) &&
248
+ rect.right <=
249
+ (window.innerWidth || document.documentElement.clientWidth)
250
+ )
251
+ }
252
+
253
+ const updatePosition = async () => {
254
+ if (infoBox.value && textContent.value) {
255
+ await nextTick()
256
+
257
+ if (isIconInView()) {
258
+ const contentWidth = textContent.value.offsetWidth
259
+ infoBoxWidth.value = Math.min(
260
+ Math.max(contentWidth, 200), // Set a minimum width of 200px
261
+ parseInt(props.maxWidth, 10)
262
+ )
263
+ infoBoxHeight.value = infoBox.value.$el.offsetHeight
264
+ boxStyle.value = calculatePosition(infoBoxWidth.value)
265
+
266
+ // Now make it visible if it should be
267
+ if (isVisible.value) {
268
+ infoBox.value.$el.style.visibility = 'visible'
269
+ }
270
+ } else if (isVisible.value) {
271
+ hideInfo()
272
+ }
273
+ }
274
+ }
275
+
276
+ const onImageLoad = () => {
277
+ if (infoImage.value) {
278
+ infoBoxHeight.value = infoBox.value.$el.offsetHeight
279
+ updatePosition()
280
+ }
281
+ }
282
+
283
+ const handleClickOutside = (event) => {
284
+ if (props.openTrigger === 'onClick' && isVisible.value) {
285
+ const clickedElement = event.target
286
+ if (
287
+ infoBox.value &&
288
+ !infoBox.value.$el.contains(clickedElement) &&
289
+ !icon.value.contains(clickedElement)
290
+ ) {
291
+ hideInfo()
292
+ }
293
+ }
294
+ }
295
+
296
+ onMounted(() => {
297
+ window.addEventListener('scroll', handleScroll, { passive: true })
298
+ window.addEventListener('resize', updatePosition)
299
+ document.addEventListener('scroll', handleScroll, {
300
+ passive: true,
301
+ capture: true,
302
+ })
303
+ document.addEventListener('click', handleClickOutside)
304
+ })
305
+
306
+ onUnmounted(() => {
307
+ window.removeEventListener('scroll', handleScroll)
308
+ window.removeEventListener('resize', updatePosition)
309
+ document.removeEventListener('scroll', handleScroll, { capture: true })
310
+ document.removeEventListener('click', handleClickOutside)
311
+ })
312
+
313
+ watch(isVisible, (newValue) => {
314
+ if (newValue) {
315
+ updatePosition()
316
+ }
317
+ })
318
+
159
319
  return {
160
- showInfo: false,
320
+ isVisible,
321
+ boxStyle,
322
+ arrowStyle,
323
+ showInfo,
324
+ hideInfo,
325
+ toggleInfo,
326
+ container,
327
+ icon,
328
+ infoBox,
329
+ textContent,
330
+ infoImage,
331
+ infoBoxWidth,
332
+ infoBoxHeight,
333
+ wrapperStyle,
334
+ textStyle,
335
+ onImageLoad,
161
336
  }
162
337
  },
163
338
  computed: {
164
339
  iconColor() {
165
- return theme.colors.mediumGray
166
- },
167
- halfComputedTextInfoWidth() {
168
- return parseInt(this.width) / 2
169
- },
170
- },
171
- methods: {
172
- toggleShowInfo() {
173
- this.showInfo = !this.showInfo
174
-
175
- if (this.showInfo) {
176
- document.addEventListener('click', this.clickOutside)
177
- } else {
178
- document.removeEventListener('click', this.clickOutside)
179
- }
180
- },
181
- clickOutside(event) {
182
- if (this.$el.contains(event.target)) {
183
- return
184
- }
185
- this.toggleShowInfo()
340
+ return this.buttonType === 'error'
341
+ ? theme.colors.red
342
+ : theme.colors.mediumGray
186
343
  },
187
344
  },
188
345
  }