Skip to content

Commit f988a73

Browse files
committed
fix: #1656
feat: datepicker: add availableFrom and availableTo
1 parent 2b5298d commit f988a73

File tree

5 files changed

+135
-18
lines changed

5 files changed

+135
-18
lines changed

src/lib/datepicker/Datepicker.svelte

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,37 @@
66
import { Button, ToolbarButton, type DatepickerProps, cn } from "$lib";
77
import { datepicker } from "./theme";
88
9-
let { value = $bindable(), defaultDate = null, range = false, rangeFrom = $bindable(), rangeTo = $bindable(), locale = "default", firstDayOfWeek = 0, dateFormat, placeholder = "Select date", disabled = false, required = false, inputClass = "", color = "primary", inline = false, autohide = true, showActionButtons = false, title = "", onselect, onclear, onapply, btnClass, inputmode = "none", classes, monthColor = "alternative", monthBtnSelected = "bg-primary-500 text-white", monthBtn = "text-gray-700 dark:text-gray-300", class: className }: DatepickerProps = $props();
9+
let {
10+
value = $bindable(),
11+
defaultDate = null,
12+
range = false,
13+
rangeFrom = $bindable(),
14+
rangeTo = $bindable(),
15+
availableFrom = null,
16+
availableTo = null,
17+
locale = "default",
18+
firstDayOfWeek = 0,
19+
dateFormat,
20+
placeholder = "Select date",
21+
disabled = false,
22+
required = false,
23+
inputClass = "",
24+
color = "primary",
25+
inline = false,
26+
autohide = true,
27+
showActionButtons = false,
28+
title = "",
29+
onselect,
30+
onclear,
31+
onapply,
32+
btnClass,
33+
inputmode = "none",
34+
classes,
35+
monthColor = "alternative",
36+
monthBtnSelected = "bg-primary-500 text-white",
37+
monthBtn = "text-gray-700 dark:text-gray-300",
38+
class: className
39+
}: DatepickerProps = $props();
1040
1141
const dateFormatDefault = { year: "numeric", month: "long", day: "numeric" };
1242
// const dateFormatOptions = $derived(dateFormat ?? dateFormatDefault);
@@ -92,7 +122,27 @@
92122
showMonthSelector = !showMonthSelector;
93123
}
94124
125+
// Helper function to check if a date is available for selection
126+
function isDateAvailable(date: Date): boolean {
127+
const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
128+
129+
if (availableFrom) {
130+
const fromDate = new Date(availableFrom.getFullYear(), availableFrom.getMonth(), availableFrom.getDate());
131+
if (dateOnly < fromDate) return false;
132+
}
133+
134+
if (availableTo) {
135+
const toDate = new Date(availableTo.getFullYear(), availableTo.getMonth(), availableTo.getDate());
136+
if (dateOnly > toDate) return false;
137+
}
138+
139+
return true;
140+
}
141+
95142
function handleDaySelect(day: Date) {
143+
// Don't allow selection of unavailable dates
144+
if (!isDateAvailable(day)) return;
145+
96146
if (range) {
97147
if (!rangeFrom || (rangeFrom && rangeTo)) {
98148
rangeFrom = day;
@@ -262,7 +312,25 @@
262312
{/each}
263313
{#each daysInMonth as day}
264314
{@const current = day.getMonth() !== currentMonth.getMonth()}
265-
<Button type="button" color={isSelected(day) ? color : "alternative"} class={dayButton({ current, today: isToday(day), color: isInRange(day) ? color : undefined, class: clsx(classes?.dayButton) })} onclick={() => handleDaySelect(day)} onkeydown={handleCalendarKeydown} aria-label={day.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" })} aria-selected={isSelected(day)} role="gridcell">
315+
{@const available = isDateAvailable(day)}
316+
<Button
317+
type="button"
318+
color={isSelected(day) ? color : "alternative"}
319+
class={dayButton({
320+
current,
321+
today: isToday(day),
322+
color: isInRange(day) ? color : undefined,
323+
unavailable: !available,
324+
class: clsx(classes?.dayButton, !available && "opacity-50 cursor-not-allowed")
325+
})}
326+
onclick={() => handleDaySelect(day)}
327+
onkeydown={handleCalendarKeydown}
328+
aria-label={day.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" })}
329+
aria-selected={isSelected(day)}
330+
aria-disabled={!available}
331+
disabled={!available}
332+
role="gridcell"
333+
>
266334
{day.getDate()}
267335
</Button>
268336
{/each}
@@ -271,7 +339,14 @@
271339

272340
{#if showActionButtons && !showMonthSelector}
273341
<div class={actionButtons({ class: clsx(classes?.actionButtons) })}>
274-
<Button onclick={() => handleDaySelect(new Date())} {color} size="sm">Today</Button>
342+
<Button
343+
onclick={() => handleDaySelect(new Date())}
344+
{color}
345+
size="sm"
346+
disabled={!isDateAvailable(new Date())}
347+
>
348+
Today
349+
</Button>
275350
<Button onclick={handleClear} color="red" size="sm">Clear</Button>
276351
<Button onclick={handleApply} {color} size="sm">Apply</Button>
277352
</div>
@@ -291,6 +366,8 @@
291366
@prop range = false
292367
@prop rangeFrom = $bindable()
293368
@prop rangeTo = $bindable()
369+
@prop availableFrom = null
370+
@prop availableTo = null
294371
@prop locale = "default"
295372
@prop firstDayOfWeek = 0
296373
@prop dateFormat
@@ -313,4 +390,4 @@
313390
@prop monthBtnSelected = "bg-primary-500 text-white"
314391
@prop monthBtn = "text-gray-700 dark:text-gray-300"
315392
@prop class: className
316-
-->
393+
-->

src/lib/datepicker/theme.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ export const datepicker = tv({
5555
},
5656
today: {
5757
true: { dayButton: "font-bold" }
58+
},
59+
unavailable: {
60+
true: { dayButton: "opacity-50 cursor-not-allowed hover:bg-gray-100 dark:hover:bg-gray-700" }
5861
}
5962
},
6063
compoundVariants: []
61-
});
64+
});

src/lib/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ export interface DarkmodeProps extends HTMLButtonAttributes {
480480
ariaLabel?: string;
481481
}
482482

483+
// datepicker
483484
// datepicker
484485
export type DateOrRange = Date | { from?: Date; to?: Date };
485486

@@ -489,6 +490,8 @@ export interface DatepickerProps extends DatepickerVariants, Omit<HTMLAttributes
489490
range?: boolean;
490491
rangeFrom?: Date;
491492
rangeTo?: Date;
493+
availableFrom?: Date | null;
494+
availableTo?: Date | null;
492495
locale?: string;
493496
firstDayOfWeek?: number;
494497
dateFormat?: Intl.DateTimeFormatOptions;

src/routes/docs/components/datepicker.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,52 @@ Listen for date selection events using the `onselect` event.
227227
</div>
228228
```
229229

230+
## Restricting the selectable date range
231+
232+
```svelte example class="h-[430px]"
233+
<script lang="ts">
234+
import { Datepicker, P, type DateOrRange } from "flowbite-svelte";
235+
let selectedDate = $state<Date | undefined>(undefined);
236+
237+
// Helper function to add/subtract days
238+
function addDays(date: Date, days: number): Date {
239+
const result = new Date(date);
240+
result.setDate(result.getDate() + days);
241+
return result;
242+
}
243+
244+
// Calculate dates relative to today
245+
const today = new Date();
246+
const availableFrom = addDays(today, -10); // 10 days ago
247+
const availableTo = addDays(today, 10); // 10 days from now
248+
249+
function formatDate(date: Date): string {
250+
return date.toLocaleDateString('en-US', {
251+
year: 'numeric',
252+
month: 'long',
253+
day: 'numeric'
254+
});
255+
}
256+
</script>
257+
258+
<Datepicker
259+
bind:value={selectedDate}
260+
{availableFrom}
261+
{availableTo}
262+
placeholder="Select available date"
263+
/>
264+
265+
<P class="mt-4">Available from: {formatDate(availableFrom)} to: {formatDate(availableTo)}</P>
266+
267+
<P>Selected date: {selectedDate ? formatDate(selectedDate) : 'None selected'}</P>
268+
269+
<!-- Show some context -->
270+
<P class="text-sm text-gray-600 mt-4">
271+
Today: {formatDate(today)}<br>
272+
Range: 10 days before today to 10 days after today
273+
</P>
274+
```
275+
230276
## Component data
231277

232278
The component has the following props, type, and default values. See [types page](/docs/pages/typescript) for type information.

src/routes/docs/plugins/wysiwyg.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,7 @@ Use `FormatButtonGroup` to enable typography styling, formatting and marking suc
123123
editorInstance?.commands.setContent(content);
124124
}
125125
126-
const content = `<p>Flowbite-Svelte is an <strong>open-source library of UI components</strong> based on the utility-first Tailwind CSS framework featuring dark mode support, a Figma design system, and more.</p><p>It includes all of the commonly used components that a website requires, such as buttons, dropdowns, navigation bars, modals, datepickers, advanced charts and the list goes on.</p><p>Here is an example of a code block:</p>
127-
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
128-
{
129-
if (i % 15 == 0)
130-
console.log("FizzBuzz");
131-
else if (i % 3 == 0)
132-
console.log("Fizz");
133-
else if (i % 5 == 0)
134-
console.log("Buzz");
135-
else
136-
console.log(i);
137-
}</code></pre>
138-
<p>Learn more about all components from the <a href="https://flowbite-svelte.com/docs/pages/quickstart">Flowbite-Svelte Docs</a>.</p>`;
126+
const content = `<p>Flowbite-Svelte is an <strong>open-source library of UI components</strong> based on the utility-first Tailwind CSS framework featuring dark mode support, a Figma design system, and more.</p><p>It includes all of the commonly used components that a website requires, such as buttons, dropdowns, navigation bars, modals, datepickers, advanced charts and the list goes on.</p>`;
139127
</script>
140128
141129
<TextEditor bind:editor={editorInstance} {content}>

0 commit comments

Comments
 (0)