@gitlab/ui 101.4.0 → 101.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "101.4.0",
3
+ "version": "101.5.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1 +1,26 @@
1
- Button group of equal options where only one can be selected and active.
1
+ A customizable button group that displays a set of equal options, where only one
2
+ option can be active at a time. This component includes the ability to disable
3
+ specific options and dynamically modify button content using slots.
4
+
5
+ ## Features
6
+
7
+ - Displays a group of selectable buttons.
8
+ - Allows only one active selection at a time.
9
+ - Supports content customization through the button-content slot.
10
+ - Options can be disabled individually.
11
+
12
+ ## Props Validation
13
+
14
+ The `options` prop is validated against a specific structure to ensure consistent
15
+ data. Each option must include:
16
+
17
+ - `value`: A `string`, `number`, or `boolean` to identify the option.
18
+ - `disabled`: A `boolean` (or `undefined`) indicating whether the option is disabled.
19
+
20
+ Optionally it can include:
21
+
22
+ - `text`: A `string` which gets displayed in the slot content.
23
+
24
+ ## Notes
25
+
26
+ - Ensure each value is unique within the options array for consistent behavior.
@@ -1,77 +1,55 @@
1
1
  <script>
2
- import { BFormRadioGroup } from '../../../vendor/bootstrap-vue/src/components/form-radio/form-radio-group';
2
+ import GlButtonGroup from '../button_group/button_group.vue';
3
+ import GlButton from '../button/button.vue';
3
4
 
4
- const genericErrorMessage = 'Segmented button should always have valid option selected';
5
+ const validateOptionsProp = (options) => {
6
+ const requiredOptionPropType = {
7
+ value: ['string', 'number', 'boolean'],
8
+ disabled: ['boolean', 'undefined'],
9
+ };
10
+ const optionProps = Object.keys(requiredOptionPropType);
11
+
12
+ return options.every((option) => {
13
+ if (!option) {
14
+ return false;
15
+ }
16
+ return optionProps.every((name) => requiredOptionPropType[name].includes(typeof option[name]));
17
+ });
18
+ };
5
19
 
6
20
  export default {
7
21
  name: 'GlSegmentedControl',
8
22
  components: {
9
- BFormRadioGroup,
10
- },
11
- inheritAttrs: false,
12
- model: {
13
- prop: 'checked',
14
- event: 'input',
23
+ GlButtonGroup,
24
+ GlButton,
15
25
  },
16
26
  props: {
17
- checked: {
18
- required: true,
19
- validator: () => true,
20
- },
21
27
  options: {
22
28
  type: Array,
23
29
  required: true,
30
+ validator: validateOptionsProp,
24
31
  },
25
- },
26
- computed: {
27
- enabledOptions() {
28
- return this.options.filter((option) => !option.disabled);
29
- },
30
- },
31
- watch: {
32
- checked: {
33
- handler(newValue, oldValue) {
34
- this.checkValue(newValue, oldValue);
35
- },
36
- },
37
- options: {
38
- handler() {
39
- this.checkValue(this.checked);
40
- },
41
- },
42
- },
43
- created() {
44
- this.checkValue(this.checked);
45
- },
46
- methods: {
47
- checkValue(newValue, oldValue = null) {
48
- if (!this.isValidValue(newValue)) {
49
- // eslint-disable-next-line no-console
50
- console.warn(genericErrorMessage);
51
- if (this.enabledOptions.length) {
52
- const suggestion =
53
- oldValue && this.isValidValue(oldValue) ? oldValue : this.enabledOptions[0].value;
54
- /**
55
- * Emitted when the selection changes
56
- * @event input
57
- * @argument checked The selected option
58
- */
59
- this.$emit('input', suggestion);
60
- }
61
- }
62
- },
63
- isValidValue(val) {
64
- return this.enabledOptions.some(({ value }) => value === val);
32
+ value: {
33
+ type: [String, Number, Boolean],
34
+ required: true,
65
35
  },
66
36
  },
67
37
  };
68
38
  </script>
39
+
69
40
  <template>
70
- <b-form-radio-group
71
- buttons
72
- button-variant="gl-segmented-button gl-button"
73
- class="gl-segmented-control"
74
- v-bind="{ ...$attrs, options, checked }"
75
- v-on="$listeners"
76
- />
41
+ <gl-button-group>
42
+ <gl-button
43
+ v-for="option in options"
44
+ :key="option.value"
45
+ :disabled="!!option.disabled"
46
+ :selected="value === option.value"
47
+ v-bind="option.props"
48
+ @click="$emit('input', option.value)"
49
+ >
50
+ <slot name="button-content" v-bind="option">
51
+ {{ option.text }}
52
+ </slot>
53
+ </gl-button>
54
+ </gl-button-group>
77
55
  </template>
@@ -61,7 +61,6 @@
61
61
  @import '../components/base/progress_bar/progress_bar';
62
62
  @import '../components/base/search_box_by_type/search_box_by_type';
63
63
  @import '../components/base/search_box_by_click/search_box_by_click';
64
- @import '../components/base/segmented_control/segmented_control';
65
64
  @import '../components/base/skeleton_loader/skeleton_loader';
66
65
  @import '../components/base/tabs/tabs/tabs';
67
66
  @import '../components/base/toast/toast';
@@ -1,189 +0,0 @@
1
- /*
2
- * Segmented-control-specific utilities
3
- */
4
-
5
- @mixin gl-btn-gl-segmented-button-first {
6
- box-shadow:
7
- inset 0 #{$gl-border-size-1} 0 0 $gray-200,
8
- inset 0 -#{$gl-border-size-1} 0 0 $gray-200,
9
- inset #{$gl-border-size-1} 0 0 0 $gray-200;
10
- }
11
-
12
- @mixin gl-btn-gl-segmented-button-last {
13
- box-shadow:
14
- inset 0 #{$gl-border-size-1} 0 0 $gray-200,
15
- inset 0 -#{$gl-border-size-1} 0 0 $gray-200,
16
- inset -#{$gl-border-size-1} 0 0 0 $gray-200;
17
- }
18
-
19
- @mixin gl-btn-gl-segmented-button-focus($color) {
20
- box-shadow:
21
- $focus-ring,
22
- inset 0 0 0 $gl-border-size-2 $color;
23
- }
24
-
25
- .btn-group.gl-segmented-control {
26
- .btn:not(:first-child),
27
- .btn:not(:last-child) {
28
- &.active,
29
- &.focus,
30
- &:hover:not(.disabled) {
31
- @apply gl-rounded-base;
32
- }
33
- }
34
- }
35
-
36
- @if $feature-button-border {
37
- .gl-segmented-control {
38
- label:not(.disabled) {
39
- @apply gl-cursor-pointer;
40
- }
41
-
42
- .btn-gl-segmented-button {
43
- @apply gl-text-base;
44
- @apply gl-leading-normal;
45
- @apply gl-text-gray-900;
46
- @apply gl-fill-current;
47
- @apply gl-bg-gray-10;
48
- border-color: $gray-200;
49
-
50
- &:not(:first-child) {
51
- border-left-color: transparent;
52
- }
53
-
54
- &:not(:last-child) {
55
- border-right-color: transparent;
56
- }
57
-
58
- &:hover {
59
- border-color: $gray-400;
60
- @apply gl-bg-gray-50;
61
- }
62
-
63
- &.focus {
64
- @apply gl-z-1;
65
- border-color: $gray-400;
66
- @apply gl-focus;
67
- @apply gl-bg-gray-50;
68
- }
69
-
70
- &.active {
71
- @apply gl-z-2;
72
- border-color: $gray-400;
73
- box-shadow: inset 0 0 0 $gl-border-size-1 $gray-400;
74
- @apply gl-bg-white;
75
-
76
- &:hover {
77
- box-shadow: inset 0 0 0 $gl-border-size-1 $gray-400;
78
- @apply gl-bg-gray-50;
79
- }
80
-
81
- &.focus,
82
- &.focus:hover {
83
- border-color: $gray-400;
84
- @apply gl-focus;
85
- @apply gl-bg-gray-50;
86
- }
87
- }
88
-
89
- &:focus-within {
90
- @apply gl-focus;
91
- }
92
-
93
- &.disabled,
94
- &[disabled],
95
- &.disabled:hover,
96
- &[disabled]:hover {
97
- @apply gl-text-gray-400;
98
- border-color: $gray-200;
99
- @apply gl-z-0;
100
- @apply gl-cursor-not-allowed;
101
-
102
- &:first-child {
103
- border-right-color: transparent;
104
- }
105
-
106
- &:last-child {
107
- border-left-color: transparent;
108
- }
109
- }
110
- }
111
- }
112
- } @else {
113
- .gl-segmented-control {
114
- label:not(.disabled) {
115
- @apply gl-cursor-pointer;
116
- }
117
-
118
- .btn-gl-segmented-button {
119
- @apply gl-text-base;
120
- @apply gl-leading-normal;
121
- @apply gl-text-gray-900;
122
- @apply gl-fill-current;
123
- @apply gl-bg-gray-10;
124
- box-shadow:
125
- inset 0 #{$gl-border-size-1} 0 0 $gray-200,
126
- inset 0 -#{$gl-border-size-1} 0 0 $gray-200;
127
-
128
- &:first-child {
129
- @include gl-btn-gl-segmented-button-first;
130
- }
131
-
132
- &:last-child {
133
- @include gl-btn-gl-segmented-button-last;
134
- }
135
-
136
- &:hover {
137
- box-shadow: inset 0 0 0 $gl-border-size-2 $gray-400;
138
- @apply gl-bg-gray-50;
139
- }
140
-
141
- &.focus {
142
- @apply gl-z-1;
143
- @include gl-btn-gl-segmented-button-focus($gray-400);
144
- @apply gl-bg-gray-50;
145
- }
146
-
147
- &.active {
148
- @apply gl-z-2;
149
- box-shadow: inset 0 0 0 $gl-border-size-2 $gray-300;
150
- @apply gl-bg-white;
151
-
152
- &:hover {
153
- box-shadow: inset 0 0 0 $gl-border-size-2 $gray-400;
154
- @apply gl-bg-gray-50;
155
- }
156
-
157
- &.focus,
158
- &.focus:hover {
159
- @include gl-btn-gl-segmented-button-focus($gray-400);
160
- @apply gl-bg-gray-50;
161
- }
162
- }
163
-
164
- &:focus-within {
165
- @apply gl-focus;
166
- }
167
-
168
- &.disabled,
169
- &[disabled],
170
- &.disabled:hover,
171
- &[disabled]:hover {
172
- @apply gl-text-gray-400;
173
- box-shadow:
174
- inset 0 #{$gl-border-size-1} 0 0 $gray-200,
175
- inset 0 -#{$gl-border-size-1} 0 0 $gray-200;
176
- @apply gl-z-0;
177
- @apply gl-cursor-not-allowed;
178
-
179
- &:first-child {
180
- @include gl-btn-gl-segmented-button-first;
181
- }
182
-
183
- &:last-child {
184
- @include gl-btn-gl-segmented-button-last;
185
- }
186
- }
187
- }
188
- }
189
- }