> {}
11 |
12 | declare module '*.svg' {
13 | const svgUrl: string;
14 | const svgComponent: SvgrComponent;
15 | export default svgUrl;
16 | export { svgComponent as ReactComponent }
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/datepicker.utils.ts:
--------------------------------------------------------------------------------
1 | import { startOfMonth, startOfWeek, isValid,
2 | endOfMonth, addDays, format as date_format, parse } from "date-fns";
3 | import {chunk, get, isString, isNaN} from 'lodash'
4 |
5 | import { DayListShape, DatePickerOutPut, MainDate,
6 | defaultConfigs } from "../interfaces/datepicker.interfaces";
7 |
8 |
9 | export const _type_safe_isValidDate = (time:any):time is Date => {
10 | return isValid(time)
11 | }
12 |
13 | export const _is_number = (num: number | undefined | null): num is number => {
14 | // check if num is number ( even 0 ); return false only if its undefined / null
15 | return !isNaN(Number(num))
16 | }
17 |
18 | export const formatDate = (date:Date | MainDate | string | undefined, format=defaultConfigs.format) => {
19 | if(_type_safe_isValidDate(date)) {
20 | return {day : date.getDate(), month : date.getMonth(), year : date.getFullYear()}
21 |
22 | } else if (isString(date)) {
23 | const ip_date = parse(date, format, new Date())
24 | if(!_type_safe_isValidDate(ip_date)) {
25 | console.log('Warning : You have passed invalid date in props !')
26 | const now = new Date()
27 | return {day : now.getDate(), month : now.getMonth(), year : now.getFullYear()}
28 | }
29 | return {day : ip_date.getDate(), month : ip_date.getMonth(), year : ip_date.getFullYear()}
30 | } else {
31 | // if(date.day < 0 || date.day > 31) {
32 | // throw `invalid date : ${date.day}`
33 | // }
34 | const now = defaultConfigs.date
35 | let ip_obj = {
36 | day : get(date , 'day'),
37 | month : get(date , 'month', now.getMonth() ),
38 | year : get(date , 'year', now.getFullYear() )
39 | }
40 | // validate if date is correct else reset day
41 | // if day is 31 feb or something else
42 | if(_is_number(ip_obj.day)) {
43 | const test_date = new Date(ip_obj.year, ip_obj.month, ip_obj.day)
44 | if(test_date.getMonth() !== ip_obj.month) {
45 | ip_obj.day = 1
46 | }
47 | }
48 | return ip_obj
49 | }
50 |
51 | }
52 |
53 | const _WEEK_MAPPER = {
54 | 0 : 'S',
55 | 1 : 'M',
56 | 2 : 'T',
57 | 3 : 'W',
58 | 4 : 'T',
59 | 5 : 'F',
60 | 6 : 'S'
61 | }
62 |
63 | /**
64 | * 0 based circular array with given length that has inc and desc func
65 | *
66 | * @param {Number} value current value that needs to increment
67 | * @param {Number} length Length of the entire circular array
68 | */
69 | export function incrementCircularData(value:number, length:number) {
70 | return (value + 1) % length
71 | }
72 |
73 | export const getWeekList = (weekStartsOn=defaultConfigs.weekStartsOn):string[] => {
74 | let res_week_list = []
75 | let curr_pointer = weekStartsOn
76 | for (let index = 0; index < 7; index++) {
77 | res_week_list.push(_WEEK_MAPPER[curr_pointer])
78 | curr_pointer = incrementCircularData(curr_pointer, 7)
79 | }
80 | return res_week_list
81 | }
82 |
83 | export const createRangeIndex = (day:number | undefined, month:number, year:number):number | undefined => {
84 | if(_is_number(day)) return (year*10000) + (month*100) + day;
85 | return undefined
86 | }
87 |
88 | export const parseRangeIndex = (rangeIndex:number):number[] => {
89 | const day = rangeIndex % 100
90 | const m_y_temp = Math.round(rangeIndex/100)
91 | const month = m_y_temp % 100
92 | const year = Math.round(m_y_temp / 100)
93 |
94 | return [ day, month, year]
95 | }
96 |
97 | export const getDayList = (
98 | day:number | undefined, month:number, year:number, weekStartsOn:any = defaultConfigs.weekStartsOn
99 | ) : DayListShape[][] => {
100 | const curr_date = !!(day) ? new Date(year, month, day) : new Date(year, month)
101 | // start month date
102 | const sm_date = startOfMonth(curr_date)
103 | const sw_date = startOfWeek(sm_date, {weekStartsOn})
104 | const em_date = endOfMonth(curr_date)
105 |
106 | const sm_day = sm_date.getDate()
107 | const em_day = em_date.getDate()
108 | // get month days in list
109 | let res_list = []
110 | for(let index = sm_day; index <= em_day; index++) {
111 | res_list.push({
112 | day : index,
113 | rangeIndex: createRangeIndex(index, month, year),
114 | curr_month : true,
115 | })
116 | }
117 | // get padding week days of previous month
118 | let start_delta = sm_date.getDay() - weekStartsOn
119 | // for week start day greater than current day
120 | if(start_delta < 0) start_delta = 7 + start_delta
121 |
122 | const sw_day = sw_date.getDate()
123 | const sw_month = sw_date.getMonth()
124 | const sw_year = sw_date.getFullYear()
125 | for(let index = start_delta - 1; index >= 0; index--) {
126 | const this_day = sw_day + index
127 | // add at the front
128 | res_list.unshift({
129 | day : this_day,
130 | rangeIndex: createRangeIndex(this_day, sw_month, sw_year),
131 | curr_month : false,
132 | })
133 | }
134 |
135 | // calculate last padding
136 | const chunked_res_list = chunk(res_list, 7)
137 | const last_week_ind = (chunked_res_list.length - 1)
138 | const end_delta = 7 - chunked_res_list[last_week_ind].length
139 |
140 | const next_month_first_day = addDays(em_date, 1)
141 | // get next month
142 | const next_month = next_month_first_day.getMonth()
143 | // year may change for next month
144 | const next_year = next_month_first_day.getFullYear()
145 | for(let index = 1; index <= end_delta; index++) {
146 | chunked_res_list[last_week_ind].push({
147 | day : index,
148 | rangeIndex: createRangeIndex(index, next_month, next_year),
149 | curr_month : false,
150 | })
151 | }
152 | // @ts-ignore; rangeIndex will not be null for any createRangeIndex here
153 | return chunked_res_list
154 | }
155 |
156 |
157 | export const generateDatePickerOutput = (
158 | day:number | undefined, month:number, year:number,
159 | format:string) : DatePickerOutPut => {
160 |
161 | let date;
162 | let formatted = ''
163 | if(!isNaN(Number(day))) {
164 | date = new Date(year, month, day)
165 | formatted = date_format(date, format)
166 |
167 | if(date.getDate() !== day) {
168 | // reset day as this month don't have that day
169 | day = 1
170 | date = new Date(year, month, day)
171 | }
172 | }
173 |
174 | return {date, formatted, day, month, year}
175 | }
176 |
177 | export const getInitialDateForInput = (
178 | date : Date | MainDate | string | undefined, format : string=defaultConfigs.format
179 | ):string => {
180 | if(!date) return ''
181 | const {day, month, year} = formatDate(date, format)
182 | return generateDatePickerOutput(day, month, year, format).formatted
183 | }
--------------------------------------------------------------------------------
/src/utils/datetimepicker.utils.ts:
--------------------------------------------------------------------------------
1 | import { format, parse } from "date-fns";
2 | import { isString } from "lodash";
3 |
4 | import { formatDate, _type_safe_isValidDate } from "./datepicker.utils";
5 | import { createInputTime, generateTimeOutput } from "./timepicker.utils";
6 |
7 | import {
8 | DateTimePickerProps,
9 | MainDateTimeObject,
10 | DateTimePickerOutPut,
11 | DateObject,
12 | defaultConfigs
13 | } from "../interfaces/datetimepicker.interfaces";
14 | import { defaultConfigs as timeDefaultConfig, OutputTime } from "../interfaces/timepicker.interfaces";
15 | import { DatePickerOutPut } from "../interfaces/datepicker.interfaces";
16 |
17 |
18 | export const getInputDate = (
19 | date_time_input: DateTimePickerProps["date"], dt_format=defaultConfigs.format
20 | ): MainDateTimeObject => {
21 | if(_type_safe_isValidDate(date_time_input)) {
22 | const time_str = format(date_time_input, timeDefaultConfig.format)
23 | const time_obj = createInputTime(time_str)
24 | return {
25 | day : date_time_input.getDate(),
26 | month : date_time_input.getMonth(),
27 | year : date_time_input.getFullYear(),
28 | ...time_obj
29 | }
30 | } else if (isString(date_time_input)) {
31 | const ip_date = parse(date_time_input, dt_format, new Date())
32 | const time_str = format(ip_date, timeDefaultConfig.format)
33 | const time_obj = createInputTime(time_str)
34 | return {
35 | day : ip_date.getDate(),
36 | month : ip_date.getMonth(),
37 | year : ip_date.getFullYear(),
38 | ...time_obj
39 | }
40 | }
41 |
42 | let date_output = formatDate(date_time_input)
43 | const time_output = createInputTime(date_time_input)
44 |
45 | return {...date_output, ...time_output}
46 | }
47 |
48 |
49 |
50 | export const generateOutPut = (
51 | curr_date:MainDateTimeObject, date_format:string,
52 | date? : DatePickerOutPut, time? : OutputTime,
53 | ):DateTimePickerOutPut => { // DateTimePickerOutPut
54 | let result, formatted = '';
55 |
56 | if(date) {
57 | // date object given; passed from calender component
58 | const new_time_json = generateTimeOutput({...curr_date}, timeDefaultConfig.format)
59 | const current_date_obj = new Date(
60 | date.year, date.month, date.day,
61 | new_time_json.time.hour24, new_time_json.time.minute
62 | )
63 | try {
64 | formatted = format(current_date_obj, date_format)
65 | } catch (error) {
66 | // pass; can not parse cause time maybe empty
67 | }
68 |
69 | result = {
70 | date : current_date_obj,
71 | day : date.day, month: date.month, year: date.year,
72 | ...new_time_json.time
73 | }
74 | } else if(time) {
75 | // time given; passed from clock component
76 | const current_date_obj = new Date(
77 | curr_date.year, curr_date.month, curr_date.day,
78 | time.time.hour24 || 0, time.time.minute || 0
79 | )
80 | try {
81 | formatted = format(current_date_obj, date_format)
82 | } catch (error) {
83 | // pass; can not parse cause time maybe empty
84 | }
85 |
86 | result = {
87 | day : curr_date.day, month: curr_date.month, year: curr_date.year,
88 | date : current_date_obj,
89 | ...time.time
90 | }
91 | } else {
92 | // only current date given
93 | const new_time_json = generateTimeOutput({...curr_date}, timeDefaultConfig.format)
94 | const current_date_obj = new Date(
95 | curr_date.year, curr_date.month, curr_date.day,
96 | new_time_json.time.hour24, new_time_json.time.minute
97 | )
98 | formatted = format(current_date_obj, date_format)
99 |
100 | result = {
101 | date : current_date_obj,
102 | day : curr_date.day, month: curr_date.month, year: curr_date.year,
103 | ...new_time_json.time
104 | }
105 | }
106 |
107 | return {
108 | date : result,
109 | formatted
110 | }
111 | }
112 |
113 |
114 | export const getInitialDateForInput = (
115 | date:Date | DateObject | string , format:string=defaultConfigs.format
116 | ): string => {
117 | if(!date) return ''
118 | const curr_date = getInputDate(date, format)
119 | return generateOutPut(curr_date, format).formatted
120 | }
--------------------------------------------------------------------------------
/src/utils/monthpicker.utils.ts:
--------------------------------------------------------------------------------
1 | import { get } from "lodash";
2 |
3 | import { _type_safe_isValidDate } from "./datepicker.utils";
4 | import { OutputShape } from "../interfaces/monthpicker.interfaces";
5 |
6 | const MONTH_SUMMERY = {
7 | 0 : {M: "1", Mo: "1st", MM: "01", MMM: "Jan", MMMM: "January", MMMMM: "J"},
8 | 1 : {M: "2", Mo: "2nd", MM: "02", MMM: "Feb", MMMM: "February", MMMMM: "F"},
9 | 2 : {M: "3", Mo: "3rd", MM: "03", MMM: "Mar", MMMM: "March", MMMMM: "M"},
10 | 3 : {M: "4", Mo: "4th", MM: "04", MMM: "Apr", MMMM: "April", MMMMM: "A"},
11 | 4 : {M: "5", Mo: "5th", MM: "05", MMM: "May", MMMM: "May", MMMMM: "M"},
12 | 5 : {M: "6", Mo: "6th", MM: "06", MMM: "Jun", MMMM: "June", MMMMM: "J"},
13 | 6 : {M: "7", Mo: "7th", MM: "07", MMM: "Jul", MMMM: "July", MMMMM: "J"},
14 | 7 : {M: "8", Mo: "8th", MM: "08", MMM: "Aug", MMMM: "August", MMMMM: "A"},
15 | 8 : {M: "9", Mo: "9th", MM: "09", MMM: "Sep", MMMM: "September", MMMMM: "S"},
16 | 9 : {M: "10", Mo: "10th", MM: "10", MMM: "Oct", MMMM: "October", MMMMM: "O"},
17 | 10 : {M: "11", Mo: "11th", MM: "11", MMM: "Nov", MMMM: "November", MMMMM: "N"},
18 | 11 : {M: "12", Mo: "12th", MM: "12", MMM: "Dec", MMMM: "December", MMMMM: "D"},
19 | }
20 |
21 | export const formatMonth = (month:number, str_format:string = 'MMM'):string => {
22 | return MONTH_SUMMERY[month][str_format]
23 | }
24 |
25 | export const getMonthAndYear = (time:OutputShape | Date): OutputShape => {
26 |
27 | if(_type_safe_isValidDate(time)) {
28 | // time is a date object
29 | return {month: time.getMonth(), year: time.getFullYear()}
30 | } else {
31 | // time is a month object
32 | const now = new Date()
33 | return {
34 | month : get(time , 'month', now.getMonth() ),
35 | year : get(time , 'year', now.getFullYear() )
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/utils/style.theme.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentTheme } from '../interfaces/style.interfaces'
2 |
3 |
4 | const LIGHT_THEME_COLORS:ComponentTheme = {
5 | primary_color : 'white',
6 | primary_font_color : 'rgba(0, 0, 0, 0.57)',
7 | light_font_color : 'rgba(0,0,0, 0.35)',
8 |
9 | secondary_color: '#efefef',
10 |
11 | primary_highlight_color: '#88b04b',
12 | secondary_highlight_color: '#00aab2',
13 | }
14 |
15 | const DARK_THEME_COLORS:ComponentTheme = {
16 | primary_color : '#36465D',
17 | primary_font_color : '#8897b9',
18 | light_font_color : '#8897b9',
19 |
20 | secondary_color: '#2b303b',
21 |
22 | primary_highlight_color: '#CDB274',
23 | secondary_highlight_color: 'rgba(165,201,213,.61)',
24 | }
25 |
26 | const COLORFUL_THEME_COLORS:ComponentTheme = {
27 | primary_color : 'ghostwhite',
28 | primary_font_color : 'SlateGray',
29 | light_font_color : 'grey',
30 |
31 | secondary_color: '#ffd180',
32 |
33 | primary_highlight_color: '#88b999',
34 | secondary_highlight_color: '#8897b9',
35 | }
36 |
37 | export const getThemeColors = (theme = 'light'):ComponentTheme => {
38 | switch (theme) {
39 | case 'light':
40 | return {...LIGHT_THEME_COLORS}
41 |
42 | case 'dark':
43 | return {...DARK_THEME_COLORS}
44 |
45 | case 'colorful':
46 | return {...COLORFUL_THEME_COLORS}
47 |
48 | default: // default theme light
49 | return {...LIGHT_THEME_COLORS};
50 | }
51 | }
--------------------------------------------------------------------------------
/src/utils/style.utils.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { assign } from 'lodash'
3 | import { getThemeColors } from './style.theme'
4 | // TypeScript imports
5 | import { ComponentTheme } from '../interfaces/style.interfaces'
6 | import {
7 | TimePickerInputProps, TimePickerProps
8 | } from '../interfaces/timepicker.interfaces'
9 | import { MonthPickerProps } from '../interfaces/monthpicker.interfaces'
10 | import {
11 | DatePickerInputProps, DatePickerProps
12 | } from '../interfaces/datepicker.interfaces'
13 | import {
14 | DateTimePickerInputProps,
15 | DateTimePickerProps
16 | } from '../interfaces/datetimepicker.interfaces'
17 | import {
18 | DateRangePickerInputProps, DateRangePickerProps,
19 | RangePickerInputProps, RangePickerProps
20 | } from '../interfaces/rangepicker.interfaces'
21 |
22 |
23 | type WrapperProps =
24 | TimePickerInputProps |
25 | TimePickerProps |
26 | MonthPickerProps |
27 | DatePickerProps |
28 | DatePickerInputProps |
29 | DateTimePickerProps |
30 | DateTimePickerInputProps |
31 | RangePickerProps |
32 | RangePickerInputProps |
33 | DateRangePickerProps |
34 | DateRangePickerInputProps
35 |
36 | /**
37 | * Wrapper component to all exported library components
38 | *
39 | * Merge theme colors with prop colors or default colors
40 | * and create colors object to be used by all component
41 | */
42 | const StyleWrapper = (
43 | WrappedComponent: React.ComponentType
44 | ):React.FC
=> ({
45 | colors, theme, ...otherProps
46 | }) => {
47 | // override theme colors provided by props
48 | let themeColors = getThemeColors(theme)
49 | themeColors = assign(themeColors, colors)
50 | // pass-on other props with new colors
51 | const mergeProps = {...otherProps, colors: themeColors}
52 |
53 | return
54 | }
55 |
56 | export default StyleWrapper
--------------------------------------------------------------------------------
/src/utils/timepicker.utils.ts:
--------------------------------------------------------------------------------
1 | import { isObject, isString, isUndefined, isNull, trim, upperCase } from "lodash";
2 | import { format } from "date-fns";
3 |
4 |
5 | import { TimePickerProps, MainTime, OutputTime } from "../interfaces/timepicker.interfaces";
6 |
7 |
8 | export const createInputTime = (input_time : TimePickerProps["time"]):MainTime => {
9 | // default values
10 | let res_hour, res_minute, res_meridiem = 'AM';
11 |
12 |
13 | if(isObject(input_time)) {
14 | const {hour, hour24, minute, meridiem} = input_time
15 | if( (isUndefined(hour) || isNull(hour)) &&
16 | (isUndefined(hour24) || isNull(hour24)) ){
17 | res_meridiem = meridiem ? meridiem : res_meridiem
18 | }
19 | // input has hour | hour24
20 | else if(isUndefined(hour)) {
21 | // 24 hour format given
22 | const modulo = Number(hour24) % 12
23 | res_hour = modulo === 0 ? 12 : modulo
24 | res_minute = Number(minute)
25 | res_meridiem = Number(hour24) >= 12 ? 'PM' : 'AM'
26 | }
27 | else {
28 | // 12 hour format given
29 | res_hour = Number(hour) === 0 ? 12 : Number(hour)
30 | res_minute = Number(minute)
31 | res_meridiem = meridiem ? meridiem : res_meridiem
32 | }
33 | } else if (isString(input_time)) {
34 | if(input_time.includes("M") || input_time.includes("m")) {
35 | // 12 hrs format
36 | const [hhmm, meridiem] = trim(input_time).split(" ")
37 | const [hour, minute] = trim(hhmm).split(":")
38 |
39 | res_hour = Number(hour) === 0 ? 12 : Number(hour)
40 | res_minute = Number(minute)
41 | if(!!meridiem) {
42 | // meridiem can be a.m. | AM | am | a; result must be AM / PM
43 | // remove all space and '.'
44 | res_meridiem = meridiem.replace(/[^a-zA-Z]/g, "")
45 | // all to capital , A | AM
46 | res_meridiem = upperCase(res_meridiem)
47 | // handle single A case
48 | res_meridiem = res_meridiem.includes("A") ? "AM" : "PM"
49 | }
50 | }
51 | else {
52 | // 24 hrs format
53 | const [hour24, minute] = trim(input_time).split(":")
54 | // handle incorrect string; where hour24 is undefined | null | NaN
55 | if(hour24) {
56 | const modulo = Number(hour24) % 12
57 | res_hour = modulo === 0 ? 12 : modulo
58 | res_minute = Number(minute)
59 | res_meridiem = Number(hour24) >= 12 ? 'PM' : 'AM'
60 | }
61 | }
62 | }
63 |
64 | return {
65 | hour : res_hour, minute : res_minute, meridiem : res_meridiem
66 | }
67 | }
68 |
69 | export const generateTimeOutput = (
70 | {hour, minute, meridiem} : MainTime,
71 | time_format:string
72 | ):OutputTime => {
73 |
74 | let hour24 = hour
75 | // create hour24
76 | if(!hour) {
77 | return {formatted: '', time: {hour, hour24, minute, meridiem}}
78 | }
79 | if(meridiem === 'PM') {
80 | // 12 PM is 12 hrs
81 | hour24 = (hour === 12) ? 12 : hour + 12
82 | }
83 | else {
84 | // 12 AM is 00 hrs
85 | if(hour === 12) hour24 = 0
86 | }
87 | let time = {hour, minute, meridiem, hour24}
88 |
89 | let formatted:string
90 | try {
91 | // create time formatted string
92 | let now = new Date()
93 | // @ts-ignore
94 | now.setHours(hour24)
95 | // @ts-ignore -- format bellow will raise err if this is wrong
96 | now.setMinutes(minute)
97 | formatted = format(now, time_format)
98 | } catch (error) {
99 | formatted = ''
100 | }
101 |
102 | return {
103 | formatted,
104 | time
105 | }
106 | }
--------------------------------------------------------------------------------
/src/utils/useOutsideAlerter.hook.ts:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 |
4 | export const useOutsideAlerter = (wrapperRef:React.RefObject, handlerShow: Function) => {
5 |
6 | useEffect(() => {
7 | /**
8 | * Alert if clicked on outside of element
9 | */
10 | const handleClickOutside = (event: any) => {
11 |
12 | if (wrapperRef.current) {
13 | // check click outside of picker, and picker open
14 | if(!wrapperRef.current.contains(event.target)) {
15 | handlerShow(false)
16 | return
17 | }
18 | }
19 | }
20 |
21 | // Bind the event listener
22 | document.addEventListener("mousedown", handleClickOutside);
23 |
24 | return () => {
25 | // Unbind the event listener on clean up
26 | document.removeEventListener("mousedown", handleClickOutside);
27 | };
28 | }, [wrapperRef]);
29 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "module": "esnext",
5 | "lib": ["dom", "esnext"],
6 | "moduleResolution": "node",
7 | "jsx": "react",
8 | "sourceMap": true,
9 | "declaration": true,
10 | "esModuleInterop": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "suppressImplicitAnyIndexErrors": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": ["src"],
21 | "exclude": ["node_modules", "dist", "example"]
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------