@gitlab/ui 49.5.1 → 49.6.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 +7 -0
- package/dist/components/base/button/button.js +3 -3
- package/dist/components/base/dropdown/dropdown.js +1 -1
- package/dist/components/base/form/form_date/form_date.js +143 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +1 -1
- package/dist/components/base/new_dropdowns/listbox/listbox.js +1 -1
- package/dist/components/mixins/button_mixin.js +2 -2
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/utils/constants.js +1 -5
- package/package.json +2 -2
- package/src/components/base/button/button.vue +2 -3
- package/src/components/base/dropdown/dropdown.vue +1 -1
- package/src/components/base/form/form_date/form_date.md +26 -0
- package/src/components/base/form/form_date/form_date.scss +7 -0
- package/src/components/base/form/form_date/form_date.spec.js +85 -0
- package/src/components/base/form/form_date/form_date.stories.js +100 -0
- package/src/components/base/form/form_date/form_date.vue +135 -0
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +1 -1
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +1 -1
- package/src/components/base/new_dropdowns/listbox/listbox.vue +1 -1
- package/src/components/mixins/button_mixin.js +2 -2
- package/src/scss/components.scss +1 -0
- package/src/utils/constants.js +0 -5
package/dist/utils/constants.js
CHANGED
|
@@ -105,10 +105,6 @@ const dropdownVariantOptions = {
|
|
|
105
105
|
link: 'link'
|
|
106
106
|
};
|
|
107
107
|
const buttonSizeOptions = {
|
|
108
|
-
small: 'small',
|
|
109
|
-
medium: 'medium'
|
|
110
|
-
};
|
|
111
|
-
const buttonSizeOptionsMap = {
|
|
112
108
|
small: 'sm',
|
|
113
109
|
medium: 'md'
|
|
114
110
|
};
|
|
@@ -256,4 +252,4 @@ const loadingIconSizes = {
|
|
|
256
252
|
'xl (64x64)': 'xl'
|
|
257
253
|
};
|
|
258
254
|
|
|
259
|
-
export { COMMA, LEFT_MOUSE_BUTTON, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeIconSizeOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions,
|
|
255
|
+
export { COMMA, LEFT_MOUSE_BUTTON, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeIconSizeOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions, buttonVariantOptions, colorThemes, columnOptions, datepickerSizeOptionsMap, defaultDateFormat, drawerVariants, dropdownVariantOptions, focusableTags, formInputSizes, formStateOptions, glThemes, iconSizeOptions, keyboard, labelColorOptions, labelSizeOptions, loadingIconSizes, maxZIndex, modalButtonDefaults, modalSizeOptions, popoverPlacements, resizeDebounceTime, tabsButtonDefaults, targetOptions, toggleLabelPosition, tokenVariants, tooltipActionEvents, tooltipDelay, tooltipPlacements, triggerVariantOptions, truncateOptions, variantCssColorMap, variantOptions, variantOptionsWithNoDefault, viewModeOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "49.
|
|
3
|
+
"version": "49.6.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"@babel/preset-env": "^7.19.4",
|
|
83
83
|
"@gitlab/eslint-plugin": "18.1.0",
|
|
84
84
|
"@gitlab/stylelint-config": "4.1.0",
|
|
85
|
-
"@gitlab/svgs": "3.
|
|
85
|
+
"@gitlab/svgs": "3.7.0",
|
|
86
86
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
87
87
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
88
88
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
buttonCategoryOptions,
|
|
6
6
|
buttonVariantOptions,
|
|
7
7
|
buttonSizeOptions,
|
|
8
|
-
buttonSizeOptionsMap,
|
|
9
8
|
} from '../../../utils/constants';
|
|
10
9
|
import { logWarning } from '../../../utils/utils';
|
|
11
10
|
import { SafeLinkMixin } from '../../mixins/safe_link_mixin';
|
|
@@ -35,7 +34,7 @@ export default {
|
|
|
35
34
|
size: {
|
|
36
35
|
type: String,
|
|
37
36
|
required: false,
|
|
38
|
-
default:
|
|
37
|
+
default: 'medium',
|
|
39
38
|
validator: (value) => Object.keys(buttonSizeOptions).includes(value),
|
|
40
39
|
},
|
|
41
40
|
selected: {
|
|
@@ -108,7 +107,7 @@ export default {
|
|
|
108
107
|
return classes;
|
|
109
108
|
},
|
|
110
109
|
buttonSize() {
|
|
111
|
-
return
|
|
110
|
+
return buttonSizeOptions[this.size];
|
|
112
111
|
},
|
|
113
112
|
},
|
|
114
113
|
mounted() {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
`GlFormDate` allows users to choose and input a date using a keyboard by by using
|
|
2
|
+
browser implemented calendar controls, where available.
|
|
3
|
+
|
|
4
|
+
`GlFormDate` extends `<input type="date">` with an `<output>` for audible announcement
|
|
5
|
+
of selected date, in full format, by screen-readers.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
On `change` the value is emitted in `YYYY-MM-DD` format.
|
|
10
|
+
|
|
11
|
+
## Accessibility
|
|
12
|
+
|
|
13
|
+
`GlFormDate` is a form `<input>` and should have an accessible name using a `<label>`.
|
|
14
|
+
|
|
15
|
+
`GlFormGroup` can be used to label `GlFormDate`.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<gl-form-group
|
|
19
|
+
label="Enter date"
|
|
20
|
+
label-for="input-id"
|
|
21
|
+
>
|
|
22
|
+
<gl-form-date
|
|
23
|
+
id="input-id"
|
|
24
|
+
/>
|
|
25
|
+
</gl-form-group>
|
|
26
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mount, shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import GlFormDate from './form_date.vue';
|
|
4
|
+
|
|
5
|
+
describe('GlFormDate', () => {
|
|
6
|
+
let wrapper;
|
|
7
|
+
|
|
8
|
+
const createComponent = (propsData = {}, mountFn = shallowMount) => {
|
|
9
|
+
wrapper = mountFn(GlFormDate, { propsData });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const findInput = () => wrapper.findComponent({ ref: 'input' });
|
|
13
|
+
const findInvalidFeedback = () => wrapper.findComponent({ ref: 'invalidFeedback' });
|
|
14
|
+
const findOutput = () => wrapper.findComponent({ ref: 'output' });
|
|
15
|
+
|
|
16
|
+
describe('props', () => {
|
|
17
|
+
it.each`
|
|
18
|
+
propName | value | attribute | expectedValue
|
|
19
|
+
${'id'} | ${'idForInput'} | ${'id'} | ${'idForInput'}
|
|
20
|
+
${'min'} | ${'2020-01-01'} | ${'min'} | ${'2020-01-01'}
|
|
21
|
+
${'max'} | ${'2020-01-31'} | ${'max'} | ${'2020-01-31'}
|
|
22
|
+
${'value'} | ${'2020-01-19'} | ${'value'} | ${'2020-01-19'}
|
|
23
|
+
`(
|
|
24
|
+
'when `$propName` prop is passed sets input `$attribute` to `$expectedValue`',
|
|
25
|
+
({ propName, value, attribute, expectedValue }) => {
|
|
26
|
+
createComponent({ [propName]: value });
|
|
27
|
+
|
|
28
|
+
expect(findInput().attributes(attribute)).toBe(expectedValue);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
it('when `id` prop is not passed sets a unique input `id` attribute', async () => {
|
|
33
|
+
createComponent({ value: '2019-01-01' }, mount);
|
|
34
|
+
await nextTick();
|
|
35
|
+
await nextTick();
|
|
36
|
+
expect(findInput().attributes('id')).not.toBe('');
|
|
37
|
+
expect(findOutput().attributes('for')).not.toBe('');
|
|
38
|
+
expect(findInput().attributes('id')).toMatch(findOutput().attributes('for'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it.each`
|
|
42
|
+
propName | attribute | expectedValue
|
|
43
|
+
${'min'} | ${'min'} | ${undefined}
|
|
44
|
+
${'max'} | ${'max'} | ${undefined}
|
|
45
|
+
${'value'} | ${'value'} | ${undefined}
|
|
46
|
+
`(
|
|
47
|
+
'when `$propName` prop is not passed sets input `$attribute` attribute to `$expectedValue`',
|
|
48
|
+
({ attribute, expectedValue }) => {
|
|
49
|
+
createComponent();
|
|
50
|
+
|
|
51
|
+
expect(findInput().attributes(attribute)).toBe(expectedValue);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('validation', () => {
|
|
57
|
+
it('when `value` is less than `min` adds `aria-invalid="true"` attribute and invalid-feedback`', () => {
|
|
58
|
+
createComponent(
|
|
59
|
+
{
|
|
60
|
+
min: '2020-01-01',
|
|
61
|
+
value: '2019-01-01',
|
|
62
|
+
},
|
|
63
|
+
mount
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(findInput().attributes('aria-describedby')).toMatch('form-date-invalid-feedback-');
|
|
67
|
+
expect(findInput().attributes('aria-invalid')).toBe('true');
|
|
68
|
+
expect(findInvalidFeedback().text()).toBe('Must be after minimum date');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('when `value` is greater than `max` adds `aria-invalid="true"` attribute and invalid-feedback`', () => {
|
|
72
|
+
createComponent(
|
|
73
|
+
{
|
|
74
|
+
max: '2020-01-01',
|
|
75
|
+
value: '2021-01-01',
|
|
76
|
+
},
|
|
77
|
+
mount
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(findInput().attributes('aria-describedby')).toMatch('form-date-invalid-feedback-');
|
|
81
|
+
expect(findInput().attributes('aria-invalid')).toBe('true');
|
|
82
|
+
expect(findInvalidFeedback().text()).toBe('Must be before maximum date');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import readme from './form_date.md';
|
|
2
|
+
import GlFormDate from './form_date.vue';
|
|
3
|
+
|
|
4
|
+
const defaultValue = (prop) => GlFormDate.props[prop].default;
|
|
5
|
+
|
|
6
|
+
const template = `
|
|
7
|
+
<gl-form-date
|
|
8
|
+
v-model="localValue"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:min="min"
|
|
11
|
+
:max="max"
|
|
12
|
+
:min-invalid-feedback="minInvalidFeedback"
|
|
13
|
+
:max-invalid-feedback="maxInvalidFeedback"
|
|
14
|
+
:readonly="readonly"
|
|
15
|
+
:value="value"
|
|
16
|
+
/>`;
|
|
17
|
+
|
|
18
|
+
const generateProps = ({
|
|
19
|
+
disabled = false,
|
|
20
|
+
min = '',
|
|
21
|
+
max = '',
|
|
22
|
+
minInvalidFeedback = defaultValue('minInvalidFeedback'),
|
|
23
|
+
maxInvalidFeedback = defaultValue('maxInvalidFeedback'),
|
|
24
|
+
readonly = false,
|
|
25
|
+
value = '',
|
|
26
|
+
} = {}) => ({
|
|
27
|
+
disabled,
|
|
28
|
+
min,
|
|
29
|
+
max,
|
|
30
|
+
minInvalidFeedback,
|
|
31
|
+
maxInvalidFeedback,
|
|
32
|
+
readonly,
|
|
33
|
+
value,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const Template = (args) => ({
|
|
37
|
+
components: { GlFormDate },
|
|
38
|
+
props: Object.keys(args),
|
|
39
|
+
watch: {
|
|
40
|
+
value(newValue) {
|
|
41
|
+
this.localValue = newValue;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
data() {
|
|
45
|
+
return {
|
|
46
|
+
localValue: this.value,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
template,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const Default = Template.bind({});
|
|
53
|
+
Default.args = generateProps();
|
|
54
|
+
|
|
55
|
+
export const Disabled = Template.bind({});
|
|
56
|
+
Disabled.args = generateProps({ disabled: true });
|
|
57
|
+
|
|
58
|
+
export const DisabledValue = Template.bind({});
|
|
59
|
+
DisabledValue.args = generateProps({
|
|
60
|
+
disabled: true,
|
|
61
|
+
value: '2020-01-19',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const MinMaxDates = Template.bind({});
|
|
65
|
+
MinMaxDates.args = generateProps({
|
|
66
|
+
min: '2020-01-01',
|
|
67
|
+
max: '2020-01-31',
|
|
68
|
+
minInvalidFeedback: 'Must be after 2020-01-01',
|
|
69
|
+
maxInvalidFeedback: 'Must be before 2020-01-31',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const Readonly = Template.bind({});
|
|
73
|
+
Readonly.args = generateProps({
|
|
74
|
+
readonly: true,
|
|
75
|
+
value: '2020-01-19',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const Value = Template.bind({});
|
|
79
|
+
Value.args = generateProps({ value: '2020-01-15' });
|
|
80
|
+
|
|
81
|
+
export const InvalidDate = Template.bind({});
|
|
82
|
+
InvalidDate.args = generateProps({
|
|
83
|
+
min: '2020-01-01',
|
|
84
|
+
max: '2020-01-31',
|
|
85
|
+
minInvalidFeedback: 'Must be after 2020-01-01',
|
|
86
|
+
maxInvalidFeedback: 'Must be before 2020-01-31',
|
|
87
|
+
value: '2020-02-02',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export default {
|
|
91
|
+
title: 'base/form/form-date',
|
|
92
|
+
component: GlFormDate,
|
|
93
|
+
parameters: {
|
|
94
|
+
docs: {
|
|
95
|
+
description: {
|
|
96
|
+
component: readme,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { uniqueId } from 'lodash';
|
|
3
|
+
import GlFormInput from '../form_input/form_input.vue';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'GlFormDate',
|
|
7
|
+
components: {
|
|
8
|
+
GlFormInput,
|
|
9
|
+
},
|
|
10
|
+
inheritAttrs: false,
|
|
11
|
+
model: {
|
|
12
|
+
event: 'change',
|
|
13
|
+
prop: 'value',
|
|
14
|
+
},
|
|
15
|
+
props: {
|
|
16
|
+
id: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: false,
|
|
19
|
+
default: null,
|
|
20
|
+
},
|
|
21
|
+
min: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: false,
|
|
24
|
+
default: null,
|
|
25
|
+
},
|
|
26
|
+
max: {
|
|
27
|
+
type: String,
|
|
28
|
+
required: false,
|
|
29
|
+
default: null,
|
|
30
|
+
},
|
|
31
|
+
minInvalidFeedback: {
|
|
32
|
+
type: String,
|
|
33
|
+
required: false,
|
|
34
|
+
default: 'Must be after minimum date',
|
|
35
|
+
},
|
|
36
|
+
maxInvalidFeedback: {
|
|
37
|
+
type: String,
|
|
38
|
+
required: false,
|
|
39
|
+
default: 'Must be before maximum date',
|
|
40
|
+
},
|
|
41
|
+
value: {
|
|
42
|
+
type: String,
|
|
43
|
+
required: false,
|
|
44
|
+
default: null,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
data() {
|
|
48
|
+
return {
|
|
49
|
+
currentValue: this.value,
|
|
50
|
+
inputId: this.id || uniqueId('form-date-'),
|
|
51
|
+
invalidFeedbackId: uniqueId('form-date-invalid-feedback-'),
|
|
52
|
+
outputId: uniqueId('form-date-output-'),
|
|
53
|
+
valueAsDate: null,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
computed: {
|
|
57
|
+
ariaDescribedBy() {
|
|
58
|
+
return [this.valueAsDate && this.outputId, this.isInvalid && this.invalidFeedbackId].join(
|
|
59
|
+
' '
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
isLessThanMin() {
|
|
63
|
+
return this.currentValue && this.min && this.currentValue < this.min;
|
|
64
|
+
},
|
|
65
|
+
isGreaterThanMax() {
|
|
66
|
+
return this.currentValue && this.max && this.currentValue > this.max;
|
|
67
|
+
},
|
|
68
|
+
isInvalid() {
|
|
69
|
+
return this.isLessThanMin || this.isGreaterThanMax;
|
|
70
|
+
},
|
|
71
|
+
outputValue() {
|
|
72
|
+
if (!this.valueAsDate) return null;
|
|
73
|
+
return new Intl.DateTimeFormat(undefined, { dateStyle: 'full' }).format(this.valueAsDate);
|
|
74
|
+
},
|
|
75
|
+
state() {
|
|
76
|
+
return !this.isInvalid;
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
watch: {
|
|
80
|
+
value: {
|
|
81
|
+
handler(newValue) {
|
|
82
|
+
this.currentValue = newValue;
|
|
83
|
+
this.updateValueAsDate();
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
async mounted() {
|
|
88
|
+
await this.$nextTick();
|
|
89
|
+
this.updateValueAsDate();
|
|
90
|
+
},
|
|
91
|
+
methods: {
|
|
92
|
+
updateValueAsDate() {
|
|
93
|
+
this.valueAsDate = this.$refs.input.$el.valueAsDate;
|
|
94
|
+
},
|
|
95
|
+
onChange($event) {
|
|
96
|
+
/**
|
|
97
|
+
* Emitted when date is changed.
|
|
98
|
+
*
|
|
99
|
+
* @event change
|
|
100
|
+
*/
|
|
101
|
+
this.updateValueAsDate();
|
|
102
|
+
this.$emit('change', $event);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
</script>
|
|
107
|
+
<template>
|
|
108
|
+
<div class="gl-form-date">
|
|
109
|
+
<gl-form-input
|
|
110
|
+
:id="inputId"
|
|
111
|
+
ref="input"
|
|
112
|
+
v-model="currentValue"
|
|
113
|
+
v-bind="$attrs"
|
|
114
|
+
:aria-describedby="ariaDescribedBy"
|
|
115
|
+
:min="min"
|
|
116
|
+
:max="max"
|
|
117
|
+
pattern="\d{4}-\d{2}-\d{2}"
|
|
118
|
+
placeholder="yyyy-mm-dd"
|
|
119
|
+
:state="state"
|
|
120
|
+
type="date"
|
|
121
|
+
@change="onChange"
|
|
122
|
+
/>
|
|
123
|
+
<output v-if="outputValue" :id="outputId" ref="output" :for="inputId" class="gl-sr-only">
|
|
124
|
+
{{ outputValue }}
|
|
125
|
+
</output>
|
|
126
|
+
<div v-if="isInvalid" :id="invalidFeedbackId" ref="invalidFeedback" class="invalid-feedback">
|
|
127
|
+
<template v-if="isLessThanMin">
|
|
128
|
+
{{ minInvalidFeedback }}
|
|
129
|
+
</template>
|
|
130
|
+
<template v-if="isGreaterThanMax">
|
|
131
|
+
{{ maxInvalidFeedback }}
|
|
132
|
+
</template>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buttonSizeOptions } from '../../utils/constants';
|
|
2
2
|
|
|
3
3
|
export const ButtonMixin = {
|
|
4
4
|
computed: {
|
|
5
5
|
buttonSize() {
|
|
6
|
-
return
|
|
6
|
+
return buttonSizeOptions[this.size];
|
|
7
7
|
},
|
|
8
8
|
},
|
|
9
9
|
};
|
package/src/scss/components.scss
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
@import '../components/base/filtered_search/filtered_search_term';
|
|
35
35
|
@import '../components/base/filtered_search/filtered_search_token';
|
|
36
36
|
@import '../components/base/filtered_search/filtered_search_token_segment';
|
|
37
|
+
@import '../components/base/form/form_date/form_date';
|
|
37
38
|
@import '../components/base/form/form_input/form_input';
|
|
38
39
|
@import '../components/base/form/form_checkbox/form_checkbox';
|
|
39
40
|
@import '../components/base/form/form_group/form_group';
|
package/src/utils/constants.js
CHANGED