nepali_calendar_plus 0.1.1
nepali_calendar_plus: ^0.1.1 copied to clipboard
A feature-rich flutter package for implementing a Nepali calendar system in your applications.
Nepali Calendar Plus #
A feature-rich Flutter package for implementing Nepali (Bikram Sambat) calendar in your applications with extensive customization options, event management, and bilingual support.
Preview #
![]() Month calendar |
![]() Week strip |
![]() Year view |
![]() Date picker |
![]() Date range picker |
Features #
- ✅ Full Nepali (Bikram Sambat) calendar support
- ✅ Modal date picker dialog
- ✅ Horizontal and vertical calendar views
- ✅ Bilingual support (Nepali/English)
- ✅ Event management with custom types
- ✅ Holiday highlighting
- ✅ Programmatic navigation with controller
- ✅ Customizable weekend patterns
- ✅ Week start configuration (Sunday/Monday)
- ✅ Custom builders for complete UI control
- ✅ English date conversion display
- ✅ Extensive styling options
- ✅ Today's date highlighting
- ✅ Previous/next month day display
- ✅ Full-year view — twelve months on one screen
- ✅ Theming with light/dark mode, following your app's
ColorScheme - ✅ Multiple events per date
- ✅ Screen-reader labels, keyboard navigation and haptics out of the box
Theming and dark mode #
Wrap any calendar in a NepaliCalendarTheme:
NepaliCalendarTheme(
data: NepaliCalendarThemeData.fromContext(context),
child: NepaliCalendar(),
)
For app-wide theming, put it above the Navigator — MaterialApp.builder is
the simplest place:
MaterialApp(
builder: (context, child) => NepaliCalendarTheme(
data: NepaliCalendarThemeData.fromContext(context),
child: child!,
),
home: const HomePage(),
)
Placed inside home: it covers that subtree and any dialog opened from it
(including showNepaliDatePicker), but not a route pushed with
Navigator.push — the pushed route is a sibling under the Navigator, not a
descendant of home:. This is ordinary Flutter behaviour and applies equally
to Material's own Theme.
fromContext derives the palette and typography from your app's Material
ColorScheme and TextTheme, so the calendar follows your light/dark mode
automatically. You can also use NepaliCalendarThemeData.light(), .dark(),
or .fromColorScheme(scheme), and adjust individual values with copyWith:
NepaliCalendarTheme(
data: NepaliCalendarThemeData.fromContext(context)
.copyWith(todayColor: Colors.orange),
child: NepaliCalendar(),
)
Styling resolves in this order, first match winning:
- an explicit
calendarStylepassed to the widget; - the nearest enclosing
NepaliCalendarTheme; - the built-in defaults.
Theming is therefore opt-in: existing code that passes calendarStyle, or
passes nothing at all, looks exactly as it did before. Prefer copyWith on the
theme over passing a calendarStyle, since an explicit style replaces the
theme rather than merging with it.
Accessibility and platform feel #
Every calendar is usable without sight or without a touchscreen, with no extra configuration.
Screen readers. Each date announces itself in full, in the configured language, rather than as a bare number:
बैशाख, १५, २०८१, शनिबार, आज, बिदा, २ कार्यक्रम
Baisakh, 15, 2081, Saturday, Today, Holiday, 2 events
The month title is exposed as a header, and the navigation chevrons are
labelled. A custom cellBuilder replaces the default cell entirely, including
its semantics — supply your own Semantics wrapper if you use one.
Keyboard. Date cells take focus, so Tab reaches the grid, the arrow keys
move between dates by position, and Enter or Space selects the focused
date. This matters most on desktop and web, where the calendar was previously
mouse-only.
Haptics. Off by default, so upgrading never makes an app start vibrating.
Opt in to have selecting a date answer the tap through touch — light is the
recommended value:
NepaliCalendar(
calendarStyle: const NepaliCalendarStyle(
config: CalendarConfig(hapticFeedback: CalendarHaptics.light),
),
)
CalendarHaptics |
Android | iOS |
|---|---|---|
none (default) |
nothing | nothing |
selection |
CLOCK_TICK |
selection generator |
light (recommended) |
VIRTUAL_KEY |
impact, light |
medium |
KEYBOARD_TAP |
impact, medium |
heavy |
CONTEXT_CLICK |
impact, heavy |
Testing haptics? Only a physical phone can render them. Desktop, web and the iOS Simulator have no haptic hardware, so every value is silently a no-op there. On Android they are also gated behind the system's touch-feedback setting — if that is off, nothing reaches the user.
selectionis the subtlest value and on many Android phones cannot be felt at all; it maps to the constant meant for a picker wheel passing detents. Uselightor firmer for taps.
Text scaling. Cell text follows the system font setting as far as the cell can hold it, then stops, so a user at 200% gets larger dates rather than a clipped grid.
Year view #
NepaliYearCalendar(
year: 2081,
eventList: events,
onDaySelected: (date) => print(date),
)
Twelve months in a scrollable grid, two per row by default (monthsPerRow),
responsive from phone to desktop, with today, the selected date, event dots and
holiday indicators. Also supports onYearChanged, jumpToSelectedMonth,
headerBuilder and monthTitleBuilder, and picks up NepaliCalendarTheme
like everything else.
Events #
A date may carry any number of events. Mark holidays with
CalendarEvent.isHoliday:
NepaliCalendar<String>(
eventList: [
CalendarEvent(date: date, additionalInfo: 'Standup'),
CalendarEvent(date: date, isHoliday: true, additionalInfo: 'Dashain'),
],
calendarBuilder: CalendarBuilder<String>(
cellBuilder: (data) => MyCell(
events: data.events, // every event that day
isHoliday: data.isHoliday, // true if any of them is a holiday
),
),
)
checkIsHoliday is deprecated and no longer required — it was never read.
Upgrading to 0.1.0 #
0.1.0 is a correctness release. It removes no APIs, but it does fix behaviour you may have worked around — which is why it is a minor bump rather than a patch. See the migration guide for step-by-step instructions, or the CHANGELOG for the full list.
Dates were wrong for users in Nepal. toNepaliDateTime() added an extra
day whenever the device's timezone was exactly UTC+5:45 and the date fell after
1986 — so it was wrong in Nepal and only in Nepal:
// 0.0.7, on a device in Nepal
DateTime(2024, 4, 13).toNepaliDateTime(); // BS 2081-01-02 ✗
// 0.1.0, on any device
DateTime(2024, 4, 13).toNepaliDateTime(); // BS 2081-01-01 ✓
Since NepaliDateTime.now() uses this path, today's-date highlighting was
wrong in every widget. If you compensated with your own -1 day adjustment,
remove it.
Also fixed: HorizontalNepaliCalendar ignored taps on phones, the date picker
could not select the 30th or 31st of a month, and both the calendar and the
picker overflowed on several common screen sizes. NepaliDateTime now has
value equality, so it works as a Map key — if you relied on identity
comparison, switch to identical(a, b).
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
nepali_calendar_plus: ^latest_version
Or install it from the command line:
flutter pub add nepali_calendar_plus
Usage #
Basic Calendar #
import 'package:nepali_calendar_plus/nepali_calendar_plus.dart';
NepaliCalendar(
calendarStyle: NepaliCalendarStyle(
config: CalendarConfig(
showEnglishDate: true,
language: Language.nepali,
),
),
onDayChanged: (date) {
print('Selected: $date');
},
)
Horizontal Calendar #
HorizontalNepaliCalendar(
initialDate: NepaliDateTime.now(),
calendarStyle: NepaliCalendarStyle(
config: CalendarConfig(
language: Language.english,
),
),
onDateSelected: (date) {
print('Selected: $date');
},
)
With Controller #
final controller = NepaliCalendarController();
NepaliCalendar(
controller: controller,
calendarStyle: NepaliCalendarStyle(
config: CalendarConfig(
showEnglishDate: true,
),
),
)
// Navigate programmatically
controller.jumpToToday();
controller.nextMonth();
controller.previousMonth();
controller.jumpToDate(NepaliDateTime(year: 2080, month: 1, day: 1));
Date Picker Dialog #
Show a modal date picker dialog for easy date selection:
Future<void> _selectDate(BuildContext context) async {
final selectedDate = await showNepaliDatePicker(
context: context,
initialDate: NepaliDateTime.now(),
calendarStyle: NepaliCalendarStyle(
config: CalendarConfig(
language: Language.nepali,
weekTitleType: TitleFormat.half,
),
cellsStyle: CellStyle(
selectedColor: Colors.blue,
todayColor: Colors.green,
),
),
);
if (selectedDate != null) {
print('Selected date: $selectedDate');
}
}
// Use in your widget
ElevatedButton(
onPressed: () => _selectDate(context),
child: Text('Pick Date'),
)
A coloured band at the top shows the selected date in BS and in AD. Below it,
‹ Month ▾ › ‹ Year ▾ › steps the grid by months or years; tap the month or
the year to pick it from a page of months or a list of years. Today sits at
the bottom left. On a phone in landscape the band moves beside the grid, so
the days stay easy to tap.
The dialog returns as soon as a date is tapped; Close, or tapping outside it,
returns null. Pass autoConfirm: false to keep the selection pending behind
Cancel / OK instead.
The weekday names above the grid are initials (आ सो मं) by default. Pass
weekdayFormat: TitleFormat.half (आइत सोम मंगल) or TitleFormat.full for
longer names; they shrink to fit their column rather than being cut off. The
same option exists on showNepaliDateRangePicker.
NepaliDatePicker is the same picker as a widget, for embedding in a page. It
waits for OK by default, so a tap never pops your page; pass onConfirm and
onCancel to receive the result without touching the Navigator.
Date Range Picker #
Pick a start and an end date. On a phone the picker opens full screen with the months in one vertical list; on a tablet or desktop it is a dialog with two months side by side. The first tap sets the start, the second the end, and a third starts over. Save is enabled once both ends are set.
final range = await showNepaliDateRangePicker(
context: context,
minDate: NepaliDateTime(year: 2080, month: 1, day: 1),
maxDate: NepaliDateTime(year: 2090, month: 12, day: 30),
maxDays: 30, // optional: the longest range, both ends counted
);
if (range != null) {
print('${range.start} – ${range.end}: ${range.days} days');
final ad = range.toDateTimeRange(); // the same range as a DateTimeRange
}
NepaliDateRangePicker is the widget on its own, for embedding in a page:
pass onConfirm and onCancel and it leaves the Navigator alone.
Customization #
NepaliCalendar(
calendarStyle: NepaliCalendarStyle(
config: CalendarConfig(
showEnglishDate: true,
showBorder: true,
language: Language.nepali,
weekendType: WeekendType.saturday,
weekStartType: WeekStartType.sunday,
weekTitleType: TitleFormat.half,
// Months are drawn at the height they need -- five or six week rows
// depending on the weekday they start on -- and the calendar resizes as
// you swipe between them. Set this to pad every month out to six rows
// instead, so the calendar keeps a constant height.
sixWeekMonthsEnforced: false,
),
cellsStyle: CellStyle(
todayColor: Colors.green,
selectedColor: Colors.blue,
weekDayColor: Colors.red,
),
headersStyle: HeaderStyle(
monthHeaderStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
)
Event Management #
class MyEvent {
final String title;
final String description;
MyEvent(this.title, this.description);
}
final events = [
CalendarEvent<MyEvent>(
date: NepaliDateTime(year: 2082, month: 9, day: 10),
isHoliday: true,
additionalInfo: MyEvent("Christmas", "Holiday"),
),
];
NepaliCalendar<MyEvent>(
eventList: events,
checkIsHoliday: (event) => event.isHoliday,
onDayChanged: (date) => print('Selected: $date'),
onMonthChanged: (date) => print('Month: ${date.month}'),
)
Custom Builders #
NepaliCalendar(
calendarBuilder: CalendarBuilder(
// Custom event widget
eventBuilder: (context, index, date, event) {
return Container(
padding: EdgeInsets.all(8),
child: Text(event.additionalInfo?.title ?? ''),
);
},
// Custom cell widget
cellBuilder: (data) {
return Container(
decoration: BoxDecoration(
color: data.isToday ? Colors.blue : null,
shape: BoxShape.circle,
),
child: Center(child: Text('${data.day}')),
);
},
// Custom header
headerBuilder: (date, controller) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(Icons.chevron_left),
onPressed: controller.previousMonth,
),
Text('${date.month}/${date.year}'),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed: controller.nextMonth,
),
],
);
},
),
)
Custom Picker Design #
Both pickers take a DatePickerBuilder to redesign any of their parts: the
day cells, the weekday names, the title band, the navigation row, the action
row, and the month and year tiles. Each builder gets a data object with the part's state and callbacks to
call, so the picker still handles dates, bounds, paging and confirming. Return
null -- or leave a builder unset -- to keep the default design.
final date = await showNepaliDatePicker(
context: context,
autoConfirm: false,
pickerBuilder: DatePickerBuilder(
// Round days; every other day keeps the default look.
dayBuilder: (day) {
if (!day.isSelected) return null;
return GestureDetector(
onTap: day.onTap,
child: CircleAvatar(child: Text(day.label)),
);
},
// Your own action row in place of Today and Cancel / OK.
footerBuilder: (footer) => Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(onPressed: footer.onToday, child: const Text('Today')),
FilledButton(onPressed: footer.onConfirm, child: const Text('Done')),
],
),
),
);
Pass the same builder to showNepaliDateRangePicker: its days also get a
rangePosition (start, middle, end, single or none) to draw the
range band from. A custom widget fills the same fixed slot as the default
one, so the grid never resizes or scrolls; the picker keeps the screen-reader
label around a custom day.
Example Project #
Check out the example folder for a complete working example with all features demonstrated.
API Reference #
For detailed API documentation, visit pub.flutter-io.cn documentation.
Key Components #
- NepaliCalendar - Main calendar widget with full month view
- HorizontalNepaliCalendar - Horizontal scrolling date picker
- showNepaliDatePicker - Modal date picker dialog
- showNepaliDateRangePicker - Date range picker: full screen on phones, a two-month dialog on wider screens
- NepaliDateTimeRange - A start and end date, both included
- NepaliCalendarController - Programmatic navigation control
- CalendarConfig - Centralized configuration
- CalendarBuilder - Custom component builders
- CalendarEvent - Event model with generic type support
Configuration Options #
- Language:
Language.nepali,Language.english - WeekendType:
saturday,sunday,saturdayAndSunday,fridayAndSaturday - WeekStartType:
sunday,monday - TitleFormat:
full,half
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
Contact #
- Issues: Report Issues
- Email: work.sabinghimire@gmail.com




