Skip to content

Commit

Permalink
feat(datepicker): add flatpickr plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
weslleyrsr committed Apr 18, 2023
1 parent b040f51 commit 4ea62b4
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 35 deletions.
11 changes: 5 additions & 6 deletions src/components/CvDatePicker/CvDatePicker.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ref } from 'vue';

const initArgs = {
dateLabel: 'Date label',
invalidText: 'Invalid value',
invalidMessage: '',
};

export default {
Expand All @@ -25,7 +25,7 @@ export default {
},
argTypes: {
dateLabel: { type: String, description: 'Date picker label' },
invalidText: {
invalidMessage: {
type: String,
description: 'Date picker text on invalid value',
},
Expand Down Expand Up @@ -58,7 +58,7 @@ Default.parameters = storyParametersObject(
);

const now = new Date();
const modelValue = ref(now.toLocaleDateString());
const modelValue = ref('');
const templateVModel = `
<div>
<cv-date-picker v-bind='args' @change='onChange' v-model="modelValue">
Expand All @@ -67,12 +67,11 @@ const templateVModel = `
<div style="margin: 32px 0;">
<div style="font-size: 150%;">Sample interaction</div>
<label for="date-model" style='margin-right: 0.5rem'>V-model:</label>
<input id="date-model" type="text" :value="modelValue" @change="ev => modelValue = ev.currentTarget.value"/>
<input id="date-model" type="text" :value="modelValue.startDate || modelValue" @change="ev => { if (args.kind === 'range') { modelValue.startDate = ev.currentTarget.value } else { modelValue = ev.currentTarget.value } }"/>
<input v-if="args.kind === 'range'" id="date-model" type="text" :value="modelValue.endDate" @change="ev => modelValue.endDate = ev.currentTarget.value"/>
</div>
`;

// <input id="date-model" type="date" :value="now.toLocaleDateString('en-CA')" @change="ev => modelValue = new Date(ev.currentTarget.value).toLocaleDateString()"/>

const TemplateVModel = args => {
return {
components: { CvDatePicker },
Expand Down
160 changes: 131 additions & 29 deletions src/components/CvDatePicker/CvDatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
ref="date"
type="text"
data-date-picker-input
:data-invalid="isInvalid || null"
:disabled="disabled"
:data-date-picker-input-from="getKind === 'range'"
:id="`${cvId}-input-1`"
:class="`${carbonPrefix}--date-picker__input`"
Expand All @@ -53,14 +55,17 @@
v-if="['single', 'range'].includes(getKind)"
:class="`${carbonPrefix}--date-picker__icon`"
data-date-picker-icon
@click="console.log($event.target)"
/>
</div>

<div :class="`${carbonPrefix}--form-requirement`" v-if="isInvalid">
<slot name="invalid-message">{{ invalidMessage }}</slot>
</div>
</div>

<!-- maybe use input as another component? -->
<div
v-if="getKind === 'range'"
v-if="isRange"
:class="{
[`${carbonPrefix}--date-picker-container`]: getKind === 'range',
[`${carbonPrefix}--date-picker--nolabel`]: dateEndLabel !== undefined,
Expand All @@ -81,6 +86,9 @@
ref="todate"
type="text"
data-date-picker-input
:data-date-picker-input-to="kind === 'range'"
:data-invalid="isInvalid || null"
:disabled="disabled"
:id="`${cvId}-input-2`"
:class="`${carbonPrefix}--date-picker__input`"
:pattern="pattern"
Expand All @@ -91,9 +99,11 @@
<Calendar16
:class="`${carbonPrefix}--date-picker__icon`"
data-date-picker-icon
@click="console.log($event)"
/>
</div>
<div :class="`${carbonPrefix}--form-requirement`" v-if="isInvalid">
<span id="invalid-message-placeholder"></span>
</div>
</div>
</div>
</cv-wrapper>
Expand All @@ -115,6 +125,11 @@ import { Calendar16 } from '@carbon/icons-vue';
import CvWrapper from '../CvWrapper/CvWrapper';
import { props as propsCvTheme, useIsLight } from '../../use/cvTheme';
import flatpickr from 'flatpickr';
import l10n from 'flatpickr/dist/l10n/index';
// import carbonFlatpickrAppendToPlugin from './plugins/appendToPlugin';
import carbonFlatpickrFixEventsPlugin from './plugins/fixEventsPlugin';
import carbonFlatpickrRangePlugin from './plugins/rangePlugin';
import carbonFlatpickrMonthSelectPlugin from './plugins/monthSelectPlugin';
const dateWrapper = ref(null);
const date = ref(null);
Expand All @@ -130,7 +145,8 @@ const props = defineProps({
dateLabel: { type: String, default: undefined },
dateEndLabel: { type: String, default: 'End date' },
invalid: { type: Boolean, default: false },
invalidText: { type: String },
disabled: { type: Boolean, default: false },
invalidMessage: { type: String },
pattern: { type: String, default: '\\d{1,2}/\\d{1,2}/\\d{4}' },
placeholder: { type: String, default: 'mm/dd/yyyy' },
calOptions: {
Expand All @@ -147,6 +163,7 @@ const props = defineProps({
default: 'simple',
validator: val => ['short', 'simple', 'single', 'range'].includes(val),
},
value: [String, Object, Array, Date],
...propsCvId,
...propsCvTheme,
});
Expand All @@ -165,7 +182,7 @@ const getKind = computed({
const getDateLabel = computed({
get() {
if (props.getKind === 'range' && !props.dateLabel) {
if (props.kind === 'range' && !props.dateLabel) {
return 'Start date';
}
Expand All @@ -179,62 +196,142 @@ const getDateLabel = computed({
const getStartDate = computed({
get() {
if (props.modelValue) {
return props.modelValue?.startDate || props.modelValue;
}
return (
props.modelValue?.startDate ||
props.modelValue ||
props.value?.startDate ||
props.value
);
},
});
const getEndDate = computed({
get() {
if (props.modelValue) {
return props.modelValue?.endDate || props.modelValue;
}
return props.modelValue?.endDate || props.value?.endDate;
},
});
const isRange = computed(() => {
return props.kind === 'range';
});
const isSingle = computed(() => {
return props.kind === 'single';
});
const isInvalid = computed(() => {
return !!props.invalidMessage;
});
const getFlatpickrOptions = () => {
const options = { ...props.calOptions };
options.plugins = [
props.kind === 'range'
? carbonFlatpickrRangePlugin({
input: todate.value,
})
: () => {},
carbonFlatpickrMonthSelectPlugin({
selectorFlatpickrMonthYearContainer: '.flatpickr-current-month',
selectorFlatpickrYearContainer: '.numInputWrapper',
selectorFlatpickrCurrentMonth: '.cur-month',
classFlatpickrCurrentMonth: 'cur-month',
}),
carbonFlatpickrFixEventsPlugin({
inputFrom: date.value,
inputTo: todate.value,
}),
];
options.nextArrow = `
<svg width="16px" height="16px" viewBox="0 0 16 16">
<polygon points="11,8 6,13 5.3,12.3 9.6,8 5.3,3.7 6,3 "/>
<rect width="16" height="16" style="fill:none" />
</svg>
`;
options.prevArrow = `
<svg width="16px" height="16px" viewBox="0 0 16 16">
<polygon points="5,8 10,3 10.7,3.7 6.4,8 10.7,12.3 10,13 "/>
<rect width="16" height="16" style="fill:none" />
</svg>
`;
options.mode = props.kind;
// add events update based on parameters
options.onChange = handleDatePick;
// options.onOpen = onOpen;
options.onReady = onCalReady;
return options;
};
const onCalReady = (selectedDates, dateStr, instance) => {
const calendarContainer = instance.calendarContainer;
const options = {
classCalendarContainer: `${carbonPrefix}--date-picker__calendar`,
classMonth: `${carbonPrefix}--date-picker__month`,
classWeekdays: `${carbonPrefix}--date-picker__weekdays`,
classDays: `${carbonPrefix}--date-picker__days`,
classWeekday: `${carbonPrefix}--date-picker__weekday`,
classDay: `${carbonPrefix}--date-picker__day`,
classFocused: `${carbonPrefix}--focused`,
classVisuallyHidden: `${carbonPrefix}--visually-hidden`,
};
if (calendarContainer) {
calendarContainer.classList.add(options.classCalendarContainer);
calendarContainer
.querySelector('.flatpickr-month')
.classList.add(options.classMonth);
calendarContainer
.querySelector('.flatpickr-weekdays')
.classList.add(options.classWeekdays);
calendarContainer
.querySelector('.flatpickr-days')
.classList.add(options.classDays);
for (const item of calendarContainer.querySelectorAll(
'.flatpickr-weekday'
)) {
const currentItem = item;
currentItem.innerHTML = currentItem.innerHTML.replace(/\s+/g, '');
currentItem.classList.add(options.classWeekday);
}
for (const item of calendarContainer.querySelectorAll('.flatpickr-day')) {
item.classList.add(options.classDay);
if (item.classList.contains('today') && selectedDates.length > 0) {
item.classList.add('no-border');
} else if (
item.classList.contains('today') &&
selectedDates.length === 0
) {
item.classList.remove('no-border');
}
}
}
};
const initFlatpickr = () => {
return flatpickr(date.value, getFlatpickrOptions());
};
let dateToString = val => {
if (typeof val === 'number') {
return this.cal.formatDate(val, this.calOptions.dateFormat);
} else {
return val || '';
}
return calendar.formatDate(val, props.calOptions.dateFormat);
};
const handleDatePick = (selectedDates, dateStr, instance) => {
if (selectedDates.length === 1) {
const temp = calendar.formatDate(
selectedDates[0],
props.calOptions.dateFormat
);
const temp = dateToString(selectedDates[0]);
nextTick(() => {
date.value.value = temp;
});
emit('update:modelValue', temp);
} else {
const startDate = calendar.formatDate(
selectedDates[0],
props.calOptions.dateFormat
);
const endDate = calendar.formatDate(
selectedDates[1],
props.calOptions.dateFormat
);
const startDate = dateToString(selectedDates[0]);
const endDate = dateToString(selectedDates[1]);
nextTick(() => {
date.value.value = startDate;
Expand All @@ -261,4 +358,9 @@ onMounted(() => {
});
</script>
<style lang="scss"></style>
<style lang="scss">
#invalid-message-placeholder {
display: block;
height: 16px;
}
</style>
64 changes: 64 additions & 0 deletions src/components/CvDatePicker/plugins/appendToPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license
*
* Copyright IBM Corp. 2019
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

/*
* COPIED FROM carbon-components-react
*/

/**
* @param {object} config Plugin configuration.
* @returns {Plugin} A Flatpickr plugin to put adjust the position of calendar dropdown.
*/
export default config => fp => {
/**
* Adjusts the floating meun position after Flatpicker sets it.
*/
const handlePreCalendarPosition = () => {
Promise.resolve().then(() => {
const {
calendarContainer,
config: fpConfig,
_positionElement: positionElement,
} = fp;
const { appendTo } = fpConfig;
const { left: containerLeft, top: containerTop } =
appendTo.getBoundingClientRect();
const { left: refLeft, bottom: refBottom } =
positionElement.getBoundingClientRect();
if (
(appendTo !== appendTo.ownerDocument.body ||
containerLeft !== 0 ||
containerTop !== 0) &&
appendTo.ownerDocument.defaultView
.getComputedStyle(appendTo)
.getPropertyValue('position') === 'static'
) {
throw new Error(
'Floating menu container must not have `position:static`.'
);
}
// `2` for negative mergin on calendar dropdown
calendarContainer.style.top = `${refBottom - containerTop + 2}px`;
calendarContainer.style.left = `${refLeft - containerLeft}px`;
});
};

/**
* Registers this Flatpickr plugin.
*/
const register = () => {
fp.loadedPlugins.push('carbonFlatpickrAppendToPlugin');
};

return {
appendTo: config.appendTo,
onReady: register,
onPreCalendarPosition: handlePreCalendarPosition,
};
};
Loading

0 comments on commit 4ea62b4

Please sign in to comment.