@eturnity/eturnity_reusable_components 9.28.3-panel-placement.0 → 9.31.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/package.json +1 -1
- package/src/assets/svgIcons/outline_tool.svg +10 -10
- package/src/components/buttons/buttonIcon/buttonIcon.spec.js +0 -40
- package/src/components/buttons/buttonIcon/index.vue +0 -18
- package/src/components/inputs/inputNumber/index.vue +0 -10
- package/src/components/inputs/switchField/index.vue +14 -23
- package/src/components/inputs/switchField/switchField.spec.js +3 -3
- package/src/components/inputs/toggle/index.vue +1 -22
- package/src/components/inputs/toggle/toggle.spec.js +9 -9
- package/src/components/navigationSideMenu/index.spec.js +145 -0
- package/src/components/navigationSideMenu/index.vue +73 -7
- package/src/components/navigationSideMenu/navigationSideMenu.stories.js +133 -0
- package/src/components/spinner/index.vue +1 -2
- package/src/assets/svgIcons/broom.svg +0 -6
- package/src/assets/svgIcons/length_2d_vertical.svg +0 -5
- package/src/assets/svgIcons/obstacle_flat_top.svg +0 -3
- package/src/assets/svgIcons/obstacle_sloped_top.svg +0 -3
- package/src/assets/tests/__mocks__/fileMock.js +0 -1
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
</svg>
|
|
2
|
+
<rect x="2.80078" y="4.06415" width="8" height="1.6" transform="rotate(90 2.80078 4.06415)" fill="white"/>
|
|
3
|
+
<rect x="7.00195" y="3.8642" width="4" height="1.6" transform="rotate(-180 7.00195 3.8642)" fill="white"/>
|
|
4
|
+
<path d="M8.5 3.07846L6.00098 0.935599L6.00098 5.22131L8.5 3.07846Z" fill="white"/>
|
|
5
|
+
<rect x="11.002" y="13.8643" width="8" height="1.6" transform="rotate(-180 11.002 13.8643)" fill="white"/>
|
|
6
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.06409C2.55228 4.06409 3 3.61637 3 3.06409C3 2.5118 2.55228 2.06409 2 2.06409C1.44772 2.06409 1 2.5118 1 3.06409C1 3.61637 1.44772 4.06409 2 4.06409ZM2 5.06409C3.10457 5.06409 4 4.16866 4 3.06409C4 1.95952 3.10457 1.06409 2 1.06409C0.895431 1.06409 0 1.95952 0 3.06409C0 4.16866 0.895431 5.06409 2 5.06409Z" fill="white"/>
|
|
7
|
+
<rect x="12.7988" y="4.06433" width="8" height="1.6" transform="rotate(90 12.7988 4.06433)" fill="white"/>
|
|
8
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.002 4.06421C12.5542 4.06421 13.002 3.61649 13.002 3.06421C13.002 2.51192 12.5542 2.06421 12.002 2.06421C11.4497 2.06421 11.002 2.51192 11.002 3.06421C11.002 3.61649 11.4497 4.06421 12.002 4.06421ZM12.002 5.06421C13.1065 5.06421 14.002 4.16878 14.002 3.06421C14.002 1.95964 13.1065 1.06421 12.002 1.06421C10.8974 1.06421 10.002 1.95964 10.002 3.06421C10.002 4.16878 10.8974 5.06421 12.002 5.06421Z" fill="white"/>
|
|
9
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.002 14.0643C12.5542 14.0643 13.002 13.6166 13.002 13.0643C13.002 12.512 12.5542 12.0643 12.002 12.0643C11.4497 12.0643 11.002 12.512 11.002 13.0643C11.002 13.6166 11.4497 14.0643 12.002 14.0643ZM12.002 15.0643C13.1065 15.0643 14.002 14.1688 14.002 13.0643C14.002 11.9597 13.1065 11.0643 12.002 11.0643C10.8974 11.0643 10.002 11.9597 10.002 13.0643C10.002 14.1688 10.8974 15.0643 12.002 15.0643Z" fill="white"/>
|
|
10
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 14.0644C2.55228 14.0644 3 13.6167 3 13.0644C3 12.5121 2.55228 12.0644 2 12.0644C1.44772 12.0644 1 12.5121 1 13.0644C1 13.6167 1.44772 14.0644 2 14.0644ZM2 15.0644C3.10457 15.0644 4 14.169 4 13.0644C4 11.9598 3.10457 11.0644 2 11.0644C0.895431 11.0644 0 11.9598 0 13.0644C0 14.169 0.895431 15.0644 2 15.0644Z" fill="white"/>
|
|
11
|
+
</svg>
|
|
@@ -157,46 +157,6 @@ describe('ButtonIcon.vue', () => {
|
|
|
157
157
|
}
|
|
158
158
|
})
|
|
159
159
|
|
|
160
|
-
it('replaces the icon with a spinner while loading', async () => {
|
|
161
|
-
expect(wrapper.find('[data-test-id="spinner_container"]').exists()).toBe(
|
|
162
|
-
false
|
|
163
|
-
)
|
|
164
|
-
expect(wrapper.findComponent({ name: 'Icon' }).exists()).toBe(true)
|
|
165
|
-
|
|
166
|
-
await wrapper.setProps({ isLoading: true })
|
|
167
|
-
expect(wrapper.find('[data-test-id="spinner_container"]').exists()).toBe(
|
|
168
|
-
true
|
|
169
|
-
)
|
|
170
|
-
expect(wrapper.findComponent({ name: 'Icon' }).exists()).toBe(false)
|
|
171
|
-
|
|
172
|
-
await wrapper.setProps({ isLoading: false })
|
|
173
|
-
expect(wrapper.find('[data-test-id="spinner_container"]').exists()).toBe(
|
|
174
|
-
false
|
|
175
|
-
)
|
|
176
|
-
expect(wrapper.findComponent({ name: 'Icon' }).props('name')).toBe(
|
|
177
|
-
'settings'
|
|
178
|
-
)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('keeps rendering the text while loading', async () => {
|
|
182
|
-
await wrapper.setProps({ isLoading: true })
|
|
183
|
-
expect(wrapper.find('[data-test-id="button-icon-text"]').text()).toBe(
|
|
184
|
-
'Click Me'
|
|
185
|
-
)
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('renders a white spinner on types with white button text', async () => {
|
|
189
|
-
await wrapper.setProps({ isLoading: true, type: 'primary' })
|
|
190
|
-
expect(wrapper.findComponent({ name: 'NewSpinner' }).props('isWhite')).toBe(
|
|
191
|
-
true
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
await wrapper.setProps({ type: 'secondary' })
|
|
195
|
-
expect(wrapper.findComponent({ name: 'NewSpinner' }).props('isWhite')).toBe(
|
|
196
|
-
false
|
|
197
|
-
)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
160
|
it('renders caret based on showCaret prop', async () => {
|
|
201
161
|
await wrapper.setProps({ showCaret: true })
|
|
202
162
|
let caret = wrapper.find('[data-test-id="button-icon-caret"]')
|
|
@@ -28,9 +28,7 @@
|
|
|
28
28
|
:type="type"
|
|
29
29
|
:variant="variant"
|
|
30
30
|
>
|
|
31
|
-
<RCSpinner v-if="isLoading" :is-white="isSpinnerWhite" size="tiny" />
|
|
32
31
|
<Icon
|
|
33
|
-
v-else
|
|
34
32
|
:color="getIconColor"
|
|
35
33
|
:disable-hover="disableIconHover"
|
|
36
34
|
:fill-type="fillType"
|
|
@@ -85,11 +83,9 @@
|
|
|
85
83
|
// iconName="external_icon"
|
|
86
84
|
// fontColor="white"
|
|
87
85
|
// fontSize="12px"
|
|
88
|
-
// :isLoading="false" // swaps the icon for a spinner
|
|
89
86
|
// />
|
|
90
87
|
|
|
91
88
|
import Icon from '../../icon'
|
|
92
|
-
import RCSpinner from '../../spinner'
|
|
93
89
|
import styled from 'vue3-styled-components'
|
|
94
90
|
import theme from '../../../assets/theme'
|
|
95
91
|
|
|
@@ -423,7 +419,6 @@
|
|
|
423
419
|
ButtonWrapper,
|
|
424
420
|
IconContainer,
|
|
425
421
|
Icon,
|
|
426
|
-
RCSpinner,
|
|
427
422
|
NumberContainer,
|
|
428
423
|
NumberText,
|
|
429
424
|
CountBadge,
|
|
@@ -546,24 +541,11 @@
|
|
|
546
541
|
default: false,
|
|
547
542
|
type: Boolean,
|
|
548
543
|
},
|
|
549
|
-
isLoading: {
|
|
550
|
-
required: false,
|
|
551
|
-
default: false,
|
|
552
|
-
type: Boolean,
|
|
553
|
-
},
|
|
554
544
|
},
|
|
555
545
|
computed: {
|
|
556
546
|
theme() {
|
|
557
547
|
return theme
|
|
558
548
|
},
|
|
559
|
-
// The spinner only offers white/purple, so pick whichever contrasts with
|
|
560
|
-
// the button background the same way the label already does.
|
|
561
|
-
isSpinnerWhite() {
|
|
562
|
-
return (
|
|
563
|
-
this.theme.mainButton[this.appTheme][this.type][this.variant].default
|
|
564
|
-
.textColor === this.theme.colors.white
|
|
565
|
-
)
|
|
566
|
-
},
|
|
567
549
|
getIconColor() {
|
|
568
550
|
if (this.iconColor) return this.iconColor
|
|
569
551
|
if (this.isDisabled) {
|
|
@@ -484,7 +484,6 @@
|
|
|
484
484
|
display: flex;
|
|
485
485
|
gap: 10px;
|
|
486
486
|
align-items: center;
|
|
487
|
-
padding: ${(props) => (props.isInteractive ? '0 4px' : '0')};
|
|
488
487
|
cursor: ${(props) => (props.isInteractive ? 'ew-resize' : 'auto')};
|
|
489
488
|
border: ${(props) =>
|
|
490
489
|
props.alignItems == 'vertical'
|
|
@@ -730,11 +729,6 @@
|
|
|
730
729
|
required: false,
|
|
731
730
|
default: true,
|
|
732
731
|
},
|
|
733
|
-
undefinedState: {
|
|
734
|
-
type: Boolean,
|
|
735
|
-
required: false,
|
|
736
|
-
default: false,
|
|
737
|
-
},
|
|
738
732
|
allowNegative: {
|
|
739
733
|
type: Boolean,
|
|
740
734
|
required: false,
|
|
@@ -850,7 +844,6 @@
|
|
|
850
844
|
},
|
|
851
845
|
computed: {
|
|
852
846
|
displayedPlaceholder() {
|
|
853
|
-
if (this.undefinedState) return '-'
|
|
854
847
|
if (this.placeholder) return this.placeholder
|
|
855
848
|
if (this.defaultNumber)
|
|
856
849
|
return `${this.defaultNumber}${this.hasSpaceBetweenUnit ? ' ' : ''}${
|
|
@@ -1128,9 +1121,6 @@
|
|
|
1128
1121
|
}
|
|
1129
1122
|
},
|
|
1130
1123
|
formatWithCurrency(value) {
|
|
1131
|
-
if (this.undefinedState && !this.isFocused) {
|
|
1132
|
-
return '-'
|
|
1133
|
-
}
|
|
1134
1124
|
let adjustedMinValue =
|
|
1135
1125
|
value || value === 0
|
|
1136
1126
|
? value
|
|
@@ -31,16 +31,15 @@
|
|
|
31
31
|
:border-color="borderColor"
|
|
32
32
|
:color-mode="colorMode"
|
|
33
33
|
:data-id="item.hasOwnProperty('dataId') ? item.dataId : ''"
|
|
34
|
-
:disabled="!!item.disabled"
|
|
35
34
|
:height="height"
|
|
36
35
|
:inactive-border-color="inactiveBorderColor"
|
|
37
36
|
:inactive-color="inactiveColor"
|
|
38
|
-
:is-active="
|
|
37
|
+
:is-active="value == item.value"
|
|
39
38
|
:primary-color="primaryColor"
|
|
40
39
|
:secondary-color="secondaryColor"
|
|
41
40
|
:size="size"
|
|
42
41
|
:width="item.width || 'auto'"
|
|
43
|
-
@click="selectItem(item)"
|
|
42
|
+
@click="selectItem(item.value)"
|
|
44
43
|
>
|
|
45
44
|
<OptionIconContainer v-if="item.icon">
|
|
46
45
|
<RCIcon
|
|
@@ -146,7 +145,6 @@
|
|
|
146
145
|
height: String,
|
|
147
146
|
borderColor: String,
|
|
148
147
|
inactiveBorderColor: String,
|
|
149
|
-
disabled: Boolean,
|
|
150
148
|
}
|
|
151
149
|
const SwitchOption = styled('div', optionAttrs)`
|
|
152
150
|
color: ${(props) =>
|
|
@@ -169,19 +167,17 @@
|
|
|
169
167
|
props.height
|
|
170
168
|
? props.height
|
|
171
169
|
: props.size === 'medium'
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
? '24px'
|
|
171
|
+
: props.size === 'small'
|
|
172
|
+
? '16px'
|
|
173
|
+
: '24px'};
|
|
176
174
|
padding: 0 10px;
|
|
177
175
|
margin-right: -1px;
|
|
178
176
|
transition: all 0.1s ease-in-out;
|
|
179
177
|
overflow: hidden;
|
|
180
178
|
gap: 5px;
|
|
181
|
-
opacity: ${(props) => (props.disabled ? 0.4 : 1)};
|
|
182
|
-
pointer-events: ${(props) => (props.disabled ? 'none' : 'auto')};
|
|
183
179
|
& :hover {
|
|
184
|
-
cursor:
|
|
180
|
+
cursor: pointer;
|
|
185
181
|
}
|
|
186
182
|
&:first-child {
|
|
187
183
|
border-radius: 4px 0 0 4px;
|
|
@@ -268,11 +264,6 @@
|
|
|
268
264
|
default: false,
|
|
269
265
|
type: Boolean,
|
|
270
266
|
},
|
|
271
|
-
undefinedState: {
|
|
272
|
-
required: false,
|
|
273
|
-
default: false,
|
|
274
|
-
type: Boolean,
|
|
275
|
-
},
|
|
276
267
|
},
|
|
277
268
|
data() {
|
|
278
269
|
return {
|
|
@@ -291,19 +282,19 @@
|
|
|
291
282
|
this.borderColor = this.secondaryColor
|
|
292
283
|
this.inactiveBorderColor = theme.semanticColors.teal[600]
|
|
293
284
|
} else {
|
|
294
|
-
this.primaryColor = theme.semanticColors.teal[
|
|
295
|
-
this.secondaryColor = theme.semanticColors.
|
|
296
|
-
this.inactiveColor = theme.semanticColors.
|
|
285
|
+
this.primaryColor = theme.semanticColors.teal[50]
|
|
286
|
+
this.secondaryColor = theme.semanticColors.teal[800]
|
|
287
|
+
this.inactiveColor = theme.semanticColors.teal[50]
|
|
297
288
|
this.borderColor = this.secondaryColor
|
|
298
|
-
this.inactiveBorderColor = theme.semanticColors.teal[
|
|
289
|
+
this.inactiveBorderColor = theme.semanticColors.teal[50]
|
|
299
290
|
}
|
|
300
291
|
},
|
|
301
292
|
methods: {
|
|
302
|
-
selectItem(
|
|
303
|
-
if (this.disabled
|
|
293
|
+
selectItem(value) {
|
|
294
|
+
if (this.disabled) {
|
|
304
295
|
return
|
|
305
296
|
}
|
|
306
|
-
this.$emit('on-switch-change',
|
|
297
|
+
this.$emit('on-switch-change', value)
|
|
307
298
|
},
|
|
308
299
|
},
|
|
309
300
|
}
|
|
@@ -65,9 +65,9 @@ describe('SwitchField', () => {
|
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
it('renders the info text only when a message is provided', () => {
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
)
|
|
68
|
+
expect(mountSwitch({ label: 'Mode' }).findComponent(InfoText).exists()).toBe(
|
|
69
|
+
false
|
|
70
|
+
)
|
|
71
71
|
expect(
|
|
72
72
|
mountSwitch({ label: 'Mode', infoTextMessage: 'Helpful hint' })
|
|
73
73
|
.findComponent(InfoText)
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
role="checkbox"
|
|
33
33
|
:size="size"
|
|
34
34
|
tabindex="0"
|
|
35
|
-
:undefined-state="undefinedState"
|
|
36
35
|
@keydown.space.prevent="onToggleChange"
|
|
37
36
|
>
|
|
38
37
|
<ToggleBackground
|
|
@@ -42,7 +41,6 @@
|
|
|
42
41
|
data-test-id="toggle_background"
|
|
43
42
|
:disabled="disabled"
|
|
44
43
|
:is-checked="isChecked"
|
|
45
|
-
:undefined-state="undefinedState"
|
|
46
44
|
/>
|
|
47
45
|
<ToggleDot
|
|
48
46
|
data-test-id="toggle_dot"
|
|
@@ -51,7 +49,6 @@
|
|
|
51
49
|
:size="size"
|
|
52
50
|
:toggle-color="toggleColor"
|
|
53
51
|
:toggle-margin="toggleMargin"
|
|
54
|
-
:undefined-state="undefinedState"
|
|
55
52
|
/>
|
|
56
53
|
</ToggleWrapper>
|
|
57
54
|
<LabelContainer
|
|
@@ -128,7 +125,6 @@
|
|
|
128
125
|
disabled: Boolean,
|
|
129
126
|
backgroundColor: String,
|
|
130
127
|
isChecked: Boolean,
|
|
131
|
-
undefinedState: Boolean,
|
|
132
128
|
}
|
|
133
129
|
const LabelText = styled('div', toggleAttrs)`
|
|
134
130
|
color: ${(props) =>
|
|
@@ -144,8 +140,6 @@
|
|
|
144
140
|
border: ${(props) =>
|
|
145
141
|
props.disabled
|
|
146
142
|
? '1px solid ' + props.theme.colors.disabled
|
|
147
|
-
: props.undefinedState
|
|
148
|
-
? '1px solid ' + props.theme.colors.grey3
|
|
149
143
|
: props.isChecked
|
|
150
144
|
? props.backgroundColor
|
|
151
145
|
? '1px solid ' + props.backgroundColor
|
|
@@ -181,7 +175,6 @@
|
|
|
181
175
|
backgroundColor: String,
|
|
182
176
|
isChecked: Boolean,
|
|
183
177
|
disabled: Boolean,
|
|
184
|
-
undefinedState: Boolean,
|
|
185
178
|
}
|
|
186
179
|
const ToggleBackground = styled('span', backgroundAttrs)`
|
|
187
180
|
display: block;
|
|
@@ -191,8 +184,6 @@
|
|
|
191
184
|
background-color: ${(props) =>
|
|
192
185
|
props.disabled
|
|
193
186
|
? props.theme.colors.grey5
|
|
194
|
-
: props.undefinedState
|
|
195
|
-
? props.theme.colors.grey3
|
|
196
187
|
: props.isChecked
|
|
197
188
|
? props.backgroundColor
|
|
198
189
|
? props.backgroundColor
|
|
@@ -207,7 +198,6 @@
|
|
|
207
198
|
size: String,
|
|
208
199
|
disabled: Boolean,
|
|
209
200
|
toggleMargin: String,
|
|
210
|
-
undefinedState: Boolean,
|
|
211
201
|
}
|
|
212
202
|
const ToggleDot = styled('span', toggleProps)`
|
|
213
203
|
position: absolute;
|
|
@@ -243,13 +233,7 @@
|
|
|
243
233
|
border-radius: 100%;
|
|
244
234
|
transition: transform 0.4s ease;
|
|
245
235
|
transform: ${(props) =>
|
|
246
|
-
props.
|
|
247
|
-
? props.size === 'medium'
|
|
248
|
-
? 'translateX(15px)'
|
|
249
|
-
: props.size === 'small'
|
|
250
|
-
? 'translateX(6px)'
|
|
251
|
-
: 'translateX(15px)'
|
|
252
|
-
: props.isChecked
|
|
236
|
+
props.isChecked
|
|
253
237
|
? props.size === 'medium'
|
|
254
238
|
? 'translateX(25px)'
|
|
255
239
|
: props.size === 'small'
|
|
@@ -352,11 +336,6 @@
|
|
|
352
336
|
default: null,
|
|
353
337
|
required: false,
|
|
354
338
|
},
|
|
355
|
-
undefinedState: {
|
|
356
|
-
type: Boolean,
|
|
357
|
-
default: false,
|
|
358
|
-
required: false,
|
|
359
|
-
},
|
|
360
339
|
},
|
|
361
340
|
methods: {
|
|
362
341
|
onToggleChange() {
|
|
@@ -77,16 +77,16 @@ describe('RCToggle', () => {
|
|
|
77
77
|
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
78
78
|
false
|
|
79
79
|
)
|
|
80
|
-
expect(
|
|
81
|
-
|
|
82
|
-
)
|
|
80
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
81
|
+
false
|
|
82
|
+
)
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
it('renders the label on the right by default', () => {
|
|
86
86
|
const wrapper = mountToggle({ label: 'My Label' })
|
|
87
|
-
expect(
|
|
88
|
-
|
|
89
|
-
)
|
|
87
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
88
|
+
true
|
|
89
|
+
)
|
|
90
90
|
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
91
91
|
false
|
|
92
92
|
)
|
|
@@ -98,9 +98,9 @@ describe('RCToggle', () => {
|
|
|
98
98
|
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
99
99
|
true
|
|
100
100
|
)
|
|
101
|
-
expect(
|
|
102
|
-
|
|
103
|
-
)
|
|
101
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
102
|
+
false
|
|
103
|
+
)
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
it('renders the info text only when infoTextMessage is provided', () => {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import NavigationSideMenu from '@/components/navigationSideMenu'
|
|
3
|
+
|
|
4
|
+
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
5
|
+
// need to mock this due to how jest handles import.meta
|
|
6
|
+
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
const menuData = [
|
|
10
|
+
{
|
|
11
|
+
groupName: 'Project',
|
|
12
|
+
children: [
|
|
13
|
+
{ id: 'technologies', name: 'Technologies', dataId: 'nav_technologies' },
|
|
14
|
+
{ id: 'finance', name: 'Finance', dataId: 'nav_finance' },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
groupName: 'Workflow',
|
|
19
|
+
children: [
|
|
20
|
+
{
|
|
21
|
+
id: 'workflow_template',
|
|
22
|
+
name: 'Workflow template',
|
|
23
|
+
dataId: 'nav_workflow_template',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const mountMenu = (props = {}) =>
|
|
30
|
+
mount(NavigationSideMenu, { props: { menuData, ...props } })
|
|
31
|
+
|
|
32
|
+
const item = (wrapper, dataId) => wrapper.find(`[data-id="${dataId}"]`)
|
|
33
|
+
const section = (wrapper, key) => wrapper.find(`[data-id="nav_section_${key}"]`)
|
|
34
|
+
|
|
35
|
+
describe('navigationSideMenu/index.vue', () => {
|
|
36
|
+
describe('default state', () => {
|
|
37
|
+
it('renders a header for each non-empty group', () => {
|
|
38
|
+
const wrapper = mountMenu()
|
|
39
|
+
|
|
40
|
+
expect(section(wrapper, 'Project').exists()).toBe(true)
|
|
41
|
+
expect(section(wrapper, 'Workflow').exists()).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('shows every group item when nothing is collapsed', () => {
|
|
45
|
+
const wrapper = mountMenu()
|
|
46
|
+
|
|
47
|
+
expect(item(wrapper, 'nav_technologies').exists()).toBe(true)
|
|
48
|
+
expect(item(wrapper, 'nav_finance').exists()).toBe(true)
|
|
49
|
+
expect(item(wrapper, 'nav_workflow_template').exists()).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('reports no group as collapsed initially', () => {
|
|
53
|
+
const wrapper = mountMenu()
|
|
54
|
+
|
|
55
|
+
expect(wrapper.vm.isGroupCollapsed('Project')).toBe(false)
|
|
56
|
+
expect(wrapper.vm.isGroupCollapsed('Workflow')).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('toggling a section', () => {
|
|
61
|
+
it("hides that section's items when its header is clicked, and shows them again on a second click", async () => {
|
|
62
|
+
const wrapper = mountMenu()
|
|
63
|
+
|
|
64
|
+
await section(wrapper, 'Project').trigger('click')
|
|
65
|
+
expect(item(wrapper, 'nav_technologies').exists()).toBe(false)
|
|
66
|
+
expect(item(wrapper, 'nav_finance').exists()).toBe(false)
|
|
67
|
+
|
|
68
|
+
await section(wrapper, 'Project').trigger('click')
|
|
69
|
+
expect(item(wrapper, 'nav_technologies').exists()).toBe(true)
|
|
70
|
+
expect(item(wrapper, 'nav_finance').exists()).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('collapses sections independently — collapsing one leaves the others expanded', async () => {
|
|
74
|
+
const wrapper = mountMenu()
|
|
75
|
+
|
|
76
|
+
await section(wrapper, 'Project').trigger('click')
|
|
77
|
+
|
|
78
|
+
expect(item(wrapper, 'nav_technologies').exists()).toBe(false)
|
|
79
|
+
expect(item(wrapper, 'nav_workflow_template').exists()).toBe(true)
|
|
80
|
+
expect(wrapper.vm.isGroupCollapsed('Project')).toBe(true)
|
|
81
|
+
expect(wrapper.vm.isGroupCollapsed('Workflow')).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('emits section-collapse-toggle with the key and post-toggle collapsed state', async () => {
|
|
85
|
+
const wrapper = mountMenu()
|
|
86
|
+
|
|
87
|
+
await section(wrapper, 'Project').trigger('click')
|
|
88
|
+
await section(wrapper, 'Project').trigger('click')
|
|
89
|
+
|
|
90
|
+
const events = wrapper.emitted('section-collapse-toggle')
|
|
91
|
+
expect(events).toHaveLength(2)
|
|
92
|
+
expect(events[0]).toEqual([{ key: 'Project', collapsed: true }])
|
|
93
|
+
expect(events[1]).toEqual([{ key: 'Project', collapsed: false }])
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('groupKey', () => {
|
|
98
|
+
it('prefers an explicit id over the group name', () => {
|
|
99
|
+
const wrapper = mountMenu()
|
|
100
|
+
|
|
101
|
+
expect(wrapper.vm.groupKey({ id: 'a', groupName: 'B' }, 0)).toBe('a')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('falls back to the group name when there is no id', () => {
|
|
105
|
+
const wrapper = mountMenu()
|
|
106
|
+
|
|
107
|
+
expect(wrapper.vm.groupKey({ groupName: 'B' }, 0)).toBe('B')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('falls back to the index (as a string) when neither id nor name is present', () => {
|
|
111
|
+
const wrapper = mountMenu()
|
|
112
|
+
|
|
113
|
+
expect(wrapper.vm.groupKey({}, 3)).toBe('3')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('edge cases', () => {
|
|
118
|
+
it('mounts without error and renders no sections when menuData is empty', () => {
|
|
119
|
+
const wrapper = mount(NavigationSideMenu, { props: { menuData: [] } })
|
|
120
|
+
|
|
121
|
+
expect(wrapper.find('[data-id^="nav_section_"]').exists()).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('treats groups that share a groupName as one section (name is the key)', async () => {
|
|
125
|
+
const dupData = [
|
|
126
|
+
{
|
|
127
|
+
groupName: 'Other',
|
|
128
|
+
children: [{ id: 'a', name: 'A', dataId: 'nav_a' }],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
groupName: 'Other',
|
|
132
|
+
children: [{ id: 'b', name: 'B', dataId: 'nav_b' }],
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
const wrapper = mount(NavigationSideMenu, {
|
|
136
|
+
props: { menuData: dupData },
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
await wrapper.findAll('[data-id="nav_section_Other"]')[0].trigger('click')
|
|
140
|
+
|
|
141
|
+
expect(item(wrapper, 'nav_a').exists()).toBe(false)
|
|
142
|
+
expect(item(wrapper, 'nav_b').exists()).toBe(false)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -120,8 +120,31 @@
|
|
|
120
120
|
v-if="group.children?.length"
|
|
121
121
|
:is-collapsed="isCollapsed"
|
|
122
122
|
>
|
|
123
|
-
<
|
|
124
|
-
|
|
123
|
+
<GroupHeader
|
|
124
|
+
v-if="!isCollapsed"
|
|
125
|
+
:data-id="`nav_section_${groupKey(group, groupIdx)}`"
|
|
126
|
+
:data-qa-id="`nav_section_${groupKey(group, groupIdx)}`"
|
|
127
|
+
@click="toggleGroup(groupKey(group, groupIdx))"
|
|
128
|
+
>
|
|
129
|
+
<GroupName>{{ group.groupName }}</GroupName>
|
|
130
|
+
<GroupChevron>
|
|
131
|
+
<IconComponent
|
|
132
|
+
:color="theme.semanticColors.grey[700]"
|
|
133
|
+
:hovered-color="theme.semanticColors.purple[500]"
|
|
134
|
+
:name="
|
|
135
|
+
isGroupCollapsed(groupKey(group, groupIdx))
|
|
136
|
+
? 'chevron_down'
|
|
137
|
+
: 'chevron_up'
|
|
138
|
+
"
|
|
139
|
+
size="10px"
|
|
140
|
+
/>
|
|
141
|
+
</GroupChevron>
|
|
142
|
+
</GroupHeader>
|
|
143
|
+
<ItemList
|
|
144
|
+
v-if="
|
|
145
|
+
isCollapsed || !isGroupCollapsed(groupKey(group, groupIdx))
|
|
146
|
+
"
|
|
147
|
+
>
|
|
125
148
|
<template
|
|
126
149
|
v-for="(item, itemIdx) in group.children"
|
|
127
150
|
:key="item.id ?? itemIdx"
|
|
@@ -137,7 +160,7 @@
|
|
|
137
160
|
:is-disabled="item.isDisabled"
|
|
138
161
|
@click="
|
|
139
162
|
!item.isDisabled &&
|
|
140
|
-
|
|
163
|
+
$emit('on-menu-item-click', { item })
|
|
141
164
|
"
|
|
142
165
|
>
|
|
143
166
|
<IconCell :is-collapsed="isCollapsed">
|
|
@@ -174,7 +197,7 @@
|
|
|
174
197
|
type="tertiary"
|
|
175
198
|
@click="
|
|
176
199
|
!item.isDisabled &&
|
|
177
|
-
|
|
200
|
+
$emit('on-menu-item-click', { item })
|
|
178
201
|
"
|
|
179
202
|
/>
|
|
180
203
|
</template>
|
|
@@ -244,7 +267,7 @@
|
|
|
244
267
|
:is-disabled="sub.isDisabled"
|
|
245
268
|
@click="
|
|
246
269
|
!sub.isDisabled &&
|
|
247
|
-
|
|
270
|
+
$emit('on-menu-item-click', { item: sub })
|
|
248
271
|
"
|
|
249
272
|
>
|
|
250
273
|
<span>{{ sub.name }}</span>
|
|
@@ -307,8 +330,8 @@
|
|
|
307
330
|
p.isCollapsed
|
|
308
331
|
? `${NAV_SIDEBAR_COLLAPSED_OUTER_WIDTH_PX}px`
|
|
309
332
|
: p.expandedWidth
|
|
310
|
-
|
|
311
|
-
|
|
333
|
+
? `${p.expandedWidth}px`
|
|
334
|
+
: 'max-content'};
|
|
312
335
|
min-width: ${(p) =>
|
|
313
336
|
p.isCollapsed ? `${NAV_SIDEBAR_COLLAPSED_OUTER_WIDTH_PX}px` : '0'};
|
|
314
337
|
border-right: 1px solid ${(p) => p.theme.semanticColors.grey[300]};
|
|
@@ -418,6 +441,18 @@
|
|
|
418
441
|
padding: ${(p) => (p.isCollapsed ? '10px 8px' : '10px 16px')};
|
|
419
442
|
`
|
|
420
443
|
|
|
444
|
+
const GroupHeader = styled.div`
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
justify-content: space-between;
|
|
448
|
+
gap: 6px;
|
|
449
|
+
cursor: pointer;
|
|
450
|
+
|
|
451
|
+
&:hover > div:first-child {
|
|
452
|
+
color: ${(p) => p.theme.semanticColors.purple[500]};
|
|
453
|
+
}
|
|
454
|
+
`
|
|
455
|
+
|
|
421
456
|
const GroupName = styled.div`
|
|
422
457
|
font-size: 10px;
|
|
423
458
|
font-weight: 400;
|
|
@@ -426,6 +461,12 @@
|
|
|
426
461
|
line-height: 150%;
|
|
427
462
|
`
|
|
428
463
|
|
|
464
|
+
const GroupChevron = styled.div`
|
|
465
|
+
display: flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
flex-shrink: 0;
|
|
468
|
+
`
|
|
469
|
+
|
|
429
470
|
const ItemList = styled.div`
|
|
430
471
|
display: flex;
|
|
431
472
|
flex-direction: column;
|
|
@@ -758,7 +799,9 @@
|
|
|
758
799
|
ScrollbarThumb,
|
|
759
800
|
GroupsList,
|
|
760
801
|
GroupSection,
|
|
802
|
+
GroupHeader,
|
|
761
803
|
GroupName,
|
|
804
|
+
GroupChevron,
|
|
762
805
|
ItemList,
|
|
763
806
|
MenuItem,
|
|
764
807
|
IconCell,
|
|
@@ -837,6 +880,7 @@
|
|
|
837
880
|
'on-menu-item-click',
|
|
838
881
|
'on-footer-button-click',
|
|
839
882
|
'sidebar-dimensions',
|
|
883
|
+
'section-collapse-toggle',
|
|
840
884
|
],
|
|
841
885
|
data() {
|
|
842
886
|
return {
|
|
@@ -844,6 +888,7 @@
|
|
|
844
888
|
isCollapsed: false,
|
|
845
889
|
responsibleSelectSurfaceOpen: false,
|
|
846
890
|
openDropdownId: null,
|
|
891
|
+
collapsedGroups: {},
|
|
847
892
|
isWrapperHovered: false,
|
|
848
893
|
expandedWidth: null,
|
|
849
894
|
scrollThumbStyle: {},
|
|
@@ -1120,6 +1165,27 @@
|
|
|
1120
1165
|
this.openDropdownId = this.openDropdownId === id ? null : id
|
|
1121
1166
|
}
|
|
1122
1167
|
},
|
|
1168
|
+
groupKey(group, idx) {
|
|
1169
|
+
return group.id ?? group.groupName ?? String(idx)
|
|
1170
|
+
},
|
|
1171
|
+
isGroupCollapsed(key) {
|
|
1172
|
+
return !!this.collapsedGroups[key]
|
|
1173
|
+
},
|
|
1174
|
+
toggleGroup(key) {
|
|
1175
|
+
if (this.collapsedGroups[key]) {
|
|
1176
|
+
delete this.collapsedGroups[key]
|
|
1177
|
+
} else {
|
|
1178
|
+
this.collapsedGroups[key] = true
|
|
1179
|
+
}
|
|
1180
|
+
this.$emit('section-collapse-toggle', {
|
|
1181
|
+
key,
|
|
1182
|
+
collapsed: this.isGroupCollapsed(key),
|
|
1183
|
+
})
|
|
1184
|
+
this.$nextTick(() => {
|
|
1185
|
+
this.updateScrollThumb()
|
|
1186
|
+
this.updateExpandedWidth()
|
|
1187
|
+
})
|
|
1188
|
+
},
|
|
1123
1189
|
},
|
|
1124
1190
|
}
|
|
1125
1191
|
</script>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'
|
|
2
|
+
import NavigationSideMenu from './index.vue'
|
|
3
|
+
import theme from '@/assets/theme'
|
|
4
|
+
|
|
5
|
+
// Grouped menu mirroring the beta settings navigation: each top-level entry is a
|
|
6
|
+
// section ({ groupName, children }). Click a section header (e.g. "OFFER") to
|
|
7
|
+
// collapse/expand its items — sections collapse independently.
|
|
8
|
+
const settingsMenu = [
|
|
9
|
+
{
|
|
10
|
+
groupName: 'Project',
|
|
11
|
+
children: [
|
|
12
|
+
{
|
|
13
|
+
id: 'technologies',
|
|
14
|
+
name: 'Technologies',
|
|
15
|
+
dataId: 'nav_technologies',
|
|
16
|
+
icon: 'settings',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'finance',
|
|
20
|
+
name: 'Finance',
|
|
21
|
+
dataId: 'nav_finance',
|
|
22
|
+
icon: 'documents',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
groupName: 'Workflow',
|
|
28
|
+
children: [
|
|
29
|
+
{
|
|
30
|
+
id: 'workflow_template',
|
|
31
|
+
name: 'Workflow template',
|
|
32
|
+
dataId: 'nav_workflow_template',
|
|
33
|
+
icon: 'settings',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'activity_type',
|
|
37
|
+
name: 'Activity type definition',
|
|
38
|
+
dataId: 'nav_activity_type',
|
|
39
|
+
icon: 'documents',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
groupName: 'Offer',
|
|
45
|
+
children: [
|
|
46
|
+
{
|
|
47
|
+
id: 'branding',
|
|
48
|
+
name: 'Branding',
|
|
49
|
+
dataId: 'nav_branding',
|
|
50
|
+
icon: 'settings',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'document_templates',
|
|
54
|
+
name: 'Document templates',
|
|
55
|
+
dataId: 'nav_document_templates',
|
|
56
|
+
icon: 'documents',
|
|
57
|
+
// An item-level dropdown (its own children) — orthogonal to section collapse.
|
|
58
|
+
children: [
|
|
59
|
+
{ id: 'quotation', name: 'Quotation', dataId: 'nav_quotation' },
|
|
60
|
+
{ id: 'invoice', name: 'Invoice', dataId: 'nav_invoice' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const projectManagerOptions = [
|
|
68
|
+
{ id: 1, full_name: 'Firstname Lastname' },
|
|
69
|
+
{ id: 2, full_name: 'Alex Meyer' },
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export default {
|
|
73
|
+
title: 'Components/Navigation/NavigationSideMenu',
|
|
74
|
+
component: NavigationSideMenu,
|
|
75
|
+
tags: ['autodocs'],
|
|
76
|
+
parameters: {
|
|
77
|
+
layout: 'fullscreen',
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const Template = (args) => ({
|
|
82
|
+
components: { NavigationSideMenu },
|
|
83
|
+
setup() {
|
|
84
|
+
const onSectionToggle = (payload) =>
|
|
85
|
+
action('section-collapse-toggle')(payload)
|
|
86
|
+
const onItemClick = (payload) => action('on-menu-item-click')(payload)
|
|
87
|
+
return { args, onSectionToggle, onItemClick }
|
|
88
|
+
},
|
|
89
|
+
template: `
|
|
90
|
+
<div data-test-id="navigation-side-menu-story" style="display: flex; min-height: 100vh; background: #f5f5f5;">
|
|
91
|
+
<NavigationSideMenu
|
|
92
|
+
v-bind="args"
|
|
93
|
+
@section-collapse-toggle="onSectionToggle"
|
|
94
|
+
@on-menu-item-click="onItemClick"
|
|
95
|
+
/>
|
|
96
|
+
<main style="flex: 1; padding: 24px;">Main content area</main>
|
|
97
|
+
</div>
|
|
98
|
+
`,
|
|
99
|
+
provide: {
|
|
100
|
+
theme,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Default: all sections expanded. Click a section header to collapse/expand it;
|
|
105
|
+
// multiple sections can be collapsed at once (independent, not accordion).
|
|
106
|
+
export const CollapsibleSections = Template.bind({})
|
|
107
|
+
CollapsibleSections.args = {
|
|
108
|
+
menuData: settingsMenu,
|
|
109
|
+
headerTitle: 'Settings',
|
|
110
|
+
activeItemId: 'technologies',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// An item that has its own children ("Document templates") renders an item-level
|
|
114
|
+
// dropdown, which behaves independently of section collapse.
|
|
115
|
+
export const WithNestedDropdown = Template.bind({})
|
|
116
|
+
WithNestedDropdown.args = {
|
|
117
|
+
menuData: settingsMenu,
|
|
118
|
+
headerTitle: 'Settings',
|
|
119
|
+
activeItemId: 'quotation',
|
|
120
|
+
activeParentId: 'document_templates',
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The project-view placement of the same component: project-manager select in the
|
|
124
|
+
// header plus a footer button. Section collapse works identically here.
|
|
125
|
+
export const ProjectPlacement = Template.bind({})
|
|
126
|
+
ProjectPlacement.args = {
|
|
127
|
+
menuData: settingsMenu,
|
|
128
|
+
showProjectManagerSelect: true,
|
|
129
|
+
projectManagerOptions,
|
|
130
|
+
projectManagerId: 1,
|
|
131
|
+
footerButtonText: 'Documents',
|
|
132
|
+
activeItemId: 'finance',
|
|
133
|
+
}
|
|
@@ -113,8 +113,7 @@
|
|
|
113
113
|
size: {
|
|
114
114
|
type: String,
|
|
115
115
|
default: 'medium',
|
|
116
|
-
validator: (value) =>
|
|
117
|
-
['tiny', 'small', 'medium', 'large'].includes(value),
|
|
116
|
+
validator: (value) => ['small', 'medium', 'large'].includes(value),
|
|
118
117
|
},
|
|
119
118
|
isWhite: {
|
|
120
119
|
required: false,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Uploaded to svgrepo.com" width="800px" height="800px" viewBox="0 0 32 32" xml:space="preserve">
|
|
2
|
-
<style type="text/css">
|
|
3
|
-
.linesandangles_een{fill:#000000;}
|
|
4
|
-
</style>
|
|
5
|
-
<path class="linesandangles_een" d="M26.281,30l-2-8H25v-5h-8V2h-2v15H7v5h0.719l-2,8H26.281z M23.719,28H21v-4h-2v4h-2v-4h-2v4h-2 v-4h-2v4H8.281l1.5-6h12.438L23.719,28z M9,19h14v1H9V19z"/>
|
|
6
|
-
</svg>
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="14" viewBox="0 0 10 14" fill="none">
|
|
2
|
-
<g transform="translate(10,0) rotate(90)">
|
|
3
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.199219 9.85718V0.142822H1.75351L1.75352 9.85718H0.199219ZM5.38021 8.20572L2.14209 4.99999L5.38021 1.79425V4.19855L8.61833 4.19855V1.79425L11.8564 4.99999L8.61832 8.20572L8.61833 5.80142L5.38021 5.80142V8.20572ZM12.2449 0.142822V9.85718H13.7992V0.142822H12.2449Z" fill="white"/>
|
|
4
|
-
</g>
|
|
5
|
-
</svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = 'test-file-stub'
|