MiniCalendar
is a server-only Vaadin component for displaying and
selecting LocalDate
values.
The sunny side 🌞 | The dark side (of the moon 🌒) |
---|---|
I am building a small Diary application and needed a way to easily let users select
days in a month with a single click (just to make it as comfortable as possible). I first tried to use the built-in
DatePicker
component, but unfortunately there's no way to have this component "always open".
Hence, I decided to build one on my own.
The next thought was "oh, this could be useful for somebody else as well" and I quickly checked how "hard" it is to contribute components to the Vaadin Directory. Luckily this is not problematic at all and I love that it's this easy ❤️
I hope you enjoy the component, if there's anything you want to chit-chat about feel free to say hello@downdrown.at.
The internals are built on the Java Time API , the displayed values are localized with the locale that is set for the current UI.
The component implements the LocaleChangeObserver
. It listens for locale changes and will redraw itself when the
locale has changed.
It is highly customizable, offers a lot of configuration- and interaction possibilities. You can either use the built-in
MiniCalendarVariant
or provide custom CSS classes using the setDayStyleProvider()
.
The Component is designed to have a single value selected. It implements the HasValue
interface and can therefore be used with a Binder
like any other default Vaadin field.
You can listen to value changes as well as YearMonth
changes which will be triggered when
the user navigates through the months or the component gets a new value set which differs
from the previous YearMonth
value.
var miniCalendar = new MiniCalendar();
miniCalendar.addValueChangeListener(event -> {
Notification.show("Value changed to " + event.getValue());
});
miniCalendar.addYearMonthChangeListener(event -> {
Notification.show("Value changed to " + event.getValue());
});
When adding a listener you'll get an instance of Registration
back which can be used to remove said listener again.
var registration = miniCalendar.addYearMonthChangeListener(...);
registration.remove();
You can dynamically en- or disable certain days in the calendar view by setting up a DayEnabledProvider
with
setDayEnabledProvider()
. The method takes a SerializablePredicate<LocallDate>
as argument which will be used to
evaluate the enabled state of a day when rendering the component.
Warning
DayEnabledProvider
could cause performance issues!The
DayEnabledProvider
may be called quite a few times during the lifetime of aMiniCalendar
, you should ensure that the backing function is not an expensive backend operation, else you could experience some performance issues.
var disabledDays = getDisabledDays();
var miniCalendar = new MiniCalendar();
miniCalendar.addThemeVariants(MiniCalendarVariant.HOVER_DAYS, MiniCalendarVariant.HIGHLIGHT_WEEKEND);
miniCalendar.setDayEnabledProvider(value -> !disabledDays.contains(value));
Warning Disabled days can still be selected by the server!
Even though a day is disabled in the calendar view, it can still be marked as the selected value from server side. Disabled days are not considered as invalid value from the server, the feature's purpose is to indicate valid selections to the user.
Details
var disabledDays = getDisabledDays();
var miniCalendar = new MiniCalendar();
miniCalendar.setValue(disabledDays.get(0));
miniCalendar.addThemeVariants(MiniCalendarVariant.HOVER_DAYS, MiniCalendarVariant.HIGHLIGHT_WEEKEND);
miniCalendar.setDayEnabledProvider(value -> !disabledDays.contains(value));
The component is based on the Lumo Theme, and it's appearance can easily be changed by using the built-in Theme Variants.
To apply a Theme Variant you simply call the addThemeVariants()
method.
miniCalendar.addThemeVariants(MiniCalendarVariant.ROUNDED);
miniCalendar.addThemeVariants(MiniCalendarVariant.HIGHLIGHT_WEEKEND);
To remove an already applied Theme Variant simply call the removeThemeVariants()
method.
miniCalendar.removeThemeVariants(MiniCalendarVariant.HOVER_DAYS);
You can combine multiple Theme Variants to change the component's appearance.
Show examples
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
The component's state implicitly affects the appearance of the component. For instance a disabled component will look gray-ish to indicate that the user cannot interact with it. A component in read only state won't change the cursor when hovering over interaction parts and hide the navigation buttons.
Check out these examples of the component in different states.
Read Only
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Disabled
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Disabled, Read Only
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
Light Mode | Dark Mode |
---|---|
You can easily provide custom css classes for the day components by using the setDayStyleProvider()
method.
The method takes a SerializableFunction<LocalDate, List<String>>
as argument which will be used to
evaluate the additional classes for a day when rendering the component.
Warning
DayStyleProvider
could cause performance issues!The
DayStyleProvider
may be called quite a few times during the lifetime of aMiniCalendar
, you should ensure that the backing function is not an expensive backend operation, else you could experience some performance issues.
funky.css
@-webkit-keyframes bounce {
0%, 20%, 50%, 80%, 100% {-webkit-transform: translateY(0);}
40% {-webkit-transform: translateY(-30px);}
60% {-webkit-transform: translateY(-15px);}
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {transform: translateY(0);}
40% {transform: translateY(-30px);}
60% {transform: translateY(-15px);}
}
.minicalendar .day.funky {
color: var(--lumo-primary-contrast-color);
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient(
90deg,
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
);
}
.bounce:hover {
-webkit-animation-name: bounce;
animation-name: bounce;
}
StyleProviderShowcaseView.java
@Route("/styleprovider")
@PageTitle("MiniCalendar Showcase")
@CssImport("css/funky.css")
public class StyleProviderShowcaseView extends VerticalLayout {
public StyleProviderShowcaseView() {
var funkyDays = getFunkyDays();
var miniCalendar = new MiniCalendar();
miniCalendar.setValue(funkyDays.get(0));
miniCalendar.addThemeVariants(MiniCalendarVariant.HOVER_DAYS, MiniCalendarVariant.HIGHLIGHT_WEEKEND);
miniCalendar.setDayStyleProvider(day -> {
if (funkyDays.contains(day)) {
return List.of("funky", "bounce");
}
return null;
});
add(miniCalendar);
}
}
The project contains a showcase module that displays some usages of the Add-on.
To start the showcase check out the repository and run ./mvnw jetty:run -pl :showcase
in the root directory.