Skip to content

Commit 2cad04d

Browse files
committed
✨ (Props) clearable - Add input clear functionality
1 parent 7eee35d commit 2cad04d

File tree

14 files changed

+265
-77
lines changed

14 files changed

+265
-77
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Using `v-model`
9898
| id | String | 'datepicker' | Sets the input id |
9999
| value | Date\|String\|Number | | Date picker model (ISO 8601 format, YY-mm-dd or YY-mm) |
100100
| name | String | 'datepicker' | Input name property & datepicker title in fullscreenMobile |
101+
| clearable | Boolean | false | Add input clear functionality |
101102
| validate | Boolean | false | Shows validations button to select date |
102103
| buttonValidate | String | 'Ok' | Sets validate text button |
103104
| buttonCancel | String | 'Cancel' | Sets cancel text button |

dist/vue-datepicker.esm.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/vue-datepicker.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/vue-datepicker.umd.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc-src/src/components/Examples.vue

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export default {
4040
description: 'Need to validate to change date (Add buttons)',
4141
filename: 'validate',
4242
},
43+
clearable: {
44+
title: '# Props: Clearable',
45+
description: 'Adds input clear functionality',
46+
filename: 'clearable',
47+
},
4348
format: {
4449
title: '# Props: Format',
4550
description: 'Allow to format input date',

doc-src/src/components/Wrapper.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
class="wrapper-example__example pa-16">
4646
<div class="wrapper-example__item">
4747
<slot v-if="$slots.example" name="example" />
48-
<div v-else data-app="true">
48+
<div v-else>
4949
<component :is="component" />
5050
</div>
5151
</div>

doc-src/src/examples/clearable.vue

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template>
2+
<VueDatePicker v-model="date" clearable />
3+
</template>
4+
5+
<script>
6+
export default {
7+
data: () => ({
8+
date: null,
9+
}),
10+
};
11+
</script>

src/components/DatePicker/DatePicker.vue

+15-7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export default {
3939
props: {
4040
id: { type: String, default: undefined },
4141
name: { type: String, default: 'datepicker' },
42+
// Add input clear functionality
43+
clearable: { type: Boolean, default: false },
4244
// Validation Buttons
4345
validate: { type: Boolean, default: false },
4446
buttonValidate: { type: String, default: undefined },
@@ -240,6 +242,10 @@ export default {
240242
KEYCODES.tab,
241243
].includes(keyCode)) return this.hideDatePicker(event);
242244
},
245+
onClearDate () {
246+
this.$emit('input', undefined);
247+
this.$emit('onChange');
248+
},
243249
// ------------------------------
244250
// Generate Template
245251
// ------------------------------
@@ -307,22 +313,24 @@ export default {
307313
genCustomInput () {
308314
return this.$createElement(DatePickerCustomInput, {
309315
props: {
310-
id: this.componentId,
311-
name: this.name,
316+
clearable: this.clearable,
317+
closeOnClickOutside: this.isMenuActive && !this.shouldShowBottomSheet,
318+
color: this.color,
312319
date: this.computedDate,
320+
disabled: this.disabled,
321+
id: this.componentId,
313322
isDateDefined: this.isDateDefined,
323+
name: this.name,
324+
noCalendarIcon: this.noCalendarIcon,
325+
noInput: this.noInput,
314326
placeholder: this.placeholder,
315-
color: this.color,
316-
disabled: this.disabled,
317327
tabindex: this.tabindex,
318-
noInput: this.noInput,
319-
noCalendarIcon: this.noCalendarIcon,
320-
closeOnClickOutside: this.isMenuActive && !this.shouldShowBottomSheet,
321328
},
322329
on: {
323330
focus: this.showDatePicker,
324331
blur: this.hideDatePicker,
325332
keydown: this.onKeyDown,
333+
clearDate: this.onClearDate,
326334
},
327335
ref: 'activator',
328336
});

src/components/DatePicker/DatePickerCustomInput.vue

+73-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default {
1515
props: {
1616
id: { type: String },
1717
name: { type: String },
18+
clearable: { type: Boolean },
1819
date: { type: [Object, Date, String] },
1920
isDateDefined: { type: Boolean, default: false },
2021
placeholder: { type: String },
@@ -29,6 +30,9 @@ export default {
2930
computedColor () {
3031
return this.isDateDefined && !this.disabled ? this.color : 'rgba(93, 106, 137, 0.5)';
3132
},
33+
isDirty () {
34+
return this.isDateDefined;
35+
},
3236
},
3337
methods: {
3438
// ------------------------------
@@ -53,24 +57,29 @@ export default {
5357
onKeyDown (event) {
5458
this.$emit('keydown', event);
5559
},
60+
clearableCallback () {
61+
this.$emit('clearDate');
62+
},
5663
// ------------------------------
5764
// Generate Template
5865
// ------------------------------
5966
genContent () {
6067
return [
6168
!this.noCalendarIcon && this.genCalendarIcon(),
6269
this.noInput ? this.genButton() : this.genInput(),
70+
this.clearable && this.genClearIcon(),
6371
];
6472
},
6573
genCalendarIcon () {
6674
return this.$createElement(Icon, {
75+
staticClass: 'datepicker__input-icon',
6776
props: {
6877
disabled: this.disabled,
6978
},
7079
}, ['calendarAlt']);
7180
},
7281
genInput () {
73-
return this.$createElement('input', {
82+
const data = {
7483
attrs: {
7584
id: this.id,
7685
name: this.name,
@@ -91,7 +100,44 @@ export default {
91100
keydown: this.onKeyDown,
92101
},
93102
ref: 'input',
94-
});
103+
};
104+
105+
return this.$createElement('div', {
106+
staticClass: `datepicker__input-wrapper`,
107+
}, [
108+
this.$createElement('input', data),
109+
]);
110+
},
111+
genClearIcon () {
112+
const iconName = this.isDirty ? 'close' : '';
113+
const data = {
114+
attrs: {
115+
'aria-label': 'clearable icon',
116+
color: this.color,
117+
disabled: this.disabled,
118+
},
119+
on: {
120+
click: event => {
121+
event.preventDefault();
122+
event.stopPropagation();
123+
this.clearableCallback();
124+
},
125+
mouseup: event => {
126+
event.preventDefault();
127+
event.stopPropagation();
128+
},
129+
},
130+
};
131+
132+
const iconElement = this.$createElement('div', {
133+
staticClass: 'datepicker__input-clear__icon',
134+
}, [
135+
this.$createElement(Icon, data, iconName),
136+
]);
137+
138+
return this.$createElement('div', {
139+
staticClass: `datepicker__input-clear`,
140+
}, [iconElement]);
95141
},
96142
genButton () {
97143
return this.$createElement('button', {
@@ -159,11 +205,16 @@ export default {
159205
}
160206
}
161207
162-
svg {
208+
&-icon {
163209
margin-bottom: 5px;
164210
}
165211
166-
input {
212+
&-wrapper {
213+
display: flex;
214+
flex: 1 1 auto;
215+
position: relative;
216+
217+
input {
167218
display: flex;
168219
flex: 1 1 auto;
169220
position: relative;
@@ -188,6 +239,24 @@ export default {
188239
color: transparentize(black, .6);
189240
}
190241
}
242+
}
243+
244+
&-clear {
245+
display: inline-flex;
246+
align-self: flex-start;
247+
line-height: 1;
248+
user-select: none;
249+
250+
&__icon {
251+
display: inline-flex;
252+
justify-content: center;
253+
align-items: center;
254+
height: 24px;
255+
min-width: 24px;
256+
width: 24px;
257+
flex: 1 0 auto;
258+
}
259+
}
191260
192261
button {
193262
@extend %reset-button;

src/components/Icon/Icon.vue

+45-31
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,76 @@
33
import { ICONS } from '../../constants/icons';
44
55
// helpers
6-
import { deepMerge } from '../../utils/helpers';
6+
import { convertToUnit } from '../../utils/helpers';
77
88
export default {
99
name: 'Icon',
1010
inheritAttrs: false,
1111
props: {
12-
height: { type: [Number, String], default: 16 },
13-
width: { type: [Number, String], default: 16 },
12+
size: { type: [Number, String], default: 16 },
1413
disabled: { type: Boolean, default: false },
1514
},
1615
computed: {
17-
defaultData () {
18-
const hasClickListener = Boolean(this.$listeners.click);
16+
hasClickListener () {
17+
return Boolean(this.$listeners.click);
18+
},
19+
},
20+
methods: {
21+
getIconName () {
22+
if (!this.$slots.default) return '';
23+
return this.$slots.default[0].text && this.$slots.default[0].text.trim();
24+
},
25+
getIcon () {
26+
const iconName = this.getIconName();
27+
return ICONS[iconName] || iconName;
28+
},
29+
getDefaultData () {
1930
return {
2031
staticClass: 'icon',
2132
class: {
2233
'icon--disabled': this.disabled,
23-
'icon--link': hasClickListener,
34+
'icon--link': this.hasClickListener,
2435
},
2536
attrs: {
26-
'aria-hidden': !hasClickListener,
27-
role: hasClickListener ? 'button' : null,
28-
...this.$attrs,
37+
'aria-hidden': !this.hasClickListener,
38+
disabled: this.hasClickListener && this.disabled,
39+
type: this.hasClickListener ? 'button' : undefined,
2940
},
3041
on: this.$listeners,
3142
};
3243
},
33-
},
34-
methods: {
35-
getIcon () {
36-
let iconName = '';
37-
if (this.$slots.default) {
38-
iconName = this.$slots.default[0].text && this.$slots.default[0].text.trim();
39-
}
40-
return ICONS[iconName] || iconName;
41-
},
4244
renderSvgIcon (icon, h) {
43-
const data = deepMerge(this.defaultData, {
45+
const tag = this.hasClickListener ? 'button' : 'span';
46+
const fontSize = convertToUnit(this.size);
47+
48+
const wrapperData = {
49+
...this.getDefaultData(),
50+
style: {
51+
...(fontSize && { fontSize, height: fontSize, width: fontSize }),
52+
},
53+
};
54+
55+
const svgData = {
4456
attrs: {
4557
xmlns: 'http://www.w3.org/2000/svg',
4658
viewBox: icon.viewBox,
47-
height: this.height,
48-
width: this.width,
59+
height: this.size,
60+
width: this.size,
4961
role: 'img',
50-
'aria-hidden': !this.$attrs['aria-label'],
51-
'aria-label': this.$attrs['aria-label'],
62+
'aria-hidden': true,
63+
'data-icon': this.getIconName(),
5264
},
53-
});
65+
};
5466
55-
return h('svg', data, [
56-
h('path', {
57-
attrs: {
58-
fill: 'currentColor',
59-
d: icon.path,
60-
},
61-
}),
67+
return h(tag, wrapperData, [
68+
h('svg', svgData, [
69+
h('path', {
70+
attrs: {
71+
fill: 'currentColor',
72+
d: icon.path,
73+
},
74+
}),
75+
]),
6276
]);
6377
},
6478
},

0 commit comments

Comments
 (0)