Skip to content

Commit 40cf3a2

Browse files
committed
[Feature] Initialize classNames props Feature
[Feature] `input` element a11y via `onChange`
1 parent f0bdfc6 commit 40cf3a2

File tree

7 files changed

+140
-35
lines changed

7 files changed

+140
-35
lines changed

pages/index.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export default function Playground() {
3131
const [startFrom, setStartFrom] = useState("2023-03-01");
3232
const [startWeekOn, setStartWeekOn] = useState("");
3333

34+
const handleChange = (value, e) => {
35+
setValue(value);
36+
console.log(e);
37+
};
3438
return (
3539
<div className="px-4 py-8">
3640
<Head>
@@ -47,7 +51,7 @@ export default function Playground() {
4751
<Datepicker
4852
value={value}
4953
primaryColor={primaryColor}
50-
onChange={setValue}
54+
onChange={handleChange}
5155
useRange={useRange}
5256
showFooter={showFooter}
5357
showShortcuts={showShortcuts}
@@ -72,6 +76,20 @@ export default function Playground() {
7276
toggleIcon={isEmpty => {
7377
return isEmpty ? "Select Date" : "Clear";
7478
}}
79+
// classNames={{
80+
// input: ({ disabled, readOnly, className }) => {
81+
// if (disabled) {
82+
// return "opacity-40";
83+
// }
84+
// return `className`;
85+
// },
86+
// toggleButton: () => {
87+
// return "bg-blue-300 ease-in-out";
88+
// },
89+
// footer: () => {
90+
// return `p-4 border-t border-gray-600 flex flex-row flex-wrap justify-end`;
91+
// }
92+
// }}
7593
/>
7694
</div>
7795

src/components/Calendar/index.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ const Calendar: React.FC<Props> = ({
5252
hideDatepicker,
5353
asSingle,
5454
i18n,
55-
startWeekOn
55+
startWeekOn,
56+
input
5657
} = useContext(DatepickerContext);
5758
loadLanguageModule(i18n);
5859

@@ -118,10 +119,14 @@ const Calendar: React.FC<Props> = ({
118119
let newEnd = null;
119120

120121
function chosePeriod(start: string, end: string) {
121-
changeDatepickerValue({
122-
startDate: start,
123-
endDate: end
124-
});
122+
const ipt = input?.current;
123+
changeDatepickerValue(
124+
{
125+
startDate: start,
126+
endDate: end
127+
},
128+
ipt
129+
);
125130
hideDatepicker();
126131
}
127132

@@ -186,7 +191,8 @@ const Calendar: React.FC<Props> = ({
186191
hideDatepicker,
187192
period.end,
188193
period.start,
189-
showFooter
194+
showFooter,
195+
input
190196
]
191197
);
192198

src/components/Datepicker.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { COLORS, DEFAULT_COLOR } from "../constants";
99
import DatepickerContext from "../contexts/DatepickerContext";
1010
import { formatDate, nextMonth, previousMonth } from "../helpers";
1111
import useOnClickOutside from "../hooks";
12-
import { Period, DateValueType, DateType, DateRangeType } from "../types";
12+
import { Period, DateValueType, DateType, DateRangeType, ClassNamesTypeProp } from "../types";
1313

1414
import { Arrow, VerticalDash } from "./utils";
1515

1616
interface Props {
1717
primaryColor?: string;
1818
value: DateValueType;
19-
onChange: (value: DateValueType) => void;
19+
onChange: (value: DateValueType, e?: HTMLInputElement | null | undefined) => void;
2020
useRange?: boolean;
2121
showFooter?: boolean;
2222
showShortcuts?: boolean;
@@ -39,6 +39,7 @@ interface Props {
3939
startFrom?: Date | null;
4040
i18n?: string;
4141
disabled?: boolean;
42+
classNames?: ClassNamesTypeProp | undefined;
4243
inputClassName?: string | null;
4344
toggleClassName?: string | null;
4445
toggleIcon?: ((open: boolean) => React.ReactNode) | undefined;
@@ -78,7 +79,8 @@ const Datepicker: React.FC<Props> = ({
7879
disabledDates = null,
7980
inputId,
8081
inputName,
81-
startWeekOn = "sun"
82+
startWeekOn = "sun",
83+
classNames = undefined
8284
}) => {
8385
// Ref
8486
const containerRef = useRef<HTMLDivElement>(null);
@@ -97,6 +99,8 @@ const Datepicker: React.FC<Props> = ({
9799
const [dayHover, setDayHover] = useState<string | null>(null);
98100
const [inputText, setInputText] = useState<string>("");
99101

102+
const [inputRef, setInputRef] = useState(React.createRef<HTMLInputElement>());
103+
100104
// Custom Hooks use
101105
useOnClickOutside(containerRef, () => {
102106
const container = containerRef.current;
@@ -296,7 +300,10 @@ const Datepicker: React.FC<Props> = ({
296300
disabledDates,
297301
inputId,
298302
inputName,
299-
startWeekOn
303+
startWeekOn,
304+
classNames,
305+
onChange,
306+
input: inputRef
300307
};
301308
}, [
302309
asSingle,
@@ -325,7 +332,9 @@ const Datepicker: React.FC<Props> = ({
325332
disabledDates,
326333
inputId,
327334
inputName,
328-
startWeekOn
335+
startWeekOn,
336+
classNames,
337+
inputRef
329338
]);
330339

331340
return (
@@ -334,7 +343,7 @@ const Datepicker: React.FC<Props> = ({
334343
className={`relative w-full text-gray-700 ${containerClassName}`}
335344
ref={containerRef}
336345
>
337-
<Input />
346+
<Input setContextRef={setInputRef} />
338347

339348
<div
340349
className="transition-all ease-out duration-300 absolute z-10 mt-[1px] text-sm lg:text-xs 2xl:text-sm translate-y-4 opacity-0 hidden"

src/components/Footer.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
import React, { useContext } from "react";
1+
import React, { useCallback, useContext } from "react";
22

33
import DatepickerContext from "../contexts/DatepickerContext";
44

55
import { PrimaryButton, SecondaryButton } from "./utils";
66

77
const Footer: React.FC = () => {
88
// Contexts
9-
const { hideDatepicker, period, changeDatepickerValue, configs } =
9+
const { hideDatepicker, period, changeDatepickerValue, configs, classNames } =
1010
useContext(DatepickerContext);
1111

12+
const getClassName = useCallback(() => {
13+
if (typeof classNames !== "undefined" && typeof classNames?.footer === "function") {
14+
return classNames.footer();
15+
}
16+
17+
return "flex items-center justify-end pb-2.5 pt-3 border-t border-gray-300 dark:border-gray-700";
18+
}, [classNames]);
19+
1220
return (
13-
<div className="flex items-center justify-end pb-2.5 pt-3 border-t border-gray-300 dark:border-gray-700">
21+
<div className={getClassName()}>
1422
<div className="w-full md:w-auto flex items-center justify-center space-x-3">
1523
<SecondaryButton
1624
onClick={() => {

src/components/Input.tsx

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { dateIsValid } from "../helpers";
77

88
import ToggleButton from "./ToggleButton";
99

10-
const Input: React.FC = () => {
10+
type Props = {
11+
setContextRef?: (ref: React.RefObject<HTMLInputElement>) => void;
12+
};
13+
14+
const Input: React.FC<Props> = (e: Props) => {
1115
// Context
1216
const {
1317
primaryColor,
@@ -30,21 +34,34 @@ const Input: React.FC = () => {
3034
readOnly,
3135
displayFormat,
3236
inputId,
33-
inputName
37+
inputName,
38+
classNames
3439
} = useContext(DatepickerContext);
3540

3641
// UseRefs
3742
const buttonRef = useRef<HTMLButtonElement>(null);
3843
const inputRef = useRef<HTMLInputElement>(null);
3944

45+
useEffect(() => {
46+
if (inputRef && e.setContextRef && typeof e.setContextRef === "function") {
47+
e.setContextRef(inputRef);
48+
}
49+
}, [e, inputRef]);
50+
4051
// Functions
4152
const getClassName = useCallback(() => {
53+
const input = inputRef.current;
54+
55+
if (input && typeof classNames != "undefined" && typeof classNames.input === "function") {
56+
return classNames?.input(input);
57+
}
58+
4259
const border = BORDER_COLOR.focus[primaryColor as keyof typeof BORDER_COLOR.focus];
4360
const ring =
4461
RING_COLOR["second-focus"][primaryColor as keyof (typeof RING_COLOR)["second-focus"]];
4562
const classNameOverload = typeof inputClassName === "string" ? inputClassName : "";
4663
return `relative transition-all duration-300 py-2.5 pl-4 pr-14 w-full border-gray-300 dark:bg-slate-800 dark:text-white/80 dark:border-slate-600 rounded-lg tracking-wide font-light text-sm placeholder-gray-400 bg-white focus:ring disabled:opacity-40 disabled:cursor-not-allowed ${border} ${ring} ${classNameOverload}`;
47-
}, [primaryColor, inputClassName]);
64+
}, [inputRef, classNames, primaryColor, inputClassName]);
4865

4966
const handleInputChange = useCallback(
5067
(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -66,10 +83,13 @@ const Input: React.FC = () => {
6683
dateIsValid(new Date(end)) &&
6784
dayjs(start).isBefore(end)
6885
) {
69-
changeDatepickerValue({
70-
startDate: start,
71-
endDate: end
72-
});
86+
changeDatepickerValue(
87+
{
88+
startDate: start,
89+
endDate: end
90+
},
91+
e.target
92+
);
7393
changeDayHover(dayjs(end).add(-1, "day").format("YYYY-MM-DD"));
7494
hideDatepicker();
7595
if (input) {
@@ -87,18 +107,23 @@ const Input: React.FC = () => {
87107

88108
function focusInput(e: Event) {
89109
e.stopPropagation();
90-
if (inputRef?.current) {
91-
inputRef.current.focus();
110+
const input = inputRef.current;
111+
112+
if (input) {
113+
input.focus();
92114
if (inputText && !readOnly) {
93115
changeInputText("");
94116
if (dayHover) {
95117
changeDayHover(null);
96118
}
97119
if (period.start && period.end) {
98-
changeDatepickerValue({
99-
startDate: null,
100-
endDate: null
101-
});
120+
changeDatepickerValue(
121+
{
122+
startDate: null,
123+
endDate: null
124+
},
125+
input
126+
);
102127
}
103128
}
104129
}
@@ -121,7 +146,8 @@ const Input: React.FC = () => {
121146
inputText,
122147
period.end,
123148
period.start,
124-
readOnly
149+
readOnly,
150+
inputRef
125151
]);
126152

127153
useEffect(() => {
@@ -178,6 +204,20 @@ const Input: React.FC = () => {
178204
[toggleIcon]
179205
);
180206

207+
const getToggleClassName = useCallback(() => {
208+
const button = buttonRef.current;
209+
210+
if (
211+
button &&
212+
typeof classNames !== "undefined" &&
213+
typeof classNames.toggleButton === "function"
214+
) {
215+
return classNames.toggleButton(button);
216+
}
217+
218+
return `absolute right-0 h-full px-3 text-gray-400 focus:outline-none disabled:opacity-40 disabled:cursor-not-allowed ${toggleClassName}`;
219+
}, [toggleClassName, buttonRef, classNames]);
220+
181221
return (
182222
<>
183223
<input
@@ -203,7 +243,7 @@ const Input: React.FC = () => {
203243
type="button"
204244
ref={buttonRef}
205245
disabled={disabled}
206-
className={`absolute right-0 h-full px-3 text-gray-400 focus:outline-none disabled:opacity-40 disabled:cursor-not-allowed ${toggleClassName}`}
246+
className={getToggleClassName()}
207247
>
208248
{renderToggleIcon(inputText == null || (inputText != null && !inputText.length))}
209249
</button>

src/contexts/DatepickerContext.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import dayjs from "dayjs";
22
import React, { createContext } from "react";
33

4-
import { Configs, Period, DateValueType, DateType, DateRangeType } from "../types";
4+
import {
5+
Configs,
6+
Period,
7+
DateValueType,
8+
DateType,
9+
DateRangeType,
10+
ClassNamesTypeProp
11+
} from "../types";
512

613
interface DatepickerStore {
14+
input?: React.RefObject<HTMLInputElement>;
715
asSingle?: boolean;
816
primaryColor: string;
917
configs?: Configs | null;
@@ -17,7 +25,7 @@ interface DatepickerStore {
1725
inputText: string;
1826
changeInputText: (text: string) => void;
1927
updateFirstDate: (date: dayjs.Dayjs) => void;
20-
changeDatepickerValue: (value: DateValueType) => void;
28+
changeDatepickerValue: (value: DateValueType, e?: HTMLInputElement | null | undefined) => void;
2129
showFooter?: boolean;
2230
placeholder?: string | null;
2331
separator?: string;
@@ -36,9 +44,11 @@ interface DatepickerStore {
3644
disabledDates?: DateRangeType[] | null;
3745
inputId?: string;
3846
inputName?: string;
47+
classNames?: ClassNamesTypeProp | undefined;
3948
}
4049

4150
const DatepickerContext = createContext<DatepickerStore>({
51+
input: undefined,
4252
primaryColor: "blue",
4353
calendarContainer: null,
4454
arrowContainer: null,
@@ -56,7 +66,13 @@ const DatepickerContext = createContext<DatepickerStore>({
5666
// eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars
5767
updateFirstDate: date => {},
5868
// eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars
59-
changeDatepickerValue: value => {},
69+
changeDatepickerValue: (
70+
// eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars
71+
value: DateValueType,
72+
// eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars
73+
e: HTMLInputElement | null | undefined
74+
// eslint-disable-next-line @typescript-eslint/no-empty-function,@typescript-eslint/no-unused-vars
75+
) => {},
6076
showFooter: false,
6177
value: null,
6278
i18n: "en",
@@ -72,7 +88,8 @@ const DatepickerContext = createContext<DatepickerStore>({
7288
inputId: undefined,
7389
inputName: undefined,
7490
startWeekOn: "sun",
75-
toggleIcon: undefined
91+
toggleIcon: undefined,
92+
classNames: undefined
7693
});
7794

7895
export default DatepickerContext;

src/types/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,10 @@ export type DateRangeType = {
3434
};
3535

3636
export type DateValueType = DateRangeType | null;
37+
38+
export type ClassNamesTypeProp = {
39+
container: (p?: object | null | undefined) => string | undefined;
40+
input: (p?: object | null | undefined) => string | undefined;
41+
toggleButton: (p?: object | null | undefined) => string | undefined;
42+
footer: (p?: object | null | undefined) => string | undefined;
43+
};

0 commit comments

Comments
 (0)