@eturnity/eturnity_reusable_components 9.37.0 → 9.37.1-ATE-87.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/dist/index.es3.js CHANGED
@@ -815,8 +815,8 @@ const theme = (() => {
815
815
  iconBackgroundColor: semanticColors.buttonIcon.overlayWhite10
816
816
  },
817
817
  disabled: {
818
- textColor: semanticColors.grey[800],
819
- backgroundColor: semanticColors.transparentTeal[100],
818
+ textColor: semanticColors.transparentWhite[400],
819
+ backgroundColor: "transparent",
820
820
  borderColor: "",
821
821
  iconBackgroundColor: semanticColors.buttonIcon.none
822
822
  }
@@ -843,7 +843,7 @@ const theme = (() => {
843
843
  },
844
844
  disabled: {
845
845
  backgroundColor: "transparent",
846
- textColor: semanticColors.transparentRed[500],
846
+ textColor: semanticColors.transparentWhite[400],
847
847
  borderColor: "",
848
848
  iconBackgroundColor: semanticColors.buttonIcon.none
849
849
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "9.37.0",
3
+ "version": "9.37.1-ATE-87.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -816,8 +816,8 @@ const theme = (() => {
816
816
  iconBackgroundColor: semanticColors.buttonIcon.overlayWhite10,
817
817
  },
818
818
  disabled: {
819
- textColor: semanticColors.grey[800],
820
- backgroundColor: semanticColors.transparentTeal[100],
819
+ textColor: semanticColors.transparentWhite[400],
820
+ backgroundColor: 'transparent',
821
821
  borderColor: '',
822
822
  iconBackgroundColor: semanticColors.buttonIcon.none,
823
823
  },
@@ -844,7 +844,7 @@ const theme = (() => {
844
844
  },
845
845
  disabled: {
846
846
  backgroundColor: 'transparent',
847
- textColor: semanticColors.transparentRed[500],
847
+ textColor: semanticColors.transparentWhite[400],
848
848
  borderColor: '',
849
849
  iconBackgroundColor: semanticColors.buttonIcon.none,
850
850
  },
@@ -14,15 +14,7 @@
14
14
  :data-value="domDataValue"
15
15
  :disabled-bg-color="disabledBgColor"
16
16
  :disabled-text-color="disabledTextColor"
17
- :hovered-bg-color="
18
- colorMode == 'dark'
19
- ? theme.semanticColors.teal[800]
20
- : colorMode == 'transparent'
21
- ? 'grey6'
22
- : hoveredBgColor
23
- ? hoveredBgColor
24
- : 'grey5'
25
- "
17
+ :hovered-bg-color="hoveredBackgroundColor"
26
18
  :is-disabled="isDisabled"
27
19
  :min-width="minWidth"
28
20
  :padding="padding"
@@ -172,6 +164,11 @@
172
164
  }
173
165
  },
174
166
  computed: {
167
+ hoveredBackgroundColor() {
168
+ if (this.colorMode == 'dark') return theme.semanticColors.teal[800]
169
+ if (this.colorMode == 'transparent') return 'grey6'
170
+ return this.hoveredBgColor || theme.semanticColors.grey[300]
171
+ },
175
172
  /** Vue omits `data-value` when bound to null, so the parent select cannot find this row for search filtering. */
176
173
  domDataValue() {
177
174
  const v = this.value
@@ -0,0 +1,39 @@
1
+ /* eslint-disable */
2
+ import { mount } from '@vue/test-utils'
3
+ import Option from '@/components/inputs/select/option'
4
+ import theme from '@/assets/theme'
5
+
6
+ // jsdom never receives the stylesheet vue3-styled-components generates, so the
7
+ // hashed class on the rendered option is the observable: two mounts share a
8
+ // class only when they resolve to the same styles.
9
+ describe('RCoption', () => {
10
+ const styleClass = (props = {}) =>
11
+ mount(Option, {
12
+ props: { value: 'a', ...props },
13
+ slots: { default: 'Option A' },
14
+ })
15
+ .classes()
16
+ .join(' ')
17
+
18
+ it('hovers on grey 300 by default', () => {
19
+ expect(styleClass()).toBe(
20
+ styleClass({ hoveredBgColor: theme.semanticColors.grey[300] })
21
+ )
22
+ })
23
+
24
+ it('lets an explicit hoveredBgColor win over the default', () => {
25
+ expect(styleClass({ hoveredBgColor: 'grey4' })).not.toBe(styleClass())
26
+ expect(styleClass({ hoveredBgColor: 'grey4' })).toBe(
27
+ styleClass({ hoveredBgColor: theme.colors.grey4 })
28
+ )
29
+ })
30
+
31
+ it('keeps its own hover colours in dark and transparent colour modes', () => {
32
+ expect(styleClass({ colorMode: 'dark', hoveredBgColor: 'grey4' })).toBe(
33
+ styleClass({ colorMode: 'dark' })
34
+ )
35
+ expect(
36
+ styleClass({ colorMode: 'transparent', hoveredBgColor: 'grey4' })
37
+ ).toBe(styleClass({ colorMode: 'transparent' }))
38
+ })
39
+ })
@@ -5,7 +5,12 @@
5
5
  <TitleContainer>
6
6
  <BoxTitle> {{ numberSelected }} {{ selectedText }} </BoxTitle>
7
7
  <IconContainer @click="$emit('on-close')">
8
- <Icon color="white" cursor="pointer" name="close" size="14px" />
8
+ <Icon
9
+ :color="theme.semanticColors.red[500]"
10
+ cursor="pointer"
11
+ name="close"
12
+ size="14px"
13
+ />
9
14
  </IconContainer>
10
15
  </TitleContainer>
11
16
  <DividerContainer>
@@ -15,17 +20,18 @@
15
20
  <ExpandedListItem
16
21
  v-for="item in expandedOptions"
17
22
  :key="item.type"
23
+ data-test-id="expanded_option"
18
24
  :disabled="item.disabled || false"
19
25
  :has-component="!!item?.component"
20
- :hover-color="item.hoverColor"
26
+ :text-color="expandedItemColor(item)"
21
27
  @click="
22
28
  !item?.component && !item.disabled && $emit('on-' + item.type)
23
29
  "
24
30
  >
25
31
  <ListItemWrapper
26
32
  v-if="item?.component"
33
+ data-test-id="expanded_list_item"
27
34
  :has-component="!!item?.component"
28
- test-data-id="expanded_list_item"
29
35
  @click="!item.disabled && toggleElement(item.type)"
30
36
  >
31
37
  <ListItemTitle>
@@ -38,7 +44,6 @@
38
44
  <Icon
39
45
  :color="theme.colors.white"
40
46
  :cursor="item.disabled ? 'not-allowed' : 'pointer'"
41
- :hovered-color="theme.colors.white"
42
47
  name="arrow_right"
43
48
  size="20px"
44
49
  />
@@ -60,42 +65,43 @@
60
65
  <SelectedContainer>
61
66
  {{ numberSelected }} {{ selectedText }}
62
67
  </SelectedContainer>
63
- <ListContainer v-if="optionsList.length">
64
- <ListItem
65
- v-for="item in limitedOptions"
66
- :key="item.type"
67
- :disabled="item.disabled || false"
68
- :hover-color="item.hoverColor"
69
- @click="$emit('on-' + item.type)"
70
- >
71
- {{ item.name }}
72
- </ListItem>
73
- <IconContainer
74
- v-if="optionsList.length > optionLimit || hasComponent"
75
- test-data-id="more_actions_button_wrapper"
76
- @click="expandOptions"
77
- >
78
- <ButtonContainer name="more_options,_tool_tips">
79
- <DotItem />
80
- <DotItem />
81
- <DotItem />
82
- </ButtonContainer>
83
- </IconContainer>
84
- </ListContainer>
85
- <EmptyText v-else test-data-id="no_bulk_actions">
86
- {{ noBulkActionsText }}
87
- </EmptyText>
88
- <CloseContainer>
89
- <IconContainer @click="$emit('on-close')">
68
+ <ActionsContainer>
69
+ <template v-if="optionsList.length">
70
+ <RCMainButton
71
+ v-for="item in limitedOptions"
72
+ :key="item.type"
73
+ app-theme="dark"
74
+ :is-disabled="item.disabled"
75
+ no-wrap
76
+ :text="item.name"
77
+ type="ghost"
78
+ :variant="item.variant"
79
+ @click="!item.disabled && $emit('on-' + item.type)"
80
+ />
81
+ <IconContainer
82
+ v-if="optionsList.length > optionLimit || hasComponent"
83
+ data-test-id="more_actions_button_wrapper"
84
+ @click="expandOptions"
85
+ >
86
+ <ButtonContainer name="more_options,_tool_tips">
87
+ <DotItem />
88
+ <DotItem />
89
+ <DotItem />
90
+ </ButtonContainer>
91
+ </IconContainer>
92
+ </template>
93
+ <EmptyText v-else data-test-id="no_bulk_actions">
94
+ {{ noBulkActionsText }}
95
+ </EmptyText>
96
+ <IconContainer data-test-id="close_button" @click="$emit('on-close')">
90
97
  <Icon
91
- :color="theme.colors.white"
98
+ :color="theme.semanticColors.red[500]"
92
99
  cursor="pointer"
93
- :hovered-color="theme.colors.white"
94
100
  name="close"
95
101
  size="14px"
96
102
  />
97
103
  </IconContainer>
98
- </CloseContainer>
104
+ </ActionsContainer>
99
105
  </BoxContainer>
100
106
  </PageContainer>
101
107
  </PageWrapper>
@@ -118,7 +124,7 @@
118
124
  // {
119
125
  // type: 'delete',
120
126
  // name: 'Delete',
121
- // hoverColor: 'red' // default is green
127
+ // variant: 'cancel' // default is 'main'
122
128
  // }
123
129
  // ]
124
130
  // @on-${type}="function" should $emit the callback for the 'type' in the optionsList
@@ -137,6 +143,7 @@
137
143
  // </SelectedOptions>
138
144
  import styled from 'vue3-styled-components'
139
145
  import InfoText from '../infoText'
146
+ import RCMainButton from '../buttons/mainButton'
140
147
  import Icon from '../icon'
141
148
  import theme from '../../assets/theme'
142
149
 
@@ -149,7 +156,7 @@
149
156
  `
150
157
 
151
158
  const Divider = styled.div`
152
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
159
+ border-bottom: 1px solid ${(props) => props.theme.semanticColors.teal[600]};
153
160
  width: 95%;
154
161
  margin: 0 auto;
155
162
  `
@@ -159,7 +166,6 @@
159
166
  bottom: 30px;
160
167
  left: 50%;
161
168
  width: auto;
162
- max-width: 70%;
163
169
  transform: translateX(-50%);
164
170
  z-index: 999;
165
171
  `
@@ -170,13 +176,12 @@
170
176
  left: 50%;
171
177
  transform: translate(-50%, -50%);
172
178
  border-radius: 4px;
173
- background-color: rgba(0, 0, 0, 0.4);
174
179
  `
175
180
 
176
181
  const OptionsContainer = styled.div`
177
182
  position: absolute;
178
183
  left: 101%;
179
- background-color: ${(props) => props.theme.colors.black};
184
+ background-color: ${(props) => props.theme.colors.transparentBlack2};
180
185
  border-radius: 4px;
181
186
  padding: 15px;
182
187
  `
@@ -185,9 +190,9 @@
185
190
  display: grid;
186
191
  align-items: center;
187
192
  height: 100%;
188
- padding-right: 20px;
193
+ padding: 0 24px;
189
194
  white-space: nowrap;
190
- border-right: 1px solid rgba(255, 255, 255, 0.2);
195
+ border-right: 1px solid ${(props) => props.theme.semanticColors.teal[600]};
191
196
  `
192
197
 
193
198
  const TitleContainer = styled.div`
@@ -210,13 +215,11 @@
210
215
  const BoxContainer = styled.div`
211
216
  display: flex;
212
217
  align-items: center;
213
- background-color: ${(props) => props.theme.colors.black};
214
- opacity: 90%;
218
+ background-color: ${(props) => props.theme.colors.transparentBlack2};
215
219
  color: ${(props) => props.theme.colors.white};
216
220
  border-radius: 4px;
217
- padding: 0px 10px 0px 20px;
218
- font-size: 13px;
219
- height: 45px;
221
+ font-size: 14px;
222
+ height: 62px;
220
223
  `
221
224
 
222
225
  const CenterBox = styled(BoxContainer)`
@@ -239,14 +242,10 @@
239
242
 
240
243
  div {
241
244
  // This is the dot color
242
- background: ${(props) => props.theme.colors.white};
245
+ background: ${(props) => props.theme.semanticColors.grey[600]};
243
246
  }
244
247
  `
245
248
 
246
- const CloseContainer = styled.div`
247
- margin-left: 20px;
248
- `
249
-
250
249
  const DotItem = styled.div`
251
250
  width: 4px;
252
251
  height: 4px;
@@ -254,38 +253,15 @@
254
253
  border-radius: 50%;
255
254
  `
256
255
 
257
- const ListContainer = styled.div`
256
+ const ActionsContainer = styled.div`
258
257
  height: 100%;
259
258
  display: flex;
260
259
  align-items: center;
260
+ gap: 8px;
261
+ padding: 0 24px;
261
262
  color: ${(props) => props.theme.colors.white};
262
263
  `
263
264
 
264
- const ListAttrs = {
265
- disabled: Boolean,
266
- hoverColor: String,
267
- }
268
-
269
- const ListItem = styled('div', ListAttrs)`
270
- width: max-content;
271
- height: 100%;
272
- display: flex;
273
- align-items: center;
274
- padding: 0 10px;
275
- cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
276
- color: ${(props) => props.disabled && props.theme.colors.grey3};
277
-
278
- &:hover {
279
- background: rgba(255, 255, 255, 0.1);
280
- color: ${(props) =>
281
- props.disabled
282
- ? props.theme.colors.grey3
283
- : props.hoverColor
284
- ? props.theme.colors[props.hoverColor]
285
- : props.theme.colors.white};
286
- }
287
- `
288
-
289
265
  const IconContainer = styled.div`
290
266
  display: grid;
291
267
  align-items: center;
@@ -293,10 +269,9 @@
293
269
  height: 30px;
294
270
  width: 30px;
295
271
  cursor: pointer;
296
- margin-left: 8px;
297
272
 
298
273
  &:hover {
299
- background: rgba(255, 255, 255, 0.1);
274
+ background: ${(props) => props.theme.semanticColors.transparentTeal[400]};
300
275
  border-radius: 4px;
301
276
  }
302
277
  `
@@ -309,8 +284,8 @@
309
284
 
310
285
  const ExpandedListItem = styled('div', {
311
286
  disabled: Boolean,
312
- hoverColor: String,
313
287
  hasComponent: Boolean,
288
+ textColor: String,
314
289
  })`
315
290
  width: 100%;
316
291
  display: flex;
@@ -318,12 +293,10 @@
318
293
  align-items: center;
319
294
  padding: ${(props) => (props.hasComponent ? '0' : '13px 15px')};
320
295
  cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
321
- color: ${(props) => props.disabled && props.theme.colors.grey3};
296
+ color: ${(props) => props.textColor};
322
297
 
323
298
  &:hover {
324
- background: rgba(255, 255, 255, 0.1);
325
- color: ${(props) =>
326
- props.disabled ? props.theme.colors.grey3 : props.theme.colors.white};
299
+ background: ${(props) => props.theme.semanticColors.transparentTeal[400]};
327
300
  }
328
301
  `
329
302
 
@@ -343,8 +316,7 @@
343
316
 
344
317
  const EmptyText = styled.div`
345
318
  color: ${(props) => props.theme.colors.white};
346
- font-size: 13px;
347
- padding-left: 16px;
319
+ font-size: 14px;
348
320
  `
349
321
 
350
322
  export default {
@@ -353,10 +325,10 @@
353
325
  PageContainer,
354
326
  BoxContainer,
355
327
  SelectedContainer,
356
- ListContainer,
328
+ ActionsContainer,
357
329
  ButtonContainer,
358
330
  DotItem,
359
- ListItem,
331
+ RCMainButton,
360
332
  InfoText,
361
333
  CenterBox,
362
334
  Icon,
@@ -373,7 +345,6 @@
373
345
  Divider,
374
346
  DividerContainer,
375
347
  ExpandedList,
376
- CloseContainer,
377
348
  },
378
349
  props: {
379
350
  optionsList: {
@@ -434,6 +405,13 @@
434
405
  this.initializeOptions()
435
406
  },
436
407
  methods: {
408
+ expandedItemColor(item) {
409
+ const ghost = this.theme.mainButton.dark.ghost
410
+ const variant = ghost[item.variant] ?? ghost.main
411
+ const state = item.disabled ? 'disabled' : 'default'
412
+
413
+ return variant[state].textColor
414
+ },
437
415
  initializeOptions() {
438
416
  this.expandedOptions = [...this.optionsList]
439
417
  .map((option, index) => {
@@ -457,17 +435,6 @@
457
435
  this.expanded = !this.expanded
458
436
  this.expandedOptions = this.optionsList
459
437
  },
460
- handleExpandedOptionClick(item) {
461
- if (item.disabled) return
462
-
463
- if (!item.component) {
464
- this.$emit('on-' + item.type)
465
-
466
- return
467
- }
468
-
469
- this.toggleElement(item.type)
470
- },
471
438
  },
472
439
  }
473
440
  </script>
@@ -1,6 +1,8 @@
1
1
  /* eslint-disable */
2
2
  import { mount } from '@vue/test-utils'
3
3
  import RCSelectedOptions from '@/components/selectedOptions'
4
+ import RCMainButton from '@/components/buttons/mainButton'
5
+ import RCIcon from '@/components/icon/Icon.vue'
4
6
  import theme from '@/assets/theme'
5
7
 
6
8
  jest.mock('@/components/icon/iconCache.mjs', () => ({
@@ -22,7 +24,7 @@ describe('RCSelectedOptions.vue', () => {
22
24
  {
23
25
  type: 'delete',
24
26
  name: 'Delete',
25
- hoverColor: 'red',
27
+ variant: 'cancel',
26
28
  },
27
29
  ],
28
30
  }
@@ -58,7 +60,7 @@ describe('RCSelectedOptions.vue', () => {
58
60
  expect(selectedOptions.exists()).toBe(true)
59
61
 
60
62
  const noBulkActions = selectedOptions.find(
61
- '[test-data-id="no_bulk_actions"]'
63
+ '[data-test-id="no_bulk_actions"]'
62
64
  )
63
65
  expect(noBulkActions.exists()).toBe(true)
64
66
  })
@@ -78,7 +80,7 @@ describe('RCSelectedOptions.vue', () => {
78
80
 
79
81
  //since we have 2 options, and the limit is 4, we should not see "•••" button
80
82
  const moreActionsButton = selectedOptions.find(
81
- '[test-data-id="more_actions_button_wrapper"]'
83
+ '[data-test-id="more_actions_button_wrapper"]'
82
84
  )
83
85
  expect(moreActionsButton.exists()).toBe(false)
84
86
  })
@@ -111,7 +113,7 @@ describe('RCSelectedOptions.vue', () => {
111
113
 
112
114
  //since we a component parameter, we should see "•••" button
113
115
  const moreActionsButton = selectedOptions.find(
114
- '[test-data-id="more_actions_button_wrapper"]'
116
+ '[data-test-id="more_actions_button_wrapper"]'
115
117
  )
116
118
  expect(moreActionsButton.exists()).toBe(true)
117
119
  })
@@ -146,7 +148,7 @@ describe('RCSelectedOptions.vue', () => {
146
148
 
147
149
  //since we a component parameter, we should see "•••" button
148
150
  const moreActionsButton = selectedOptions.find(
149
- '[test-data-id="more_actions_button_wrapper"]'
151
+ '[data-test-id="more_actions_button_wrapper"]'
150
152
  )
151
153
  expect(moreActionsButton.exists()).toBe(true)
152
154
 
@@ -160,7 +162,7 @@ describe('RCSelectedOptions.vue', () => {
160
162
 
161
163
  //toggle the element with the component parameter
162
164
  const expandedListItems = selectedOptions.findAll(
163
- '[test-data-id="expanded_list_item"]'
165
+ '[data-test-id="expanded_list_item"]'
164
166
  )
165
167
 
166
168
  //there should be only one since we have only one option with the component parameter
@@ -173,4 +175,238 @@ describe('RCSelectedOptions.vue', () => {
173
175
  const setSupplierSlot = wrapper.find('[data-id="set_supplier"]')
174
176
  expect(setSupplierSlot.exists()).toBe(true)
175
177
  })
178
+ it('renders each collapsed option as a main button', async () => {
179
+ const wrapper = mount(RCSelectedOptions, {
180
+ props: simpleOptionsProps,
181
+ global: {
182
+ provide: {
183
+ theme,
184
+ },
185
+ },
186
+ })
187
+
188
+ await wrapper.vm.$nextTick()
189
+
190
+ const optionButtons = wrapper.findAllComponents(RCMainButton)
191
+ expect(optionButtons).toHaveLength(2)
192
+ expect(optionButtons[0].props('text')).toBe('Close')
193
+ expect(optionButtons[1].props('text')).toBe('Delete')
194
+ })
195
+
196
+ it('passes the option variant straight through to the button', async () => {
197
+ const wrapper = mount(RCSelectedOptions, {
198
+ props: simpleOptionsProps,
199
+ global: {
200
+ provide: {
201
+ theme,
202
+ },
203
+ },
204
+ })
205
+
206
+ await wrapper.vm.$nextTick()
207
+
208
+ const optionButtons = wrapper.findAllComponents(RCMainButton)
209
+ expect(optionButtons[0].props('variant')).toBe('main')
210
+ expect(optionButtons[1].props('variant')).toBe('cancel')
211
+ })
212
+
213
+ it('marks a disabled option as a disabled button and does not emit on click', async () => {
214
+ const wrapper = mount(RCSelectedOptions, {
215
+ props: {
216
+ ...simpleOptionsProps,
217
+ optionsList: [
218
+ {
219
+ type: 'delete',
220
+ name: 'Delete',
221
+ disabled: true,
222
+ },
223
+ ],
224
+ },
225
+ global: {
226
+ provide: {
227
+ theme,
228
+ },
229
+ },
230
+ })
231
+
232
+ await wrapper.vm.$nextTick()
233
+
234
+ const deleteButton = wrapper.findComponent(RCMainButton)
235
+ expect(deleteButton.props('isDisabled')).toBe(true)
236
+
237
+ await deleteButton.trigger('click')
238
+ expect(wrapper.emitted('on-delete')).toBeUndefined()
239
+ })
240
+
241
+ it('emits the option event when an enabled option is clicked', async () => {
242
+ const wrapper = mount(RCSelectedOptions, {
243
+ props: simpleOptionsProps,
244
+ global: {
245
+ provide: {
246
+ theme,
247
+ },
248
+ },
249
+ })
250
+
251
+ await wrapper.vm.$nextTick()
252
+
253
+ await wrapper.findAllComponents(RCMainButton)[1].trigger('click')
254
+ expect(wrapper.emitted('on-delete')).toHaveLength(1)
255
+ })
256
+ it('renders the close button and emits on-close even when there are no options', async () => {
257
+ const wrapper = mount(RCSelectedOptions, {
258
+ props: {
259
+ ...simpleOptionsProps,
260
+ optionsList: [],
261
+ },
262
+ global: {
263
+ provide: {
264
+ theme,
265
+ },
266
+ },
267
+ })
268
+
269
+ await wrapper.vm.$nextTick()
270
+
271
+ const closeButton = wrapper.find('[data-test-id="close_button"]')
272
+ expect(closeButton.exists()).toBe(true)
273
+
274
+ await closeButton.trigger('click')
275
+ expect(wrapper.emitted('on-close')).toHaveLength(1)
276
+ })
277
+
278
+ const mountExpanded = async (optionsList) => {
279
+ const wrapper = mount(RCSelectedOptions, {
280
+ props: {
281
+ ...simpleOptionsProps,
282
+ optionsList,
283
+ },
284
+ global: {
285
+ provide: {
286
+ theme,
287
+ },
288
+ },
289
+ })
290
+
291
+ await wrapper
292
+ .find('[data-test-id="more_actions_button_wrapper"]')
293
+ .trigger('click')
294
+ await wrapper.vm.$nextTick()
295
+
296
+ return wrapper
297
+ .findAll('[data-test-id="expanded_option"]')
298
+ .map((option) => option.classes().join(' '))
299
+ }
300
+
301
+ const expandedItemColors = async (optionsList) => {
302
+ const wrapper = mount(RCSelectedOptions, {
303
+ props: {
304
+ ...simpleOptionsProps,
305
+ optionsList,
306
+ },
307
+ global: {
308
+ provide: {
309
+ theme,
310
+ },
311
+ },
312
+ })
313
+
314
+ await wrapper
315
+ .find('[data-test-id="more_actions_button_wrapper"]')
316
+ .trigger('click')
317
+ await wrapper.vm.$nextTick()
318
+
319
+ return wrapper
320
+ .findAllComponents('[data-test-id="expanded_option"]')
321
+ .map((option) => option.props('textColor'))
322
+ }
323
+
324
+ it('styles a cancel option differently from the default options in the expanded list', async () => {
325
+ const [close, archive, remove] = await mountExpanded([
326
+ { type: 'close', name: 'Close' },
327
+ { type: 'archive', name: 'Archive' },
328
+ { type: 'delete', name: 'Delete', variant: 'cancel' },
329
+ { type: 'supplier', name: 'Supplier', component: 'set_supplier' },
330
+ ])
331
+
332
+ expect(archive).toBe(close)
333
+ expect(remove).not.toBe(close)
334
+ })
335
+
336
+ it('styles disabled options alike whatever the variant in the expanded list', async () => {
337
+ const [close, remove] = await mountExpanded([
338
+ { type: 'close', name: 'Close', disabled: true },
339
+ { type: 'delete', name: 'Delete', variant: 'cancel', disabled: true },
340
+ { type: 'supplier', name: 'Supplier', component: 'set_supplier' },
341
+ ])
342
+
343
+ expect(remove).toBe(close)
344
+ })
345
+
346
+ it('takes expanded option colours from the ghost button theme', async () => {
347
+ const [close, remove, archive, purge] = await expandedItemColors([
348
+ { type: 'close', name: 'Close' },
349
+ { type: 'delete', name: 'Delete', variant: 'cancel' },
350
+ { type: 'archive', name: 'Archive', disabled: true },
351
+ { type: 'purge', name: 'Purge', variant: 'cancel', disabled: true },
352
+ { type: 'supplier', name: 'Supplier', component: 'set_supplier' },
353
+ ])
354
+
355
+ const ghost = theme.mainButton.dark.ghost
356
+
357
+ expect(close).toBe(ghost.main.default.textColor)
358
+ expect(remove).toBe(ghost.cancel.default.textColor)
359
+ expect(archive).toBe(ghost.main.disabled.textColor)
360
+ expect(purge).toBe(ghost.cancel.disabled.textColor)
361
+ })
362
+
363
+ it('uses the same close colour in the expanded modal as in the collapsed bar', async () => {
364
+ const wrapper = mount(RCSelectedOptions, {
365
+ props: {
366
+ ...simpleOptionsProps,
367
+ optionsList: [
368
+ { type: 'close', name: 'Close' },
369
+ { type: 'supplier', name: 'Supplier', component: 'set_supplier' },
370
+ ],
371
+ },
372
+ global: {
373
+ provide: {
374
+ theme,
375
+ },
376
+ },
377
+ })
378
+
379
+ await wrapper
380
+ .find('[data-test-id="more_actions_button_wrapper"]')
381
+ .trigger('click')
382
+ await wrapper.vm.$nextTick()
383
+
384
+ const closeIcon = wrapper
385
+ .findAllComponents(RCIcon)
386
+ .find((icon) => icon.props('name') === 'close')
387
+
388
+ expect(closeIcon.props('color')).toBe(theme.semanticColors.red[500])
389
+ })
390
+
391
+ it('falls back to the main variant when an option has an unknown variant', async () => {
392
+ const [mystery] = await expandedItemColors([
393
+ { type: 'mystery', name: 'Mystery', variant: 'not_a_variant' },
394
+ { type: 'supplier', name: 'Supplier', component: 'set_supplier' },
395
+ ])
396
+
397
+ expect(mystery).toBe(theme.mainButton.dark.ghost.main.default.textColor)
398
+ })
399
+
400
+ it('gives every disabled option the same background whatever its variant', () => {
401
+ const ghost = theme.mainButton.dark.ghost
402
+
403
+ expect(ghost.main.disabled.backgroundColor).toBe('transparent')
404
+ expect(ghost.cancel.disabled.backgroundColor).toBe('transparent')
405
+ })
406
+
407
+ it('gives every disabled option the same text colour whatever its variant', () => {
408
+ const ghost = theme.mainButton.dark.ghost
409
+
410
+ expect(ghost.cancel.disabled.textColor).toBe(ghost.main.disabled.textColor)
411
+ })
176
412
  })
@@ -9,7 +9,7 @@ export default {
9
9
  optionsList: {
10
10
  control: 'object',
11
11
  description:
12
- 'Array of options to display. Each option should have a type and name, and can optionally have a component, disabled state, hoverColor, and disabledInfo.',
12
+ 'Array of options to display. Each option should have a type and name, and can optionally have a component, disabled state, variant, and disabledInfo.',
13
13
  },
14
14
  numberSelected: {
15
15
  control: 'number',
@@ -49,7 +49,7 @@ Default.args = {
49
49
  {
50
50
  type: 'delete',
51
51
  name: 'Delete',
52
- hoverColor: 'red',
52
+ variant: 'cancel',
53
53
  },
54
54
  ],
55
55
  numberSelected: 5,
@@ -98,7 +98,7 @@ ManyOptions.args = {
98
98
  {
99
99
  type: 'delete',
100
100
  name: 'Delete',
101
- hoverColor: 'red',
101
+ variant: 'cancel',
102
102
  },
103
103
  {
104
104
  type: 'archive',
@@ -132,7 +132,7 @@ CustomLabels.args = {
132
132
  {
133
133
  type: 'delete',
134
134
  name: 'Delete',
135
- hoverColor: 'red',
135
+ variant: 'cancel',
136
136
  },
137
137
  ],
138
138
  numberSelected: 1,
@@ -158,7 +158,7 @@ CustomLabels.args = {
158
158
  // {
159
159
  // type: 'delete',
160
160
  // name: 'Delete',
161
- // hoverColor: 'red' // default is green
161
+ // variant: 'cancel' // default is 'main'
162
162
  // }
163
163
  // ]
164
164
  //
@@ -365,7 +365,7 @@
365
365
  }
366
366
 
367
367
  &:hover {
368
- background-color: ${(props) => props.theme.colors.grey5};
368
+ background-color: ${(props) => props.theme.semanticColors.grey[300]};
369
369
  }
370
370
  `
371
371
 
@@ -420,7 +420,7 @@
420
420
  height: 37px;
421
421
 
422
422
  &:hover {
423
- background-color: ${(props) => props.theme.colors.grey5};
423
+ background-color: ${(props) => props.theme.semanticColors.grey[300]};
424
424
  }
425
425
  `
426
426
 
@@ -143,11 +143,13 @@
143
143
  .arrow-container,
144
144
  .input-placeholder,
145
145
  .table-dropdown-item {
146
- background-color: ${(props) => props.theme.colors.grey5};
146
+ background-color: ${(props) =>
147
+ props.theme.semanticColors.grey[300]};
147
148
  }
148
149
 
149
150
  input {
150
- background-color: ${(props) => props.theme.colors.grey5};
151
+ background-color: ${(props) =>
152
+ props.theme.semanticColors.grey[300]};
151
153
  }
152
154
  }
153
155
 
@@ -337,7 +339,7 @@
337
339
  &:hover {
338
340
  input:disabled {
339
341
  background-color: ${(props) =>
340
- props.theme.colors.grey5 + '!important'};
342
+ props.theme.semanticColors.grey[300] + '!important'};
341
343
  }
342
344
 
343
345
  .drag-icon {
@@ -345,7 +347,7 @@
345
347
  }
346
348
 
347
349
  .drag-wrapper {
348
- background-color: ${(props) => props.theme.colors.grey5};
350
+ background-color: ${(props) => props.theme.semanticColors.grey[300]};
349
351
  }
350
352
 
351
353
  .arrow-dropdown {
@@ -416,7 +418,7 @@
416
418
  height: unset;
417
419
 
418
420
  &:focus {
419
- background: ${(props) => props.theme.colors.grey5};
421
+ background: ${(props) => props.theme.semanticColors.grey[300]};
420
422
  }
421
423
  }
422
424
 
@@ -254,12 +254,12 @@
254
254
  background-color: ${(props) =>
255
255
  props.colorTheme == 'dark'
256
256
  ? theme.semanticColors.teal[700]
257
- : '#ebeef4'};
257
+ : theme.semanticColors.grey[300]};
258
258
  }
259
259
 
260
260
  &:focus-visible {
261
261
  outline: none;
262
- background-color: #ebeef4;
262
+ background-color: ${theme.semanticColors.grey[300]};
263
263
  }
264
264
  `
265
265
  const OptionChild = styled('div', { colorTheme: String })`
@@ -272,12 +272,12 @@
272
272
  background-color: ${(props) =>
273
273
  props.colorTheme == 'dark'
274
274
  ? theme.semanticColors.teal[600]
275
- : '#ebeef4'};
275
+ : theme.semanticColors.grey[300]};
276
276
  }
277
277
 
278
278
  &:focus-visible {
279
279
  outline: none;
280
- background-color: #ebeef4;
280
+ background-color: ${theme.semanticColors.grey[300]};
281
281
  }
282
282
  `
283
283
 
@@ -358,7 +358,7 @@
358
358
  },
359
359
  hoveredBackgroundColor: {
360
360
  type: String,
361
- default: theme.colors.grey5,
361
+ default: theme.semanticColors.grey[300],
362
362
  },
363
363
  dataId: {
364
364
  required: false,
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable */
2
2
  import { mount, DOMWrapper } from '@vue/test-utils'
3
3
  import ThreeDots from './index.vue'
4
+ import theme from '@/assets/theme'
4
5
 
5
6
  jest.mock('@/components/icon/iconCache.mjs', () => ({
6
7
  fetchIcon: jest.fn(() => Promise.resolve('')),
@@ -118,4 +119,28 @@ describe('ThreeDots', () => {
118
119
  expect(w.emitted('on-select')).toHaveLength(1)
119
120
  expect(w.emitted('on-select')[0][0]).toMatchObject({ value: 'c1' })
120
121
  })
122
+
123
+ // jsdom never receives the stylesheet vue3-styled-components generates, so the
124
+ // hashed class on the trigger is the observable: two mounts share a class only
125
+ // when they resolve to the same styles.
126
+ describe('hovered background colour', () => {
127
+ const styleClass = (props = {}) => {
128
+ const w = mount(ThreeDots, { props: { options, ...props } })
129
+ const classes = w.classes().join(' ')
130
+ w.unmount()
131
+ return classes
132
+ }
133
+
134
+ it('hovers on grey 300 by default', () => {
135
+ expect(styleClass()).toBe(
136
+ styleClass({ hoveredBackgroundColor: theme.semanticColors.grey[300] })
137
+ )
138
+ })
139
+
140
+ it('lets an explicit hoveredBackgroundColor win over the default', () => {
141
+ expect(
142
+ styleClass({ hoveredBackgroundColor: theme.colors.grey5 })
143
+ ).not.toBe(styleClass())
144
+ })
145
+ })
121
146
  })