GitHub - geeofree/kalendaryo: Build flexible react date components + fns
source link: https://github.com/geeofree/kalendaryo
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
README.md
kalendaryo
Build flexible react date components using primitives ⚛️ + ?fns
Problem
You want a date component that's:
✔️ Laid out the way you want
✔️ Functions the way you want
✔️ Flexible for your use case
This solution
Kalendaryo is a React component that provides the toolsets for you to build calendar components that works for your use cases. It has no layout or functionalities other than the ones you can think of to build through its API.
The component uses the render props pattern which helps in exposing various variables for you to use and also gives you the flexibility to build your calendar's layout anyway you want due to its inline rendering nature.
See the Basic Usage section to see how you can build a basic calendar component using Kalendaryo or see the Examples section to see more examples built with Kalendaryo.
Table of Contents
Installation
This package expects you to have >= [email protected]
, >= prop-types@15
, and [email protected]
Once you're done installing these peer dependencies, install the package like so:
npm i -d kalendaryo // <-- for npm peeps yarn add kalendaryo // <-- for yarn peeps
After doing all of those, hopefully everything should work and be ready for use ?
Basic Usage
// Step 1: Import the component import Kalendaryo from 'kalendaryo' // Step 2: Invoke and pass your desired calendar as a function in the render prop const BasicCalendar = () => <Kalendaryo render={MyCalendar} /> // Step 3: Build your calendar! function MyCalendar(kalendaryo) { const { getFormattedDate, getWeeksInMonth, getDatePrevMonth, getDateNextMonth, setSelectedDate, setDate } = kalendaryo const currentDate = getFormattedDate("MMMM YYYY") const weeksInCurrentMonth = getWeeksInMonth() const setDateNextMonth = () => setDate(getDateNextMonth()) const setDatePrevMonth = () => setDate(getDatePrevMonth()) const selectDay = date => () => setSelectedDate(date) /* For this basic example we're going to build a calendar that has: * 1. A header where you have: * 1.1 Controls for moving to the previous/next month of the current date * 1.2 A label for current month & year of the current date * 2. A body where you have: * 2.1 A row for the label of the days of a week * 2.2 Rows containing the days of each week in the current date's month where you can: * 2.2.1 Select a date by clicking on a day */ return ( <div className="my-calendar"> // (1) <div className="my-calendar-header"> // (1.1) <button onClick={setDatePrevMonth}>←</button> // (1.2) <span className="text-white">{currentDate}</span> // (1.1) <button onClick={setDateNextMonth}>→</button> </div> // (2) <div className="my-calendar-body"> // (2.1) <div className="week day-labels"> <div className="day">Sun</div> <div className="day">Mon</div> <div className="day">Tue</div> <div className="day">Wed</div> <div className="day">Thu</div> <div className="day">Fri</div> <div className="day">Sat</div> </div> // (2.2) {weeksInCurrentMonth.map((week, i) => ( <div className="week" key={i}> {week.map(day => ( <div key={day.label} // (2.2.1) onClick={selectDay(day.dateValue)} > {day.label} </div> ))} </div> ))} </div> </div> ) }
See this basic usage snippet in action here!
API
This section contains descriptions of the various things the <Kalendaryo />
component has to offer which are split into three parts:
state
: Description of the component's state that could changeprops
: Description of the component's props that you can change or hook intomethods
: Description of the component's various helper methods you can use from the render prop
State
#date
type: Date
Is the state for the current date the component is in. By convention, you should only change this when the calendar you're building changes it's current date, i.e. moving to and from a month or year on the calendar. Defaults to today's date if startCurrentDateAt prop is not set.
#selectedDate
type: Date
Is the state for the selected date on the component. By convention, you should only change this when the calendar you're building receives a date selection input from the user, i.e. selecting a day on the calendar. Defaults to today's date if startCurrentDateAt prop is not set.
Props
#startCurrentDateAt
type: Date required: false default: new Date()
Modifies the initial value of #date
& #selectedDate
states. Great for when you want your calendar to boot up in some date other than today.
const birthday = new Date(1988, 4, 27) <Kalendaryo startCurrentDateAt={birthday} />
#defaultFormat
type: String required: false default 'MM/DD/YY'
Modifies the default format value on the #getFormattedDate
method. Accepts any format that date-fns' format
function can support.
const myFormat = 'yyyy-mm-dd' <Kalendaryo defaultFormat={myFormat} />
#onChange
type: func(state: Object): void required: false
Callback for listening to state changes on the #date
& #selectedDate
states.
const logState = (state) => console.log(state) <Kalendaryo onChange={logState}/>
#onDateChange
type: func(date: Date): void required: false
Callback for listening to state changes only to the #date
state.
const logDateState = (date) => console.log(date) <Kalendaryo onDateChange={logDateState} />
#onSelectedChange
type: func(date: Date): void required: false
Callback for listening to state changes only to the #selectedDate
state.
const logSelectedDateState = (selectedDate) => console.log(selectedDate) <Kalendaryo onSelectedChange={logSelectedDateState} />
#render
type: func(kalendaryo: Object): void required: true
Callback for rendering your date component. This function receives an object which has <Kalendaryo />
's state
, methods
, as well as props you pass that are invalid(see passing variables to the render prop for more information).
const MyCalendar = (kalendaryo) => { console.log(kalendaryo) return <p>Some layout</p> } <Kalendaryo render={MyCalendar} />
Passing variables to the render prop
Sometimes you may need to have states other than the #date
and #selectedDate
state, i.e for a date range calendar component you may need to have a state for startDate
and endDate
and may need to create the calendar component as a method inside the date range calendar's class like so:
class DateRangeCalendar extends React.Component { state = { startDate: null, endDate: null } Calendar = () => { const { startDate, endDate } = this.state return // Your calendar layout } setDateRange = (selectedDate) => { // Logic for updating the start and end date states } render() { return <Kalendaryo onSelectedChange={this.setDateRange} render={this.Calendar} /> } }
This approach however, leaves the Calendar
render callback tightly coupled to the DateRangeCalendar
component and bloats it with an unnecessary method for rendering UI.
An approach I decided to go for and highly suggest is to pass any variables you want to pass through the Kalendaryo
component's prop, any invalid or unknown props that Kalendaryo
isn't concerned about gets passed to the #render
callback's object parameter, this makes the render
callback more pure and simple!
class DateRangeCalendar extends React.Component { state = { startDate: null, endDate: null } setDateRange = (selectedDate) => { // Logic for updating the start and end date states } render() { return ( <Kalendaryo startDate={this.state.startDate} endDate={this.state.endDate} onSelectedChange={this.setDateRange} render={Calendar} /> ) } } // Pure and simple! function Calendar(kalendaryo) { const { startDate, endDate } = kalendaryo return // Your calendar component }
You can also use this functionality to add helper functions inside the render
callback!
import { differenceInDays, isWithinRange } from 'date-fns' const dateIsInRange = (date, startDate, endDate) => { if (!isDate(data) || !isDate(startDate) || !isDate(endDate)) { throw new Error('Argument is not an instance of Date') } return differenceInDays(startDate, endDate) < 1 && isWithinRange(date, startDate, endDate) } function MyCalendar(kalendaryo) { const { dateIsInRange } = kalendaryo return // Your calendar layout } <Kalendaryo dateIsInRange={dateIsInRange} render={MyCalendar} />
Methods
#getFormattedDate
type: func(date?: Date | format?: String, format?: String): String
Returns the date formatted by the given format string. You can invoke this in four ways:
-
getFormattedDate()
- When no arguments are given, by default#getFormattedDate
returns the current value in the#date
state formatted as the value given in the#defaultFormat
prop -
getFormattedDate(date)
- When a date object is given in the first argument and the second argument is not given,#getFormattedDate
returns the given date object formatted as the value given in the#defaultFormat
prop -
getFormattedDate(formatString)
- When a string is given in the first argument and the second argument is not given,#getFormattedDate
returns the current value in the#date
state formatted as the value from the given string -
getFormattedDate(date, formatString)
- When the second argument is given, the first argument must be a date object, this will return the given date object formatted as the value from the given string value on the second argument
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const birthday = new Date(1988, 4, 27) const myFormattedDate = kalendaryo.getFormattedDate(birthday, 'yyyy-mm-dd') return <p>My birthday is at {myFormattedDate}</p> } <Kalendaryo render={MyCalendar} />
#getDateNextMonth
type: func(date?: Date | integer?: Integer, integer?: Integer): Date
Returns a date object with months added from some given integer. You can invoke this in four ways:
-
getDateNextMonth()
- When no arguments are given, by default#getDateNextMonth
will add 1 month to the value of the#date
state -
getDateNextMonth(date)
- When a date object is given in the first argument and the second argument is not given,#getDateNextMonth
will add 1 month by default to the given date object -
getDateNextMonth(integer)
- When an integer is given in the first argument and the second argument is not given,#getDateNextMonth
will add a month to the value of the#date
state by the specified integer value -
getDateNextMonth(date, integer)
- When the second argument is given, the first value must be a date object, this will return the given date object from the first argument that has a month added from the specified integer value on the second argument
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const nextMonth = kalendaryo.getDateNextMonth() const nextMonthFormatted = kalendaryo.getFormattedDate(nextMonth, 'MMMM') return <p>The next month from today is: {nextMonthFormatted}</p> } <Kalendaryo render={MyCalendar} />
#getDatePrevMonth
type: func(date?: Date | integer?: Integer, integer?: Integer): Date
Returns a date object with months subtracted from some given integer. You can invoke this in four ways:
-
getDatePrevMonth()
- When no arguments are given, by default#getDatePrevMonth
will subtract 1 month to the value of the#date
state -
getDatePrevMonth(date)
- When a date object is given in the first argument and the second argument is not given,#getDatePrevMonth
will subtract 1 month by default to the given date object -
getDatePrevMonth(integer)
- When an integer is given in the first argument and the second argument is not given,#getDatePrevMonth
will subtract a month to the value of the#date
state by the specified integer value -
getDatePrevMonth(date, integer)
- When the second argument is given, the first value must be a date object, this will return the given date object from the first argument that has a month subtracted from the specified integer value on the second argument
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const prevMonth = kalendaryo.getDatePrevMonth() const prevMonthFormatted = kalendaryo.getFormattedDate(prevMonth, 'MMMM') return <p>The previous month from today is: {prevMonthFormatted}</p> } <Kalendaryo render={MyCalendar} />
#getDaysInMonth
type: func(date?: Date): Array: { label: Integer, dateValue: Date }
Returns an array of day objects which are objects that have data for the label
of the day as well as the dateValue
for that day. You can invoke this in two ways:
-
getDaysInMonth()
- When no argument is given, by default#getDaysInMonth
returns an array of day objects from the#date
state's value -
getDaysInMonth(date)
- When a date object is given,#getDaysInMonth
returns an array of day objects for the given date
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const nextMonth = kalendaryo.getDateNextMonth() const daysNextMonth = kalendaryo.getDaysInMonth(nextMonth) return ( <div> {daysNextMonth.map((day) => ( <p key={day.label} onClick={() => console.log(day.dateValue)} > {day.label} </p> ))} </div> ) } <Kalendaryo render={MyCalendar} />
#getWeeksInMonth
type: func(date?: Date): WeekArray: DayArray: { label: Integer, dateValue: Date }
Returns an array of each weeks for the month of the given date, each array of weeks contain an array of days for that week. You can invoke this in two ways:
-
getWeeksInMonth()
- When no argument is given, by default#getWeeksInMonth
returns an array of weeks for the month of the#date
state's value -
getWeeksInMonth(date)
- When a date object is given,#getWeeksInMonth
returns an array of weeks for the month of the given date value
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const prevMonth = kalendaryo.getDateNextMonth() const weeksPrevMonth = kalendaryo.getWeeksInMonth(prevMonth) return ( <div> {weeksPrevMonth.map((week, i) => ( <div class="week" key={i}> {week.map((day) => ( <p key={day.label} onClick={() => console.log(day.dateValue)} > {day.label} </p> ))} </div> ))} </div> ) } <Kalendaryo render={MyCalendar} />
#setDate
type: func(date: Date): void
Updates the #date
state to the given date object
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const birthday = new Date(1988, 4, 27) const currentDate = kalendaryo.getFormattedDate() const setDateToBday = () => kalendaryo.setDate(birthday) return ( <div> <p>The date is: {currentDate}</p> <button onClick={setDateToBday}>Set date to my birthday</button> </div> ) } <Kalendaryo render={MyCalendar} />
#setSelectedDate
type: func(date: Date): void
Updates the #selectedDate
state to the given date object
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const birthday = new Date(1988, 4, 27) const currentDate = kalendaryo.getFormattedDate() const selectedDate = kalendaryo.getFormattedDate(kalendaryo.selectedDate) const selectBdayDate = () => kalendaryo.setSelectedDate(birthday) return ( <div> <p>The date is: {currentDate}</p> <p>The selected date is: {selectedDate}</p> <button onClick={selectBdayDate}>Set selected date to my birthday!</button> </div> ) } <Kalendaryo render={MyCalendar} />
#pickDate
type: func(date: Date): void
Updates both the #date
& #selectedDate
state to the given date object
NOTE: Throws an error if an invalid argument value is passed to the function
function MyCalendar(kalendaryo) { const birthday = new Date(1988, 4, 27) const currentDate = kalendaryo.getFormattedDate() const selectedDate = kalendaryo.getFormattedDate(kalendaryo.selectedDate) const selectBday = () => kalendaryo.pickDate(birthday) return ( <div> <p>The date is: {currentDate}</p> <p>The selected date is: {selectedDate}</p> <button onClick={selectBday}>Set date and selected date to my birthday!</button> </div> ) } <Kalendaryo render={MyCalendar} />
Examples
Inspiration
This project is heavily inspired from Downshift by Kent C. Dodds, a component library that uses render props to expose certain APIs for you to build flexible and accessible autocomplete, dropdown, combobox, etc. components.
Without it, I would not have been able to create this very first OSS project of mine, so thanks Mr. Dodds and Contributors for it! ❤️
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK