Skip to content

Commit

Permalink
[aform] adate refactor (#98)
Browse files Browse the repository at this point in the history
* wip: adate refactor

* wip: datepicker styling, v-model wip

* fix: minor fixes

* fix: add changelogs

* wip: datepicker, v-model WIP, test

* wip: adatepicker tests

* fix: adatepicker emits

* fix: remove extra dev dependencies

* fix: update changelogs

* test: update tests

* fix: add changelogs

* fix: cleanup ADatepicker component

* fix: add missing prop for ADate

* fix: add changelogs

* fix: update story styling

* fix: bump version to 0.2.7

* fix: minor cleanup

* test: fix datepicker test

* styled today, selected, and cursor for datepicker.

* fix: spawn datepicker on click

* fix: minor cleanup

* fix: remove obsolete changelogs

* fix: use focus composable to conditionally apply keyboard navigation

* fix: add changelogs

* ci: add build artefacts

---------

Co-authored-by: Tyler Matteson <[email protected]>
Co-authored-by: Rohan Bansal <[email protected]>
Co-authored-by: Curt Rabinak <[email protected]>
  • Loading branch information
4 people authored May 6, 2024
1 parent 5ad07a2 commit 2d57d63
Show file tree
Hide file tree
Showing 25 changed files with 488 additions and 326 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
registry-url: https://npm.pkg.github.com/

- name: Install Rush
run: node common/scripts/install-run-rush.js install
run: node common/scripts/install-run-rush.js install && node common/scripts/install-run-rush.js build

- name: Run Tests
working-directory: ./aform
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
registry-url: https://npm.pkg.github.com/

- name: Install Rush
run: node common/scripts/install-run-rush.js install
run: node common/scripts/install-run-rush.js install && node common/scripts/install-run-rush.js build

- name: Run Tests
working-directory: ./atable
Expand Down
331 changes: 74 additions & 257 deletions aform/src/components/form/ADate.vue
Original file line number Diff line number Diff line change
@@ -1,277 +1,94 @@
<template>
<div
v-if="!readonly"
:event="event"
:colIndex="colIndex"
:rowIndex="rowIndex"
:tableid="tableid"
class="adate"
tabindex="0"
ref="adatepicker">
<table>
<tr>
<td @click="previousMonth" :tabindex="-1">&lt;</td>
<th colspan="5">{{ monthAndYear }}</th>
<td @click="nextMonth" :tabindex="-1">&gt;</td>
</tr>
<tr v-for="rowNo in numberOfRows" :key="rowNo">
<!-- TODO: (style) remove inline styling and replace with theme package -->
<td
v-for="colNo in numberOfColumns"
:key="(rowNo - 1) * numberOfColumns + colNo"
:contenteditable="false"
:spellcheck="false"
:tabindex="0"
:style="{
border: isSelectedDate(currentDates[(rowNo - 1) * numberOfColumns + colNo])
? '2px solid var(--focus-cell-outline)'
: 'none',
borderBottomColor: isTodaysDate(currentDates[(rowNo - 1) * numberOfColumns + colNo])
? 'var(--focus-cell-outline)'
: 'none',
}"
@click.prevent.stop="selectDate($event, (rowNo - 1) * numberOfColumns + colNo)"
:class="{
todaysdate: isTodaysDate(currentDates[(rowNo - 1) * numberOfColumns + colNo]),
selecteddate: isSelectedDate(currentDates[(rowNo - 1) * numberOfColumns + colNo]),
}">
{{ new Date(currentDates[(rowNo - 1) * numberOfColumns + colNo]).getDate() }}
</td>
</tr>
</table>
<div>
<input
ref="dateRef"
type="date"
:id="uuid"
:disabled="readonly"
:required="required"
:value="inputDate"
@click="showPicker" />
<label :for="uuid">{{ label }}</label>
<p v-show="validation.errorMessage" v-html="validation.errorMessage"></p>
</div>
</template>

<script setup lang="ts">
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
import { TableDataStore } from '@stonecrop/atable'
import { defaultKeypressHandlers, useKeyboardNav } from '@stonecrop/utilities'
const props = defineProps<{
colIndex?: number
rowIndex?: number
tableid?: string
event?: Event
indent?: number
readonly?: boolean
}>()
const tableData = inject<TableDataStore>(props.tableid)
const numberOfRows = 6
const numberOfColumns = 7
const todaysDate = new Date()
const selectedDate = ref<Date>()
const currentMonth = ref<number>()
const currentYear = ref<number>()
const currentDates = ref<number[]>([])
// const width = ref('')
onMounted(async () => {
let cellDate = tableData.cellData<string | number | Date>(props.colIndex, props.rowIndex)
if (cellDate) {
if (!(cellDate instanceof Date)) {
cellDate = new Date(cellDate)
}
selectedDate.value = cellDate
currentMonth.value = selectedDate.value.getMonth()
currentYear.value = selectedDate.value.getFullYear()
} else {
currentMonth.value = todaysDate.getMonth()
currentYear.value = todaysDate.getFullYear()
}
renderMonth()
await nextTick()
const $selectedDate = document.getElementsByClassName('selecteddate')
if ($selectedDate.length > 0) {
;($selectedDate[0] as HTMLElement).focus()
} else {
const $todaysDate = document.getElementsByClassName('todaysdate')
if ($todaysDate.length > 0) {
;($todaysDate[0] as HTMLElement).focus()
}
}
})
watch([currentMonth, currentYear], () => {
renderMonth()
})
const renderMonth = () => {
currentDates.value = []
const firstOfMonth = new Date(currentYear.value, currentMonth.value, 1)
const monthStartWeekday = firstOfMonth.getDay()
const calendarStartDay = firstOfMonth.setDate(firstOfMonth.getDate() - monthStartWeekday)
for (let dayIndex of Array(43).keys()) {
currentDates.value.push(calendarStartDay + dayIndex * 86400000)
}
}
const previousYear = () => {
currentYear.value -= 1
}
const nextYear = () => {
currentYear.value += 1
}
const previousMonth = () => {
if (currentMonth.value == 0) {
currentMonth.value = 11
previousYear()
} else {
currentMonth.value -= 1
import { ref } from 'vue'
withDefaults(
defineProps<{
label: string
required?: boolean
readonly?: boolean
uuid?: string
validation?: Record<string, any>
}>(),
{
validation: () => ({ errorMessage: '&nbsp;' }),
}
}
)
const nextMonth = () => {
if (currentMonth.value == 11) {
currentMonth.value = 0
nextYear()
} else {
currentMonth.value += 1
}
}
const inputDate = defineModel<string | number | Date>()
const dateRef = ref<HTMLInputElement | null>(null)
const isTodaysDate = (day: string | number | Date) => {
if (currentMonth.value !== todaysDate.getMonth()) {
return
const showPicker = () => {
if (dateRef.value) {
dateRef.value.showPicker()
}
return todaysDate.toDateString() === new Date(day).toDateString()
}
const isSelectedDate = (day: string | number | Date) => {
return new Date(day).toDateString() === new Date(selectedDate.value).toDateString()
}
const selectDate = (event: Event, currentIndex: number) => {
selectedDate.value = new Date(currentDates.value[currentIndex])
updateData()
// TODO: (typing) figure out a way to close datepicker
// context.refs.adatepicker.destroy()
}
const updateData = () => {
// TODO: check proper date format to feed back (assuming number for now)
tableData.setCellData(props.rowIndex, props.colIndex, selectedDate.value.getTime())
}
// const dayWidth = computed(() => {
// const widthValue = Number(width.value.replace('px', ''))
// return `${widthValue / (numberOfColumns - 1)}px`
// })
const monthAndYear = computed(() => {
return new Date(currentYear.value, currentMonth.value, 1).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
})
})
// setup keyboard navigation
useKeyboardNav([
{
parent: 'table.adate',
selectors: 'td',
handlers: {
...defaultKeypressHandlers,
...{
'keydown.pageup': previousMonth,
'keydown.shift.pageup': previousYear,
'keydown.pagedown': nextMonth,
'keydown.shift.pagedown': nextYear,
},
},
},
])
</script>

<style scoped>
@import '@/theme/aform.css';
.adate {
border: 2px solid var(--focus-cell-outline);
div {
min-width: 40ch;
border: 1px solid transparent;
padding: 0rem;
margin: 0rem;
margin-right: 1ch;
}
input {
width: calc(100% - 1ch);
outline: 1px solid transparent;
border: 1px solid var(--input-border-color);
padding: 1ch 0.5ch 0.5ch 1ch;
margin: calc(1.15rem / 2) 0 0 0;
min-height: 1.15rem;
border-radius: 0.25rem;
}
p,
label {
color: var(--input-label-color);
display: block;
min-height: 1.15rem;
padding: 0rem;
margin: 0rem;
border: 1px solid transparent;
margin-bottom: 0.25rem;
}
p {
width: 100%;
color: red;
font-size: 85%;
}
label {
z-index: 2;
font-size: 80%;
position: absolute;
z-index: 100;
font-size: var(--table-font-size);
display: inline-table;
background-color: var(--row-color-zebra-light);
color: var(--cell-text-color);
outline: none;
width: calc(100% - 4px);
}
.adate tr {
height: 1.15rem;
text-align: center;
vertical-align: middle;
}
.adate td {
border: 2px solid transparent;
min-width: 2.25ch; /* this doesn't zoom correctly */
}
.adate td:hover {
border: 2px solid var(--focus-cell-outline);
}
.adate td {
border: 1px;
border-style: solid;
border-color: var(--cell-border-color);
border-radius: 0px;
box-sizing: border-box;
margin: 0px;
outline: none;
box-shadow: none;
color: var(--cell-text-color);
text-overflow: ellipsis;
overflow: hidden;
padding-left: 0.5ch;
padding-right: 0.5ch;
}
.adate td:focus,
.adate td:focus-within {
background-color: var(--focus-cell-background);
outline-width: 2px;
outline-style: solid;
outline-color: var(--focus-cell-outline);
box-shadow: none;
overflow: hidden;
min-height: 1.15em;
max-height: 1.15em;
overflow: hidden;
}
button {
background-color: var(--row-color-zebra-light);
border: none;
padding: 0px;
margin: 0px;
color: var(--cell-text-color);
outline: none;
font-size: var(--table-font-size);
}
.dateheader {
font-weight: 700;
display: flex;
align-items: center;
justify-content: space-between;
background: white;
margin: calc(-1.5rem - calc(2.15rem / 2)) 0 0 1ch;
padding: 0 0.25ch 0 0.25ch;
}
.adate .todaysdate {
border-bottom-color: var(--focus-cell-outline);
input:focus {
border: 1px solid var(--input-active-border-color);
}
.adate .selecteddate {
border: 2px solid var(--focus-cell-outline);
input:focus + label {
color: var(--input-active-label-color);
}
</style>
Loading

0 comments on commit 2d57d63

Please sign in to comment.