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
101 changes: 101 additions & 0 deletions cypress/e2e/spec.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,105 @@ describe('basic', () => {
'Nov 2030'
)
})

it('should disable dates before min', function () {
cy.visit('localhost:8000')

const today = new Date()
const tomorrow = new Date(today)
tomorrow.setDate(today.getDate() + 1)

const formatDateInput = date => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

cy.get('input[name="min"]')
.invoke('val', formatDateInput(tomorrow))
.trigger('change')

const todayISO = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate()
).toISOString()
cy.get(`[data-date="${todayISO}"]`).should(
'have.class',
'preachjs-calendar--grid-cell-disabled'
)
cy.get(`[data-date="${todayISO}"] > button`).click({ force: true })

cy.get('.selected-text').should('have.text', 'Selected: None')
})

it('should disable dates after max', function () {
cy.visit('localhost:8000')

const today = new Date()
const yesterday = new Date(today)
yesterday.setDate(today.getDate() - 1)

const formatDateInput = date => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

cy.get('input[name="max"]')
.invoke('val', formatDateInput(yesterday))
.trigger('change')

const todayISO = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate()
).toISOString()
cy.get(`[data-date="${todayISO}"]`).should(
'have.class',
'preachjs-calendar--grid-cell-disabled'
)
cy.get(`[data-date="${todayISO}"] > button`).click({ force: true })

cy.get('.selected-text').should('have.text', 'Selected: None')
})

it('should allow selection within min and max', function () {
cy.visit('localhost:8000')

const today = new Date()
const yesterday = new Date(today)
yesterday.setDate(today.getDate() - 1)
const tomorrow = new Date(today)
tomorrow.setDate(today.getDate() + 1)

const formatDateInput = date => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

cy.get('input[name="min"]')
.invoke('val', formatDateInput(yesterday))
.trigger('change')
cy.get('input[name="max"]')
.invoke('val', formatDateInput(tomorrow))
.trigger('change')

const todayISO = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate()
).toISOString()
cy.get(`[data-date="${todayISO}"]`).should(
'not.have.class',
'preachjs-calendar--grid-cell-disabled'
)
cy.get(`[data-date="${todayISO}"] > button`).click()

cy.get('.selected-text').should('not.have.text', 'Selected: None')
})
})
40 changes: 40 additions & 0 deletions example/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ import './main.css'
const mode = signal('single')
const selectedDate = signal()
const readOnly = signal(false)
const minDate = signal()
const maxDate = signal()

function parseDateInput(value) {
if (!value) return undefined
const [year, month, day] = value.split('-').map(Number)
return new Date(year, month - 1, day)
}

function formatDateInput(date) {
if (!date) return ''
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

const App = () => {
return (
Expand Down Expand Up @@ -43,11 +59,35 @@ const App = () => {
/>{' '}
Read Only
</label>
<label>
Min{' '}
<input
name="min"
type="date"
value={formatDateInput(minDate.value)}
onChange={e => {
minDate.value = parseDateInput(e.target.value)
}}
/>
</label>
<label>
Max{' '}
<input
name="max"
type="date"
value={formatDateInput(maxDate.value)}
onChange={e => {
maxDate.value = parseDateInput(e.target.value)
}}
/>
</label>
</div>
<Calendar
mode={mode.value}
readOnly={readOnly.value}
value={selectedDate.value}
min={minDate.value}
max={maxDate.value}
onSelect={value => {
selectedDate.value = value
}}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"package:lint": "publint",
"example:build": "node build.js",
"example:dev": "node build.js --dev",
"test:e2e": "node ./script/e2e-run.js",
"test:e2e": "node ./scripts/e2e-run.js",
"test": "echo true",
"next": "bumpp",
"fix": "prettier --write .",
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ function App() {
| `arrowLeft` | Icon Rendered on the left of the month selector | `&lt;` |
| `arrowRight` | Icon Rendered on the right of the month selector | `&gt;` |
| `readOnly` | Change to readOnly mode, date selection will do nothing | `false` |
| `min` | Minimum selectable date (inclusive) | |
| `max` | Maximum selectable date (inclusive) | |

## License

Expand Down
2 changes: 2 additions & 0 deletions src/calendar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ export type CalendarProps<M extends CalendarMode = CalendarMode> = {
mode?: M
readOnly: boolean
onSelect?: (nextValue: CalendarValue<M>) => void
min?: Date
max?: Date
}
16 changes: 15 additions & 1 deletion src/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export function Calendar({
weekdayFormat = 'narrow',
arrowLeft: ArrowLeft = () => <>&lt;</>,
arrowRight: ArrowRight = () => <>&gt;</>,
min = undefined,
max = undefined,
}) {
const selecting = useRef(false)
const refRange$ = useSignal([])
Expand Down Expand Up @@ -199,12 +201,18 @@ export function Calendar({
isRangeEnd && 'preachjs-calendar--grid-cell-end',
]

if (dateItem.previousMonth || dateItem.nextMonth) {
const isDisabled =
dateItem.previousMonth ||
dateItem.nextMonth ||
isOutOfRange(dateItem.date, min, max)

if (isDisabled) {
return (
<td
role="gridcell"
data-row={rowIndex}
data-col={colIndex}
data-date={dateItem.date.toISOString()}
aria-disabled="true"
class={mergeStyle(
gridCellStyles,
Expand Down Expand Up @@ -377,6 +385,12 @@ function getDaysInMonth(monthIndex, year) {
return new Date(year, monthIndex + 1, 0).getDate()
}

function isOutOfRange(date, min, max) {
if (min && date.getTime() < min.getTime()) return true
if (max && date.getTime() > max.getTime()) return true
return false
}

function updateDateFromParts(sourceDate, nextValue) {
const nextDate = new Date(sourceDate)
const nextMonth = nextValue.month ?? nextDate.getMonth()
Expand Down
Loading