Skip to content

Commit

Permalink
feat: date picker (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuagraber authored Nov 20, 2024
1 parent 13ba960 commit 835b11f
Show file tree
Hide file tree
Showing 15 changed files with 700 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Form](../src/components/Form//README.md)
- [Header](../src/components/Header//README.md)
- [Input](../src/components/Input//README.md)
- [InputDatePicker](../src/components/InputDatePicker//README.md)
- [InputSelect](../src/components/InputSelect//README.md)
- [Nav](../src/components/Nav//README.md)
- [QuickSearchForm](../src/components/QuickSearchForm//README.md)
Expand Down
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^12.1.0",
"@types/lodash": "^4.17.13",
"@types/node": "^20.8.9",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
Expand Down Expand Up @@ -92,6 +93,7 @@
"@fortawesome/vue-fontawesome": "^3.0.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vuepic/vue-datepicker": "^10.0.0",
"fs-extra": "^11.1.1",
"happy-dom": "^6.0.4",
"minimist": "^1.2.8",
Expand Down
138 changes: 138 additions & 0 deletions src/components/InputDatePicker/PdapInputDatePicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<div class="pdap-input" :class="{ 'pdap-input-error': error }">
<label v-if="$slots.label" :id="`${name}-${id}-label`" :for="id">
<slot name="label" />
</label>
<label v-else-if="label" :id="`${name}-${id}-label`" :for="id">
{{ label }}
</label>
<div v-if="$slots.error && error" class="pdap-input-error-message">
<slot name="error" />
</div>
<div v-else-if="error" class="pdap-input-error-message">{{ error }}</div>

<VueDatePicker
v-bind="{ ...$attrs, ...$props }"
v-model="date"
:state="error"
:dark="darkModePreference"
/>
</div>
</template>

<script setup lang="ts">
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue';
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css';
import { provideKey } from '../FormV2/util';
import { PdapFormProvideV2 } from '../FormV2/types';
import { PdapDatePickerProps } from './types';
import _isEqual from 'lodash/isEqual';
const { setValues, values, v$ } = inject<PdapFormProvideV2>(provideKey)!;
const { label, name, id } = defineProps<PdapDatePickerProps>();
const date = ref();
const error = computed(() => v$.value[name]?.$errors?.[0]?.$message);
// TODO: when decision made on whether to do dark them with inputs, pass as `:dark` prop to `VueDatePicker` (also add listener for updates )
const darkModeQuery = ref(window.matchMedia('(prefers-color-scheme: dark)'));
const darkModePreference = ref(darkModeQuery.value.matches);
function updateColorMode(e: MediaQueryListEvent) {
darkModePreference.value = e.matches;
}
onMounted(() => {
darkModeQuery.value.addEventListener('change', updateColorMode);
});
onUnmounted(() => {
darkModeQuery.value.removeEventListener('change', updateColorMode);
});
watch(
() => date.value,
(newDate) => {
// Sync values with underlying input, for form
if (newDate) setValues({ [name]: newDate });
}
);
watch(
// Welcome to the land of edge-cases.
() => values.value,
// In the (unlikely, unrecommended, but sometimes unfortunately necessary) event of form values changing upstream from a parent component:
(formValuesUpdated) => {
// Case 0: Values are equivalent, or the change was made here, do nothing.
/*
* Case 1: Value does not exist in form values object, meaning either:
** a. it has not been set by the upstream change, or
** b. has been changed to an empty string by `Form` after submit event
** In either case, clear state or keep it clear
*/
if (!formValuesUpdated[name]) date.value = undefined;
// Case 2 (rare): value has been programmatically updated upstream of `Form`
else if (
!_isEqual(formValuesUpdated[name], date.value) &&
formValuesUpdated[name] instanceof Date
) {
// Set the date to the value of the form value
date.value = formValuesUpdated[name];
}
}
);
</script>

<style>
@tailwind base;
.dp__theme_light,
.dp__theme_dark {
/* FULL LIST OF CUSTOMIZING OPTIONS from https://vue3datepicker.com/customization/theming/ */
/* --dp-background-color: #212121;
--dp-text-color: #fff;
--dp-hover-color: #484848;
--dp-hover-text-color: #fff;
--dp-hover-icon-color: #959595;
--dp-primary-color: #005cb2;
--dp-primary-disabled-color: #61a8ea;
--dp-primary-text-color: #fff;
--dp-secondary-color: #a9a9a9;
--dp-border-color: #2d2d2d;
--dp-menu-border-color: #2d2d2d;
--dp-border-color-hover: #aaaeb7;
--dp-border-color-focus: #aaaeb7;
--dp-disabled-color: #737373;
--dp-disabled-color-text: #d0d0d0;
--dp-scroll-bar-background: #212121;
--dp-scroll-bar-color: #484848;
--dp-success-color: #00701a;
--dp-success-color-disabled: #428f59;
--dp-icon-color: #959595;
--dp-danger-color: #e53935;
--dp-marker-color: #e53935;
--dp-tooltip-color: #3e3e3e;
--dp-highlight-color: rgb(0 92 178 / 20%);
--dp-range-between-dates-background-color: var(--dp-hover-color, #484848);
--dp-range-between-dates-text-color: var(--dp-hover-text-color, #fff);
--dp-range-between-border-color: var(--dp-hover-color, #fff); */
}
.dp__theme_light {
--dp-primary-color: rgb(var(--color-wine-neutral-700));
--dp-primary-text-color: rgb(var(--color-neutral-50));
}
.dp__theme_dark {
--dp-primary-color: rgb(var(--color-wine-neutral-300));
--dp-primary-text-color: rgb(var(--color-neutral-950));
}
.pdap-input .dp__input {
@apply text-lg pl-9;
}
div[role='gridcell'] {
@apply outline-none border-2 border-solid border-transparent;
}
</style>
53 changes: 53 additions & 0 deletions src/components/InputDatePicker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# InputSelect
Date picker component. Uses Vue3 Date Picker library under the hood.

## Props - required

| name | required? | types | description | default |
| ------- | ----------------------------- | -------- | ------------- | ------- |
| `id` | yes | `string` | id attr | |
| `label` | yes, if label slot not passed | `string` | label content | |
| `name` | yes | `string` | name attr | |

## Props - Vue3 Date Picker
The props interface extends the underlying component interface, so [all props available on the Vue 3 Date Picker component](https://vue3datepicker.com/props/modes/) are available to be passed.

## Slots

| name | required? | types | description | default |
| ------- | ----------------------------- | --------- | ------------------------------------ | ------- |
| `error` | no<sup>*</sup> | `Element` | slot content to be rendered as error | |
| `label` | yes, if label prop not passed | `Element` | slot content to be rendered as label | |

<sup>*</sup> Note: The error message is determined by Vuelidate via our form validation schema. If the error UI needs to be more complicated than a string that can be passed with the schema, pass an `\#error` slot and it will override the string.

## Example

```vue
<template>
<FormV2
id="form-id"
name="ice-cream-preference"
:schema="SCHEMA"
@submit="(values) => onSubmit({ values })"
@change="(values, event) => onChange({ values, event })"
>
<!-- Other inputs... -->
<InputDatePicker
:id="INPUT_DATE_NAME"
:name="INPUT_DATE_NAME"
position="left"
>
<template #label>
<h4>When will you next consume ice cream?</h4>
</template>
</InputDatePicker>
</FormV2>
</template>
<script setup>
import { InputDatePicker, FormV2 } from 'pdap-design-system';
</script>
...
```
Loading

0 comments on commit 835b11f

Please sign in to comment.