@rancher/shell 3.0.4 → 3.0.5-rc.2

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 (270) hide show
  1. package/assets/images/providers/sks.svg +1 -0
  2. package/assets/styles/base/_basic.scss +6 -0
  3. package/assets/styles/base/_helpers.scss +4 -0
  4. package/assets/styles/base/_variables.scss +1 -0
  5. package/assets/styles/global/_button.scss +1 -0
  6. package/assets/translations/en-us.yaml +65 -15
  7. package/assets/translations/zh-hans.yaml +4 -3
  8. package/chart/monitoring/index.vue +3 -1
  9. package/cloud-credential/aws.vue +2 -0
  10. package/components/ActionDropdownShell.vue +71 -0
  11. package/components/AppModal.vue +18 -4
  12. package/components/AsyncButton.vue +24 -7
  13. package/components/BannerGraphic.vue +1 -0
  14. package/components/CommunityLinks.vue +4 -59
  15. package/components/CopyToClipboardText.vue +2 -1
  16. package/components/CruResource.vue +6 -1
  17. package/components/DetailText.vue +5 -0
  18. package/components/ExplorerMembers.vue +1 -1
  19. package/components/ExplorerProjectsNamespaces.vue +68 -18
  20. package/components/GlobalRoleBindings.vue +5 -1
  21. package/components/GrowlManager.vue +1 -0
  22. package/components/LandingPagePreference.vue +7 -3
  23. package/components/LocaleSelector.vue +39 -95
  24. package/components/ModalManager.vue +55 -0
  25. package/components/ModalWithCard.vue +1 -0
  26. package/components/PromptModal.vue +47 -8
  27. package/components/PromptRemove.vue +1 -0
  28. package/components/PromptRestore.vue +1 -0
  29. package/components/ResourceCancelModal.vue +1 -0
  30. package/components/ResourceDetail/Masthead.vue +38 -12
  31. package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
  32. package/components/ResourceDetail/index.vue +47 -12
  33. package/components/ResourceTable.vue +54 -19
  34. package/components/SideNav.vue +5 -1
  35. package/components/SlideInPanelManager.vue +126 -0
  36. package/components/SortableTable/THead.vue +5 -2
  37. package/components/SortableTable/actions.js +1 -1
  38. package/components/SortableTable/index.vue +64 -51
  39. package/components/SortableTable/paging.js +16 -19
  40. package/components/SortableTable/selection.js +0 -11
  41. package/components/Wizard.vue +2 -2
  42. package/components/__tests__/AsyncButton.test.ts +2 -2
  43. package/components/__tests__/ModalManager.spec.ts +176 -0
  44. package/components/__tests__/PromptModal.test.ts +148 -0
  45. package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
  46. package/components/auth/AuthBanner.vue +13 -11
  47. package/components/auth/Principal.vue +1 -0
  48. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  49. package/components/auth/login/ldap.vue +1 -1
  50. package/components/fleet/FleetResources.vue +21 -6
  51. package/components/form/ArrayList.vue +76 -60
  52. package/components/form/BannerSettings.vue +17 -2
  53. package/components/form/ColorInput.vue +35 -6
  54. package/components/form/Command.vue +6 -15
  55. package/components/form/EnvVars.vue +16 -8
  56. package/components/form/HealthCheck.vue +3 -3
  57. package/components/form/HookOption.vue +11 -16
  58. package/components/form/LabeledSelect.vue +18 -22
  59. package/components/form/LifecycleHooks.vue +3 -3
  60. package/components/form/MatchExpressions.vue +14 -8
  61. package/components/form/NameNsDescription.vue +128 -104
  62. package/components/form/Networking.vue +20 -12
  63. package/components/form/NodeAffinity.vue +31 -23
  64. package/components/form/NodeScheduling.vue +13 -3
  65. package/components/form/NotificationSettings.vue +15 -1
  66. package/components/form/Password.vue +1 -0
  67. package/components/form/PodAffinity.vue +43 -43
  68. package/components/form/Probe.vue +68 -66
  69. package/components/form/ResourceQuota/Project.vue +5 -1
  70. package/components/form/ResourceSelector.vue +7 -9
  71. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +16 -24
  72. package/components/form/SSHKnownHosts/index.vue +30 -13
  73. package/components/form/Security.vue +54 -56
  74. package/components/form/Select.vue +32 -21
  75. package/components/form/ShellInput.vue +5 -1
  76. package/components/form/Tolerations.vue +5 -1
  77. package/components/form/ValueFromResource.vue +134 -121
  78. package/components/form/WorkloadPorts.vue +18 -18
  79. package/components/form/__tests__/ArrayList.test.ts +5 -2
  80. package/components/form/__tests__/ColorInput.test.ts +35 -0
  81. package/components/form/__tests__/LabeledSelect.test.ts +40 -0
  82. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  83. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  84. package/components/form/__tests__/Probe.test.ts +12 -8
  85. package/components/form/__tests__/SSHKnownHosts.test.ts +22 -2
  86. package/components/form/__tests__/Select.test.ts +37 -0
  87. package/components/formatter/InternalExternalIP.vue +2 -0
  88. package/components/formatter/SecretData.vue +20 -7
  89. package/components/nav/Group.vue +27 -5
  90. package/components/nav/Header.vue +17 -43
  91. package/components/nav/NamespaceFilter.vue +134 -86
  92. package/components/nav/TopLevelMenu.vue +4 -5
  93. package/components/nav/Type.vue +12 -1
  94. package/components/nav/WindowManager/ContainerLogs.vue +87 -61
  95. package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
  96. package/components/templates/blank.vue +4 -1
  97. package/components/templates/default.vue +8 -3
  98. package/components/templates/home.vue +10 -1
  99. package/components/templates/plain.vue +10 -4
  100. package/composables/focusTrap.ts +12 -4
  101. package/composables/useRuntimeFlag.ts +29 -0
  102. package/config/router/routes.js +20 -13
  103. package/config/store.js +4 -0
  104. package/config/uiplugins.js +5 -1
  105. package/core/types.ts +12 -6
  106. package/detail/catalog.cattle.io.app.vue +6 -1
  107. package/detail/fleet.cattle.io.bundle.vue +70 -6
  108. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  109. package/detail/namespace.vue +0 -3
  110. package/detail/node.vue +17 -13
  111. package/detail/provisioning.cattle.io.cluster.vue +72 -6
  112. package/dialog/AddCustomBadgeDialog.vue +1 -1
  113. package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
  114. package/{components/AssignTo.vue → dialog/AssignToDialog.vue} +71 -80
  115. package/dialog/ChangePasswordDialog.vue +106 -0
  116. package/dialog/DeactivateDriverDialog.vue +1 -0
  117. package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
  118. package/dialog/DisableAuthProviderDialog.vue +101 -0
  119. package/dialog/DrainNode.vue +1 -1
  120. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
  121. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
  122. package/dialog/FeatureFlagListDialog.vue +288 -0
  123. package/dialog/ForceMachineRemoveDialog.vue +5 -2
  124. package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
  125. package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
  126. package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -59
  127. package/dialog/MoveNamespaceDialog.vue +157 -0
  128. package/dialog/ScalePoolDownDialog.vue +1 -1
  129. package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
  130. package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
  131. package/dialog/WechatDialog.vue +57 -0
  132. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  133. package/edit/auth/__tests__/oidc.test.ts +152 -109
  134. package/edit/auth/azuread.vue +2 -1
  135. package/edit/auth/github.vue +1 -1
  136. package/edit/auth/googleoauth.vue +5 -1
  137. package/edit/auth/ldap/index.vue +1 -1
  138. package/edit/auth/oidc.vue +38 -5
  139. package/edit/auth/saml.vue +1 -1
  140. package/edit/cloudcredential.vue +24 -9
  141. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  142. package/edit/management.cattle.io.user.vue +28 -3
  143. package/edit/namespace.vue +1 -4
  144. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  145. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  146. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  147. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
  148. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -9
  149. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
  150. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
  151. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
  152. package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
  153. package/edit/provisioning.cattle.io.cluster/rke2.vue +49 -41
  154. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  155. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
  156. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +33 -2
  157. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  158. package/edit/token.vue +2 -0
  159. package/edit/workload/index.vue +1 -0
  160. package/edit/workload/mixins/workload.js +0 -2
  161. package/initialize/install-plugins.js +2 -1
  162. package/list/harvesterhci.io.management.cluster.vue +4 -1
  163. package/list/management.cattle.io.feature.vue +4 -287
  164. package/list/provisioning.cattle.io.cluster.vue +20 -12
  165. package/machine-config/azure.vue +16 -4
  166. package/mixins/vue-select-overrides.js +0 -4
  167. package/models/__tests__/namespace.test.ts +25 -1
  168. package/models/cloudcredential.js +5 -0
  169. package/models/fleet.cattle.io.cluster.js +8 -2
  170. package/models/fleet.cattle.io.gitrepo.js +8 -34
  171. package/models/kontainerdriver.js +6 -3
  172. package/models/management.cattle.io.feature.js +7 -1
  173. package/models/management.cattle.io.node.js +3 -3
  174. package/models/namespace.js +11 -6
  175. package/models/nodedriver.js +6 -3
  176. package/models/workload.js +4 -1
  177. package/package.json +3 -3
  178. package/pages/about.vue +13 -3
  179. package/pages/account/index.vue +16 -6
  180. package/pages/auth/login.vue +18 -7
  181. package/pages/auth/logout.vue +4 -1
  182. package/pages/auth/setup.vue +2 -0
  183. package/pages/auth/verify.vue +13 -8
  184. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  185. package/pages/c/_cluster/apps/charts/install.vue +26 -26
  186. package/pages/c/_cluster/auth/config/index.vue +10 -12
  187. package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
  188. package/pages/c/_cluster/explorer/index.vue +17 -15
  189. package/pages/c/_cluster/istio/index.vue +2 -2
  190. package/pages/c/_cluster/longhorn/index.vue +1 -1
  191. package/pages/c/_cluster/monitoring/index.vue +1 -1
  192. package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
  193. package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
  194. package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
  195. package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
  196. package/pages/c/_cluster/neuvector/index.vue +1 -1
  197. package/pages/c/_cluster/settings/banners.vue +4 -3
  198. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
  199. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
  200. package/pages/c/_cluster/uiplugins/index.vue +98 -55
  201. package/pages/diagnostic.vue +59 -11
  202. package/pages/fail-whale.vue +14 -8
  203. package/pages/home.vue +24 -18
  204. package/pages/prefs.vue +7 -6
  205. package/pages/support/index.vue +4 -1
  206. package/plugins/internal-api/index.ts +37 -0
  207. package/plugins/internal-api/shared/base-api.ts +13 -0
  208. package/plugins/internal-api/shell/shell.api.ts +108 -0
  209. package/plugins/steve/actions.js +0 -12
  210. package/public/index.html +1 -0
  211. package/rancher-components/Card/Card.vue +1 -1
  212. package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
  213. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
  214. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
  215. package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
  216. package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
  217. package/rancher-components/Form/Radio/RadioButton.vue +20 -4
  218. package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
  219. package/rancher-components/Form/Radio/RadioGroup.vue +52 -10
  220. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
  221. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
  222. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
  223. package/rancher-components/RcButton/RcButton.vue +2 -1
  224. package/rancher-components/RcButton/types.ts +1 -0
  225. package/rancher-components/RcDropdown/RcDropdown.vue +18 -6
  226. package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
  227. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
  228. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
  229. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
  230. package/rancher-components/RcDropdown/index.ts +2 -0
  231. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  232. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  233. package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
  234. package/scripts/extension/bundle +20 -0
  235. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
  236. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
  237. package/scripts/extension/helmpatch +44 -31
  238. package/scripts/extension/publish +12 -12
  239. package/scripts/typegen.sh +2 -4
  240. package/server/har-file.js +25 -3
  241. package/store/action-menu.js +26 -56
  242. package/store/features.js +2 -1
  243. package/store/index.js +5 -0
  244. package/store/modal.ts +71 -0
  245. package/store/slideInPanel.ts +47 -0
  246. package/store/type-map.js +12 -1
  247. package/store/type-map.utils.ts +4 -4
  248. package/types/global-vue.d.ts +5 -0
  249. package/types/internal-api/shell/growl.d.ts +25 -0
  250. package/types/internal-api/shell/modal.d.ts +77 -0
  251. package/types/internal-api/shell/slideIn.d.ts +15 -0
  252. package/types/resources/fleet.d.ts +0 -14
  253. package/types/shell/index.d.ts +43 -24
  254. package/types/vue-shim.d.ts +4 -1
  255. package/utils/__mocks__/tabbable.js +13 -0
  256. package/utils/__tests__/object.test.ts +38 -4
  257. package/utils/cluster.js +35 -0
  258. package/utils/fleet.ts +15 -73
  259. package/utils/object.js +48 -5
  260. package/utils/validators/formRules/__tests__/index.test.ts +10 -1
  261. package/utils/validators/formRules/index.ts +27 -3
  262. package/utils/validators/machine-pool.ts +20 -0
  263. package/components/DisableAuthProviderModal.vue +0 -114
  264. package/components/MoveModal.vue +0 -166
  265. package/components/PromptChangePassword.vue +0 -123
  266. package/components/fleet/FleetBundleResources.vue +0 -86
  267. package/components/formatter/ExtensionCache.vue +0 -74
  268. package/components/formatter/Port.vue +0 -24
  269. package/components/formatter/SecretType.vue +0 -41
  270. package/types/vue-shim.d +0 -20
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent } from 'vue';
3
3
  import { _VIEW } from '@shell/config/query-params';
4
- import { randomStr } from '@shell/utils/string';
4
+ import { generateRandomAlphaString } from '@shell/utils/string';
5
5
 
6
6
  export default defineComponent({
7
- props: {
7
+
8
+ inheritAttrs: false,
9
+ props: {
8
10
  /**
9
11
  * The name of the input, for grouping.
10
12
  */
@@ -76,7 +78,16 @@ export default defineComponent({
76
78
  preventFocusOnRadioGroups: {
77
79
  type: Boolean,
78
80
  default: false
79
- }
81
+ },
82
+
83
+ /**
84
+ * Radio option Id - used to link to aria-activedescendant
85
+ * when using inside of the context of a Radio Group
86
+ */
87
+ radioOptionId: {
88
+ type: String,
89
+ default: undefined
90
+ },
80
91
  },
81
92
 
82
93
  emits: ['update:value'],
@@ -84,7 +95,8 @@ export default defineComponent({
84
95
  data() {
85
96
  return {
86
97
  isChecked: this.value === this.val,
87
- randomString: `${ randomStr() }-radio`,
98
+ randomString: `${ generateRandomAlphaString(12) }-radio`,
99
+ describeById: `${ generateRandomAlphaString(12) }-radio-described-id`,
88
100
  };
89
101
  },
90
102
 
@@ -165,11 +177,14 @@ export default defineComponent({
165
177
  @click.stop.prevent
166
178
  >
167
179
  <span
180
+ :id="radioOptionId"
168
181
  ref="custom"
169
182
  :class="[ isDisabled ? 'text-muted' : '', 'radio-custom']"
170
183
  :tabindex="isDisabled || preventFocusOnRadioGroups ? -1 : 0"
171
184
  :aria-label="label"
172
185
  :aria-checked="isChecked"
186
+ :aria-disabled="isDisabled"
187
+ :aria-describedby="descriptionKey || description ? describeById : undefined"
173
188
  role="radio"
174
189
  />
175
190
  <div class="labeling">
@@ -190,6 +205,7 @@ export default defineComponent({
190
205
  </label>
191
206
  <div
192
207
  v-if="descriptionKey || description"
208
+ :id="describeById"
193
209
  class="radio-button-outer-container-description"
194
210
  >
195
211
  <t
@@ -24,4 +24,64 @@ describe('component: RadioGroup', () => {
24
24
  expect(slot.disabled).toBe(disabled);
25
25
  });
26
26
  });
27
+
28
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
29
+ const inputLabel = 'some-label';
30
+ const ariaDescribedById = 'some-external-id';
31
+ const currValue = 'whatever';
32
+
33
+ const wrapper = mount(RadioGroup, {
34
+ propsData: {
35
+ name: 'some-name',
36
+ label: inputLabel,
37
+ value: currValue,
38
+ options: [{ label: currValue, value: currValue }]
39
+ },
40
+ attrs: { 'aria-describedby': ariaDescribedById }
41
+ });
42
+
43
+ const field = wrapper.find('[role="radiogroup"]');
44
+ const role = field.attributes('role');
45
+ const ariaLabel = field.attributes('aria-label');
46
+ const ariaDescribedBy = field.attributes('aria-describedby');
47
+ const ariaActiveDescendant = field.attributes('aria-activedescendant');
48
+
49
+ expect(ariaLabel).toBe(inputLabel);
50
+ expect(role).toBe('radiogroup');
51
+ expect(ariaActiveDescendant).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
52
+ expect(ariaDescribedBy).toBe(ariaDescribedById);
53
+
54
+ const radioOption = wrapper.find(`.radio-custom`);
55
+
56
+ // make sure we validate when using RadioGroup without custom slot data
57
+ // we do assign an ID that is important to get 'aria-activedescendant' working
58
+ expect(radioOption.attributes('id')).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
59
+ });
60
+
61
+ it('a11y: adding aria-label ($attrs) from parent should override label-based aria-label', async() => {
62
+ const inputLabel = 'some-label';
63
+ const overrideLabel = 'some-override-label';
64
+ const currValue = 'whatever';
65
+
66
+ const wrapper = mount(RadioGroup, {
67
+ propsData: {
68
+ name: 'some-name',
69
+ label: inputLabel,
70
+ value: currValue,
71
+ disabled: true,
72
+ options: [{ label: currValue, value: currValue }]
73
+ },
74
+ attrs: { 'aria-label': overrideLabel }
75
+ });
76
+
77
+ const field = wrapper.find('[role="radiogroup"]');
78
+ const ariaLabel = field.attributes('aria-label');
79
+ const ariaDisabled = field.attributes('aria-disabled');
80
+ const tabIndex = field.attributes('tabindex');
81
+
82
+ expect(ariaLabel).toBe(overrideLabel);
83
+ expect(ariaLabel).not.toBe(inputLabel);
84
+ expect(ariaDisabled).toBe('true');
85
+ expect(tabIndex).toBe('-1');
86
+ });
27
87
  });
@@ -2,11 +2,13 @@
2
2
  import { PropType, defineComponent } from 'vue';
3
3
  import { _VIEW } from '@shell/config/query-params';
4
4
  import RadioButton from '@components/Form/Radio/RadioButton.vue';
5
+ import { generateRandomAlphaString } from '@shell/utils/string';
5
6
 
6
7
  interface Option {
7
8
  value: unknown,
8
9
  label: string,
9
10
  description?: string,
11
+ radioOptionId?: string,
10
12
  }
11
13
 
12
14
  export default defineComponent({
@@ -106,7 +108,10 @@ export default defineComponent({
106
108
  emits: ['update:value'],
107
109
 
108
110
  data() {
109
- return { currFocusedElem: undefined as undefined | EventTarget | null };
111
+ return {
112
+ currFocusedElem: undefined as undefined | EventTarget | null,
113
+ radioOptionsIdPrefix: `radio-option-${ generateRandomAlphaString(12) }-`
114
+ };
110
115
  },
111
116
 
112
117
  computed: {
@@ -120,16 +125,21 @@ export default defineComponent({
120
125
  const opt = this.options[i];
121
126
 
122
127
  if (typeof opt === 'object' && opt) {
123
- out.push(opt);
128
+ out.push({
129
+ ...opt,
130
+ radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
131
+ });
124
132
  } else if (this.labels) {
125
133
  out.push({
126
- label: this.labels[i],
127
- value: opt
134
+ label: this.labels[i],
135
+ value: opt,
136
+ radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
128
137
  });
129
138
  } else {
130
139
  out.push({
131
- label: opt,
132
- value: opt
140
+ label: opt,
141
+ value: opt,
142
+ radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
133
143
  });
134
144
  }
135
145
  }
@@ -150,8 +160,36 @@ export default defineComponent({
150
160
  isDisabled(): boolean {
151
161
  return (this.disabled || this.isView);
152
162
  },
153
- radioGroupLabel(): string {
154
- return this.labelKey ? this.t(this.labelKey) : this.label ? this.label : '';
163
+ /**
164
+ * Radio Group Aria Label based on the label present on this input
165
+ */
166
+ radioGroupAriaLabel(): string | undefined {
167
+ // seems like VoiceOver screen reader isn't really picking up aria-labelledby
168
+ // let's just gather the label that comes in and assign it.
169
+ // We allow override with $attrs['aria-label'] for more control
170
+ if (this.$attrs['aria-label']) {
171
+ return this.$attrs['aria-label'] as string || undefined;
172
+ }
173
+
174
+ return this.labelKey ? this.t(this.labelKey) : this.label ? this.label : undefined;
175
+ },
176
+ /**
177
+ * Radio Group Aria DescribedBy parent attribute for extendability
178
+ */
179
+ radioGroupAriaDescribedBy(): string | undefined {
180
+ return this.$attrs['aria-describedby'] as string || undefined;
181
+ },
182
+ /**
183
+ * Radio Group value for aria-activedescendant HTML prop
184
+ */
185
+ ariaActiveDescendant(): string | undefined {
186
+ const activeOpt = this.normalizedOptions.find((opt) => opt.value === this.value);
187
+
188
+ if (this.value && activeOpt) {
189
+ return activeOpt.radioOptionId;
190
+ }
191
+
192
+ return '';
155
193
  }
156
194
  },
157
195
 
@@ -232,10 +270,13 @@ export default defineComponent({
232
270
  <div
233
271
  ref="radioGroup"
234
272
  role="radiogroup"
235
- :aria-label="radioGroupLabel"
273
+ :aria-label="radioGroupAriaLabel"
274
+ :aria-describedby="radioGroupAriaDescribedBy"
275
+ :aria-activedescendant="ariaActiveDescendant"
236
276
  class="radio-group"
237
277
  :class="{'row':row}"
238
- tabindex="0"
278
+ :tabindex="isDisabled ? -1 : 0"
279
+ :aria-disabled="isDisabled"
239
280
  @keydown.down.prevent.stop="clickNext(1)"
240
281
  @keydown.up.prevent.stop="clickNext(-1)"
241
282
  @keydown.space.enter.stop.prevent
@@ -255,6 +296,7 @@ export default defineComponent({
255
296
  :name="name"
256
297
  :value="value"
257
298
  :label="option.label"
299
+ :radio-option-id="option.radioOptionId"
258
300
  :description="option.description"
259
301
  :val="option.value"
260
302
  :disabled="isDisabled"
@@ -91,4 +91,21 @@ describe('toggleSwitch.vue', () => {
91
91
  expect(wrapper.emitted('update:value')).toHaveLength(1);
92
92
  expect(wrapper.emitted('update:value')[0][0]).toBe(offValue);
93
93
  });
94
+
95
+ it('adds focus class when input is focused', async() => {
96
+ const wrapper = shallowMount(ToggleSwitch);
97
+
98
+ await wrapper.find('input').trigger('focus');
99
+
100
+ expect(wrapper.find('.slider').classes()).toContain('focus');
101
+ });
102
+
103
+ it('removes focus class when input is blurred', async() => {
104
+ const wrapper = shallowMount(ToggleSwitch);
105
+
106
+ await wrapper.find('input').trigger('focus');
107
+ await wrapper.find('input').trigger('blur');
108
+
109
+ expect(wrapper.find('.slider').classes()).not.toContain('focus');
110
+ });
94
111
  });
@@ -54,6 +54,11 @@ export default defineComponent({
54
54
  switchInput.value?.removeEventListener('focus', focus);
55
55
  switchInput.value?.removeEventListener('blur', blur);
56
56
  });
57
+
58
+ return {
59
+ switchChrome,
60
+ switchInput,
61
+ };
57
62
  },
58
63
 
59
64
  data() {
@@ -26,7 +26,15 @@ export default defineComponent({
26
26
  hover: {
27
27
  type: Boolean,
28
28
  default: true
29
- }
29
+ },
30
+ /**
31
+ * Inherited global identifier prefix for tests
32
+ * Define a term based on the parent component to avoid conflicts on multiple components
33
+ */
34
+ componentTestid: {
35
+ type: String,
36
+ default: 'labeledTooltip-info-icon'
37
+ },
30
38
  },
31
39
  computed: {
32
40
  iconClass(): string {
@@ -64,6 +72,7 @@ export default defineComponent({
64
72
  :class="{'hover':!value, [iconClass]: true}"
65
73
  class="icon status-icon"
66
74
  tabindex="0"
75
+ :data-testid="componentTestid"
67
76
  />
68
77
  </template>
69
78
  <template v-else>
@@ -15,6 +15,7 @@ const buttonRoles: { role: keyof ButtonRoleProps, className: string }[] = [
15
15
  { role: 'secondary', className: 'role-secondary' },
16
16
  { role: 'tertiary', className: 'role-tertiary' },
17
17
  { role: 'link', className: 'role-link' },
18
+ { role: 'multiAction', className: 'role-multi-action' },
18
19
  { role: 'ghost', className: 'role-ghost' },
19
20
  ];
20
21
 
@@ -50,7 +51,7 @@ defineExpose({ focus });
50
51
  <button
51
52
  ref="RcFocusTarget"
52
53
  role="button"
53
- :class="{ ...buttonClass, ...($attrs.class || { }) }"
54
+ :class="{ ...buttonClass }"
54
55
  >
55
56
  <slot name="before">
56
57
  <!-- Empty Content -->
@@ -9,6 +9,7 @@ export type ButtonRoleProps = {
9
9
  secondary?: boolean;
10
10
  tertiary?: boolean;
11
11
  link?: boolean;
12
+ multiAction?: boolean;
12
13
  ghost?: boolean;
13
14
  }
14
15
 
@@ -24,9 +24,18 @@ import { ref } from 'vue';
24
24
  import { useClickOutside } from '@shell/composables/useClickOutside';
25
25
  import { useDropdownContext } from '@components/RcDropdown/useDropdownContext';
26
26
 
27
- defineProps<{
28
- ariaLabel?: string
29
- }>();
27
+ import type { Placement } from 'floating-vue';
28
+
29
+ withDefaults(
30
+ defineProps<{
31
+ // eslint-disable-next-line vue/require-default-prop
32
+ ariaLabel?: string;
33
+ // eslint-disable-next-line vue/require-default-prop
34
+ distance?: number;
35
+ placement?: Placement;
36
+ }>(),
37
+ { placement: 'bottom-end' }
38
+ );
30
39
 
31
40
  const emit = defineEmits(['update:open']);
32
41
 
@@ -49,7 +58,7 @@ useClickOutside(dropdownTarget, () => showMenu(false));
49
58
 
50
59
  const applyShow = () => {
51
60
  registerDropdownCollection(dropdownTarget.value);
52
- setFocus();
61
+ setFocus('down');
53
62
  };
54
63
 
55
64
  </script>
@@ -61,7 +70,8 @@ const applyShow = () => {
61
70
  :shown="isMenuOpen"
62
71
  :auto-hide="false"
63
72
  :container="popperContainer"
64
- :placement="'bottom-end'"
73
+ :placement="placement"
74
+ :distance="distance"
65
75
  @apply-show="applyShow"
66
76
  >
67
77
  <slot name="default">
@@ -78,7 +88,8 @@ const applyShow = () => {
78
88
  dropdown-menu-collection
79
89
  :aria-label="ariaLabel || 'Dropdown Menu'"
80
90
  @keydown="handleKeydown"
81
- @keydown.down="setFocus()"
91
+ @keydown.down.prevent="setFocus('down')"
92
+ @keydown.up.prevent="setFocus('up')"
82
93
  >
83
94
  <slot name="dropdownCollection">
84
95
  <!--Empty slot content-->
@@ -110,6 +121,7 @@ const applyShow = () => {
110
121
  }
111
122
 
112
123
  .v-popper__inner {
124
+ overflow: unset;
113
125
  padding: 10px 0 10px 0;
114
126
  }
115
127
  }
@@ -2,56 +2,12 @@
2
2
  /**
3
3
  * An item for a dropdown menu. Used in conjunction with RcDropdown.
4
4
  */
5
- import { inject } from 'vue';
6
- import { DropdownContext, defaultContext } from './types';
5
+ import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
7
6
 
8
7
  const props = defineProps({ disabled: Boolean });
9
8
  const emits = defineEmits(['click']);
10
9
 
11
- const { close, dropdownItems } = inject<DropdownContext>('dropdownContext') || defaultContext;
12
-
13
- /**
14
- * Handles keydown events to navigate between dropdown items.
15
- * @param {KeyboardEvent} e - The keydown event.
16
- */
17
- const handleKeydown = (e: KeyboardEvent) => {
18
- const activeItem = document.activeElement;
19
-
20
- const activeIndex = dropdownItems.value.indexOf(activeItem || new HTMLElement());
21
-
22
- if (activeIndex < 0) {
23
- return;
24
- }
25
-
26
- const shouldAdvance = e.key === 'ArrowDown';
27
-
28
- const newIndex = findNewIndex(shouldAdvance, activeIndex, dropdownItems.value);
29
-
30
- if (dropdownItems.value[newIndex] instanceof HTMLElement) {
31
- dropdownItems.value[newIndex].focus();
32
- }
33
- };
34
-
35
- /**
36
- * Finds the new index for the dropdown item based on the key pressed.
37
- * @param shouldAdvance - Whether to advance to the next or previous item.
38
- * @param activeIndex - Current active index.
39
- * @param itemsArr - Array of dropdown items.
40
- * @returns The new index.
41
- */
42
- const findNewIndex = (shouldAdvance: boolean, activeIndex: number, itemsArr: Element[]) => {
43
- const newIndex = shouldAdvance ? activeIndex + 1 : activeIndex - 1;
44
-
45
- if (newIndex > itemsArr.length - 1) {
46
- return 0;
47
- }
48
-
49
- if (newIndex < 0) {
50
- return itemsArr.length - 1;
51
- }
52
-
53
- return newIndex;
54
- };
10
+ const { handleKeydown, close, handleActivate } = useDropdownItem();
55
11
 
56
12
  const handleClick = (e: MouseEvent) => {
57
13
  if (props.disabled) {
@@ -62,15 +18,6 @@ const handleClick = (e: MouseEvent) => {
62
18
  close();
63
19
  };
64
20
 
65
- /**
66
- * Handles keydown events to activate the dropdown item.
67
- * @param e - The keydown event.
68
- */
69
- const handleActivate = (e: KeyboardEvent) => {
70
- if (e?.target instanceof HTMLElement) {
71
- e?.target?.click();
72
- }
73
- };
74
21
  </script>
75
22
 
76
23
  <template>
@@ -83,7 +30,7 @@ const handleActivate = (e: KeyboardEvent) => {
83
30
  :aria-disabled="disabled || false"
84
31
  @click.stop="handleClick"
85
32
  @keydown.enter.space="handleActivate"
86
- @keydown.up.down.stop="handleKeydown"
33
+ @keydown.up.down.prevent.stop="handleKeydown"
87
34
  >
88
35
  <slot name="before">
89
36
  <!--Empty slot content-->
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * An item for a dropdown menu. Used in conjunction with RcDropdown.
4
+ */
5
+ import { Checkbox as RcCheckbox } from '@components/Form/Checkbox';
6
+ import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
7
+
8
+ const props = defineProps({ modelValue: Boolean, disabled: Boolean });
9
+ const emits = defineEmits(['click']);
10
+
11
+ const { handleKeydown, handleActivate } = useDropdownItem();
12
+
13
+ const handleClick = () => {
14
+ if (props.disabled) {
15
+ return;
16
+ }
17
+
18
+ emits('click', !props.modelValue);
19
+ };
20
+ </script>
21
+
22
+ <template>
23
+ <div
24
+ ref="dropdownMenuItem"
25
+ dropdown-menu-item
26
+ tabindex="-1"
27
+ role="menuitemcheckbox"
28
+ :disabled="disabled || null"
29
+ :aria-disabled="disabled || false"
30
+ @click.stop="handleClick"
31
+ @keydown.enter.space="handleActivate"
32
+ @keydown.up.down.prevent.stop="handleKeydown"
33
+ >
34
+ <rc-checkbox :value="modelValue">
35
+ <template #label>
36
+ <slot name="default">
37
+ <!--Empty slot content-->
38
+ </slot>
39
+ </template>
40
+ </rc-checkbox>
41
+ </div>
42
+ </template>
43
+
44
+ <style lang="scss" scoped>
45
+ [dropdown-menu-item] {
46
+ display: flex;
47
+ gap: 8px;
48
+ align-items: center;
49
+ padding: 9px 8px;
50
+ margin: 0 9px;
51
+ border-radius: 4px;
52
+
53
+ &:hover {
54
+ cursor: pointer;
55
+ background-color: var(--dropdown-hover-bg);
56
+ }
57
+ &:focus-visible, &:focus {
58
+ @include focus-outline;
59
+ outline-offset: 0;
60
+ }
61
+ &[disabled] {
62
+ color: var(--disabled-text);
63
+ &:hover {
64
+ cursor: not-allowed;
65
+ }
66
+ }
67
+ }
68
+ </style>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * An item for a dropdown menu. Used in conjunction with RcDropdown.
4
+ */
5
+ import LabeledSelect from '@shell/components/form/LabeledSelect';
6
+ import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
7
+ import { ref } from 'vue';
8
+
9
+ type LabeledSelectComponent = {
10
+ focusSearch: () => void;
11
+ };
12
+
13
+ defineProps({
14
+ modelValue: {
15
+ type: String,
16
+ default: ''
17
+ },
18
+ disabled: Boolean,
19
+ options: {
20
+ type: Array,
21
+ default() {
22
+ return [];
23
+ }
24
+ },
25
+ });
26
+ const emits = defineEmits(['click', 'select']);
27
+
28
+ const { handleKeydown, handleActivate } = useDropdownItem();
29
+
30
+ const dropdownMenuItem = ref<HTMLDivElement | null>(null);
31
+ const menuItemSelect = ref<LabeledSelectComponent | null>(null);
32
+
33
+ const handleClick = () => {
34
+ menuItemSelect?.value?.focusSearch();
35
+ };
36
+
37
+ const focusMenuItem = () => {
38
+ dropdownMenuItem?.value?.focus();
39
+ };
40
+ </script>
41
+
42
+ <template>
43
+ <div
44
+ ref="dropdownMenuItem"
45
+ dropdown-menu-item
46
+ tabindex="-1"
47
+ role="menuitem"
48
+ :disabled="disabled || null"
49
+ :aria-disabled="disabled || false"
50
+ @click.stop="handleClick"
51
+ @keydown.enter.space="handleActivate"
52
+ @keydown.up.down.prevent.stop="handleKeydown"
53
+ >
54
+ <LabeledSelect
55
+ ref="menuItemSelect"
56
+ :value="modelValue"
57
+ :label="t('wm.containerLogs.range.label')"
58
+ :options="options"
59
+ :clearable="false"
60
+ placement="top"
61
+ @keydown.enter.stop
62
+ @update:value="emits('select', $event)"
63
+ @on-close="focusMenuItem"
64
+ />
65
+ </div>
66
+ </template>
67
+
68
+ <style lang="scss" scoped>
69
+ [dropdown-menu-item] {
70
+ display: flex;
71
+ gap: 8px;
72
+ align-items: center;
73
+ padding: 9px 8px;
74
+ margin: 0 9px;
75
+ border-radius: 4px;
76
+
77
+ &:hover {
78
+ cursor: pointer;
79
+ background-color: var(--dropdown-hover-bg);
80
+ }
81
+ &:focus-visible, &:focus {
82
+ @include focus-outline;
83
+ outline-offset: 0;
84
+ }
85
+ &[disabled] {
86
+ color: var(--disabled-text);
87
+ &:hover {
88
+ cursor: not-allowed;
89
+ }
90
+ }
91
+ }
92
+ </style>
@@ -35,8 +35,18 @@ defineExpose({ focus });
35
35
  @keydown.enter.space="handleKeydown"
36
36
  @click="showMenu(true)"
37
37
  >
38
+ <template #before>
39
+ <slot name="before">
40
+ <!-- Empty Content -->
41
+ </slot>
42
+ </template>
38
43
  <slot name="default">
39
44
  <!--Empty slot content-->
40
45
  </slot>
46
+ <template #after>
47
+ <slot name="after">
48
+ <!-- Empty Content -->
49
+ </slot>
50
+ </template>
41
51
  </RcButton>
42
52
  </template>
@@ -1,5 +1,7 @@
1
1
  export { default as RcDropdown } from './RcDropdown.vue';
2
2
  export { default as RcDropdownItem } from './RcDropdownItem.vue';
3
+ export { default as RcDropdownItemCheckbox } from './RcDropdownItemCheckbox.vue';
4
+ export { default as RcDropdownItemSelect } from './RcDropdownItemSelect.vue';
3
5
  export { default as RcDropdownSeparator } from './RcDropdownSeparator.vue';
4
6
  export { default as RcDropdownTrigger } from './RcDropdownTrigger.vue';
5
7
  export { default as RcDropdownMenu } from './RcDropdownMenu.vue';
@@ -10,6 +10,7 @@ export const useDropdownCollection = () => {
10
10
  const dropdownItems = ref<Element[]>([]);
11
11
  const dropdownContainer = ref<HTMLElement | null>(null);
12
12
  const firstDropdownItem = ref<HTMLElement | null>(null);
13
+ const lastDropdownItem = ref<HTMLElement | null>(null);
13
14
 
14
15
  /**
15
16
  * Registers the dropdown container and initializes dropdown items.
@@ -22,6 +23,12 @@ export const useDropdownCollection = () => {
22
23
  if (dropdownItems.value[0] instanceof HTMLElement) {
23
24
  firstDropdownItem.value = dropdownItems.value[0];
24
25
  }
26
+
27
+ const lastItem = dropdownItems.value[dropdownItems.value.length - 1];
28
+
29
+ if (lastItem instanceof HTMLElement) {
30
+ lastDropdownItem.value = lastItem;
31
+ }
25
32
  }
26
33
  };
27
34
 
@@ -40,6 +47,7 @@ export const useDropdownCollection = () => {
40
47
  return {
41
48
  dropdownItems,
42
49
  firstDropdownItem,
50
+ lastDropdownItem,
43
51
  dropdownContainer,
44
52
  registerDropdownCollection,
45
53
  };