Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cypress/e2e/spec.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,18 @@ describe('basic', () => {
.should('have.css', 'background-color')
.and('match', /(rgb\(0, 0, 0\))/)
})

it('changes month and year from dropdowns', function () {
cy.visit('localhost:8000')

// Months are 0-indexed: 10 => November
cy.get('.preachjs-calendar--header-month').select('10')
cy.get('.preachjs-calendar--header-year').select('2030')

cy.get('.preachjs-calendar--grid').should(
'have.attr',
'aria-label',
'Nov 2030'
)
})
})
15 changes: 15 additions & 0 deletions example/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@ nav a {
font-weight: 600;
}

.preachjs-calendar
.preachjs-calendar--header
.preachjs-calendar--header-selectors {
display: flex;
gap: 6px;
}

.preachjs-calendar .preachjs-calendar--header select {
border: 1px solid #efefef;
border-radius: 6px;
padding: 4px 8px;
font-size: 14px;
background-color: white;
}

.preachjs-calendar .preachjs-calendar--grid {
width: 100%;
border-collapse: collapse;
Expand Down
64 changes: 63 additions & 1 deletion src/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
sortByDate,
} from './utils.js'

const YEAR_RANGE_OFFSET = 100
const YEAR_OPTION_COUNT = YEAR_RANGE_OFFSET * 2 + 1

/**
* @param {import("./calendar").CalendarProps} props
* @returns
Expand Down Expand Up @@ -72,6 +75,18 @@ export function Calendar({
})

let tabIndexOffset = 3
const monthLabel = new Intl.DateTimeFormat(locale, { month: 'long' })
const yearInView = activeDate$.value.getFullYear()
const monthInView = activeDate$.value.getMonth()
const monthOptions = new Array(12).fill(null).map((_, monthIndex) => {
return {
value: monthIndex,
label: monthLabel.format(new Date(2000, monthIndex, 1)),
}
})
const yearOptions = new Array(YEAR_OPTION_COUNT)
.fill(null)
.map((_, offset) => yearInView - YEAR_RANGE_OFFSET + offset)

return (
<div class="preachjs-calendar">
Expand All @@ -87,7 +102,38 @@ export function Calendar({
>
<ArrowLeft />
</button>
<h2 aria-hidden="true">{getMonthAndYearFromDate(activeDate$.value)}</h2>
<div class="preachjs-calendar--header-selectors">
<select
class="preachjs-calendar--header-month"
aria-label="Select month"
value={String(monthInView)}
onChange={e => {
const nextMonth = +e.target.value
activeDate$.value = updateDateFromParts(activeDate$.value, {
month: nextMonth,
})
}}
>
{monthOptions.map(month => (
<option value={String(month.value)}>{month.label}</option>
))}
</select>
<select
class="preachjs-calendar--header-year"
aria-label="Select year"
value={String(yearInView)}
onChange={e => {
const nextYear = +e.target.value
activeDate$.value = updateDateFromParts(activeDate$.value, {
year: nextYear,
})
}}
>
{yearOptions.map(year => (
<option value={String(year)}>{year}</option>
))}
</select>
</div>
<button
aria-label="Next"
tabIndex={1}
Expand Down Expand Up @@ -326,3 +372,19 @@ function tieHoveredElmToSignal(window, sign$) {
function mergeStyle(arr, ...additional) {
return additional.filter(Boolean).concat(arr.filter(Boolean)).join(' ')
}

function getDaysInMonth(monthIndex, year) {
return new Date(year, monthIndex + 1, 0).getDate()
}

function updateDateFromParts(sourceDate, nextValue) {
const nextDate = new Date(sourceDate)
const nextMonth = nextValue.month ?? nextDate.getMonth()
const nextYear = nextValue.year ?? nextDate.getFullYear()
const nextDay = Math.min(
nextDate.getDate(),
getDaysInMonth(nextMonth, nextYear)
)
nextDate.setFullYear(nextYear, nextMonth, nextDay)
return nextDate
}
Loading