@gitlab/ui 32.28.0 → 32.29.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [32.29.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v32.28.0...v32.29.0) (2021-10-29)
2
+
3
+
4
+ ### Features
5
+
6
+ * **Toggle:** Update label semantics for accessibility ([a16af22](https://gitlab.com/gitlab-org/gitlab-ui/commit/a16af22246dbd4e886ca8ac4640bf9c44da5c995))
7
+
1
8
  # [32.28.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v32.27.0...v32.28.0) (2021-10-28)
2
9
 
3
10
 
@@ -66,7 +66,15 @@ var script = {
66
66
  },
67
67
 
68
68
  /**
69
- * The label's position relative to the toggle.
69
+ * The id for the label. This id is used by the aria-labelledby attribute on the toggle button.
70
+ */
71
+ labelId: {
72
+ type: String,
73
+ required: true
74
+ },
75
+
76
+ /**
77
+ * The label's position relative to the toggle. If 'hidden', the toggle will add the .gl-sr-only class so the label is still accessible to screen readers.
70
78
  */
71
79
  labelPosition: {
72
80
  type: String,
@@ -84,10 +92,6 @@ var script = {
84
92
  return this.value ? 'mobile-issue-close' : 'close';
85
93
  },
86
94
 
87
- shouldShowLabel() {
88
- return this.label && this.labelPosition !== 'hidden';
89
- },
90
-
91
95
  helpId() {
92
96
  return this.help ? `toggle-help-${this.uuid}` : undefined;
93
97
  },
@@ -123,11 +127,11 @@ var script = {
123
127
  const __vue_script__ = script;
124
128
 
125
129
  /* template */
126
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('label',{staticClass:"gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content"},[_c('span',{staticClass:"gl-toggle-wrapper",class:{ 'gl-toggle-label-inline': _vm.labelPosition === 'left', 'is-disabled': _vm.disabled }},[(_vm.shouldShowLabel)?_c('span',{staticClass:"gl-toggle-label",attrs:{"data-testid":"toggle-label"}},[_vm._t("label",[_vm._v(_vm._s(_vm.label))])],2):_vm._e(),_vm._v(" "),(_vm.name)?_c('input',{attrs:{"name":_vm.name,"type":"hidden"},domProps:{"value":_vm.value}}):_vm._e(),_vm._v(" "),_c('button',{class:{
127
- 'gl-toggle': true,
128
- 'is-checked': _vm.value,
129
- 'is-disabled': _vm.disabled,
130
- },attrs:{"role":"switch","aria-checked":_vm.isChecked,"aria-label":_vm.label,"aria-describedby":_vm.helpId,"type":"button"},on:{"click":function($event){$event.preventDefault();return _vm.toggleFeature($event)}}},[(_vm.isLoading)?_c('gl-loading-icon',{staticClass:"toggle-loading",attrs:{"color":"light"}}):_c('span',{class:{ 'toggle-icon': true, disabled: _vm.disabled }},[_c('gl-icon',{attrs:{"name":_vm.icon,"size":16}})],1)],1)]),_vm._v(" "),(_vm.help)?_c('span',{staticClass:"gl-help-label",attrs:{"id":_vm.helpId,"data-testid":"toggle-help"}},[_vm._t("help",[_vm._v(_vm._s(_vm.help))])],2):_vm._e()])};
130
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-toggle-wrapper gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content",class:{ 'gl-toggle-label-inline': _vm.labelPosition === 'left', 'is-disabled': _vm.disabled }},[_c('span',{staticClass:"gl-toggle-label",class:{ 'gl-sr-only': _vm.labelPosition === 'hidden' },attrs:{"id":_vm.labelId,"data-testid":"toggle-label"}},[_vm._t("label",[_vm._v(_vm._s(_vm.label))])],2),_vm._v(" "),(_vm.name)?_c('input',{attrs:{"name":_vm.name,"type":"hidden"},domProps:{"value":_vm.value}}):_vm._e(),_vm._v(" "),_c('button',{class:{
131
+ 'gl-toggle': true,
132
+ 'is-checked': _vm.value,
133
+ 'is-disabled': _vm.disabled,
134
+ },attrs:{"role":"switch","aria-checked":_vm.isChecked,"aria-labelledby":_vm.labelId,"aria-describedby":_vm.helpId,"type":"button"},on:{"click":function($event){$event.preventDefault();return _vm.toggleFeature($event)}}},[(_vm.isLoading)?_c('gl-loading-icon',{staticClass:"toggle-loading",attrs:{"color":"light"}}):_c('span',{class:{ 'toggle-icon': true, disabled: _vm.disabled }},[_c('gl-icon',{attrs:{"name":_vm.icon,"size":16}})],1)],1),_vm._v(" "),(_vm.help)?_c('span',{staticClass:"gl-help-label",attrs:{"id":_vm.helpId,"data-testid":"toggle-help"}},[_vm._t("help",[_vm._v(_vm._s(_vm.help))])],2):_vm._e()])};
131
135
  var __vue_staticRenderFns__ = [];
132
136
 
133
137
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "32.28.0",
3
+ "version": "32.29.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -8,12 +8,14 @@ describe('toggle', () => {
8
8
  let wrapper;
9
9
 
10
10
  const label = 'toggle label';
11
+ const labelId = 'toggle-label-id';
11
12
  const helpText = 'help text';
12
13
 
13
14
  const createWrapper = (props = {}) => {
14
15
  wrapper = shallowMount(Toggle, {
15
16
  propsData: {
16
17
  label,
18
+ labelId,
17
19
  ...props,
18
20
  },
19
21
  });
@@ -24,7 +26,6 @@ describe('toggle', () => {
24
26
 
25
27
  afterEach(() => {
26
28
  wrapper.destroy();
27
- wrapper = null;
28
29
  });
29
30
 
30
31
  it('has role=switch', () => {
@@ -112,21 +113,25 @@ describe('toggle', () => {
112
113
 
113
114
  describe('label position', () => {
114
115
  describe.each`
115
- state | labelPosition | labelVisibility
116
- ${'top'} | ${toggleLabelPosition.top} | ${true}
117
- ${'left'} | ${toggleLabelPosition.left} | ${true}
118
- ${'hidden'} | ${toggleLabelPosition.hidden} | ${false}
119
- `('when $state', ({ labelPosition, labelVisibility }) => {
116
+ state | labelPosition | hasGlSrOnlyClass
117
+ ${'top'} | ${toggleLabelPosition.top} | ${false}
118
+ ${'left'} | ${toggleLabelPosition.left} | ${false}
119
+ ${'hidden'} | ${toggleLabelPosition.hidden} | ${true}
120
+ `('when $state', ({ labelPosition, hasGlSrOnlyClass }) => {
120
121
  beforeEach(() => {
121
122
  createWrapper({ labelPosition });
122
123
  });
123
124
 
124
- it(`${labelVisibility ? 'shows' : 'hides'} label`, () => {
125
- expect(wrapper.find('[data-testid="toggle-label"]').exists()).toBe(labelVisibility);
125
+ it(`${hasGlSrOnlyClass ? 'adds' : 'does not add'} 'gl-sr-only' class to the label`, () => {
126
+ const cssClasses = wrapper.find('[data-testid="toggle-label"]').classes();
127
+
128
+ return hasGlSrOnlyClass
129
+ ? expect(cssClasses).toContain('gl-sr-only')
130
+ : expect(cssClasses).not.toContain('gl-sr-only');
126
131
  });
127
132
 
128
133
  it('has accessible name for the button', () => {
129
- expect(findButton().attributes('aria-label')).toBe(label);
134
+ expect(findButton().attributes('aria-labelledby')).toBe(labelId);
130
135
  });
131
136
  });
132
137
  });
@@ -9,6 +9,7 @@ const generateProps = ({
9
9
  disabled = defaultValue('disabled'),
10
10
  isLoading = defaultValue('isLoading'),
11
11
  label = 'Dark mode',
12
+ labelId = 'dark-mode-toggle',
12
13
  help = 'Toggle dark mode for the website',
13
14
  labelPosition = defaultValue('labelPosition'),
14
15
  } = {}) => ({
@@ -16,6 +17,7 @@ const generateProps = ({
16
17
  disabled,
17
18
  isLoading,
18
19
  label,
20
+ labelId,
19
21
  help,
20
22
  labelPosition,
21
23
  });
@@ -29,6 +31,7 @@ const Template = (args, { argTypes }) => ({
29
31
  v-model="value"
30
32
  :disabled="disabled"
31
33
  :help="help"
34
+ :label-id="labelId"
32
35
  :is-loading="isLoading"
33
36
  :label="label"
34
37
  :label-position="labelPosition"
@@ -61,7 +61,14 @@ export default {
61
61
  default: undefined,
62
62
  },
63
63
  /**
64
- * The label's position relative to the toggle.
64
+ * The id for the label. This id is used by the aria-labelledby attribute on the toggle button.
65
+ */
66
+ labelId: {
67
+ type: String,
68
+ required: true,
69
+ },
70
+ /**
71
+ * The label's position relative to the toggle. If 'hidden', the toggle will add the .gl-sr-only class so the label is still accessible to screen readers.
65
72
  */
66
73
  labelPosition: {
67
74
  type: String,
@@ -77,9 +84,6 @@ export default {
77
84
  icon() {
78
85
  return this.value ? 'mobile-issue-close' : 'close';
79
86
  },
80
- shouldShowLabel() {
81
- return this.label && this.labelPosition !== 'hidden';
82
- },
83
87
  helpId() {
84
88
  return this.help ? `toggle-help-${this.uuid}` : undefined;
85
89
  },
@@ -110,38 +114,41 @@ export default {
110
114
  </script>
111
115
 
112
116
  <template>
113
- <label class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content">
117
+ <div
118
+ class="gl-toggle-wrapper gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content"
119
+ :class="{ 'gl-toggle-label-inline': labelPosition === 'left', 'is-disabled': disabled }"
120
+ >
114
121
  <span
115
- class="gl-toggle-wrapper"
116
- :class="{ 'gl-toggle-label-inline': labelPosition === 'left', 'is-disabled': disabled }"
122
+ :id="labelId"
123
+ :class="{ 'gl-sr-only': labelPosition === 'hidden' }"
124
+ class="gl-toggle-label"
125
+ data-testid="toggle-label"
117
126
  >
118
- <span v-if="shouldShowLabel" class="gl-toggle-label" data-testid="toggle-label">
119
- <!-- @slot The toggle's label. -->
120
- <slot name="label">{{ label }}</slot>
121
- </span>
122
- <input v-if="name" :name="name" :value="value" type="hidden" />
123
- <button
124
- role="switch"
125
- :aria-checked="isChecked"
126
- :aria-label="label"
127
- :aria-describedby="helpId"
128
- :class="{
129
- 'gl-toggle': true,
130
- 'is-checked': value,
131
- 'is-disabled': disabled,
132
- }"
133
- type="button"
134
- @click.prevent="toggleFeature"
135
- >
136
- <gl-loading-icon v-if="isLoading" color="light" class="toggle-loading" />
137
- <span v-else :class="{ 'toggle-icon': true, disabled: disabled }">
138
- <gl-icon :name="icon" :size="16" />
139
- </span>
140
- </button>
127
+ <!-- @slot The toggle's label. -->
128
+ <slot name="label">{{ label }}</slot>
141
129
  </span>
130
+ <input v-if="name" :name="name" :value="value" type="hidden" />
131
+ <button
132
+ role="switch"
133
+ :aria-checked="isChecked"
134
+ :aria-labelledby="labelId"
135
+ :aria-describedby="helpId"
136
+ :class="{
137
+ 'gl-toggle': true,
138
+ 'is-checked': value,
139
+ 'is-disabled': disabled,
140
+ }"
141
+ type="button"
142
+ @click.prevent="toggleFeature"
143
+ >
144
+ <gl-loading-icon v-if="isLoading" color="light" class="toggle-loading" />
145
+ <span v-else :class="{ 'toggle-icon': true, disabled: disabled }">
146
+ <gl-icon :name="icon" :size="16" />
147
+ </span>
148
+ </button>
142
149
  <span v-if="help" :id="helpId" class="gl-help-label" data-testid="toggle-help">
143
150
  <!-- @slot A help text to be shown below the toggle. -->
144
151
  <slot name="help">{{ help }}</slot>
145
152
  </span>
146
- </label>
153
+ </div>
147
154
  </template>