Skip to content

Commit

Permalink
chore: write readme
Browse files Browse the repository at this point in the history
  • Loading branch information
arjendevos committed Jan 16, 2024
1 parent abaa667 commit c5722a1
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 154 deletions.
48 changes: 48 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Contributing to React Datepicker

Thank you for considering contributing to React Datepicker! All community contributions are welcome, no matter how small.

This guide outlines the process for contributing to this project. Please make sure to go through the documentation before making your contribution.

> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation:
> - Star the project
> - Tweet about it
> - Submit bug problems (so we can make it even better)
## Getting Started
You can either fix an existing issue or add something new. Before you add something new, always make sure to create an issue for it first. This way we can discuss if it's a good fit for the project and if it's something that we would merge.

## Contributing Guidelines

1. **Fork the Repository:** Click on the 'Fork' button in the upper right corner of the repository's GitHub page. This will create a copy of the repository in your GitHub account.
2. **Clone the Repository:** Clone your forked repository to your local machine using `git clone`.
```shell
git clone https://github.com/yourusername/react-datepicker.git
cd react-datepicker
```

3. **Make Changes:** Make your desired changes and ensure that your code adheres to the coding standards.
4. **Test Locally:** Test your changes locally to ensure they work as expected.
5. **Commit Changes:** Commit your changes with a clear and concise commit message.

```shell
git commit -m "<convential commit>: Add your detailed description here"
```
6. **Push Changes:** Push your changes to your forked repository.

```shell
git push origin branch-name
```

7. **Create a Pull Request:** Go to the original repository and create a pull request. Please provide a detailed description of your changes.
8. **Code Review:** Your pull request will undergo a code review. Note that you might need to make any necessary adjustments based on feedback.
9. **Merge:** Once approved, maintainers will merge your pull request into the main repository.


## Reporting Issues

If you encounter any issues or have suggestions for improvements, please feel free to (create an issue on React Datepicker's GitHub repository)[https://github.com/expanse-agency/react-datepicker/issues/new]. When reporting issues, please provide as much detail as possible to help in understanding and addressing the problem effectively.

---

Thank you for considering contributing to React Datepicker. Your contributions help make this project better for everyone.
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,61 @@
TODO
# React Datepicker

Modular date picker component for React using the native date class. Supports single, multiple and range.

## Features
- ✅ Select single date
- ✅ Select multiple dates
- ✅ Select date range
- ✅ Use shortcuts to select a date range
- ✅ TypeScript support
- ✅ Locale support
- ✅ Modular

### Todo
- [ ] Localization(i18n)
- [ ] Disable specific dates
- [ ] Minimum Date and Maximum Date
- [ ] Bring your own styles
- [ ] Custom date format

## Installation

### Install via pnpm

```
pnpm install @expanselabs/react-datepicker
```

### Install via npm

```
npm install @expanselabs/react-datepicker
```

### Install via yarn

```
yarn add @expanselabs/react-datepicker
```

## Current Version

![Different pick modes](https://raw.githubusercontent.com/expanse-agency/react-datepicker/main/assets/react-datepicker-modes.jpg?raw=true)


## Playground
Clone the repository and run the following commands:

### Using pnpm
pnpm i && pnpm dev

### Using npm
npm i && npm dev

### Using yarn
yarn install && yarn dev

The playground runs on `http://localhost:3000`

## Contributing
See CONTRIBUTING.md
12 changes: 10 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ import { DatepickerValue } from '@types';

export default function Page() {
const [date, setDate] = React.useState<DatepickerValue>();
const [multipleDate, setMultipleDate] = React.useState<DatepickerValue>();
const [dateRange, setDateRange] = React.useState<DatepickerValue>();

return (
<div className="m-48">
<Datepicker type="range" value={date} onChange={setDate} expand shortcuts footer />
<div className="m-12 flex flex-wrap gap-2">
<Datepicker type="single" value={date} onChange={setDate} dir="rtl" />
<Datepicker type="single" value={date} onChange={setDate} dir="rtl" locale="ar-SA" />
<Datepicker type="multiple" value={multipleDate} onChange={setMultipleDate} />
<Datepicker type="range" value={dateRange} onChange={setDateRange} />
<Datepicker type="range" value={dateRange} onChange={setDateRange} expand dir="rtl" />
<Datepicker type="range" value={dateRange} onChange={setDateRange} expand shortcuts />
<Datepicker type="range" value={dateRange} onChange={setDateRange} expand shortcuts footer />
</div>
);
}
Binary file added assets/react-datepicker-modes.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions src/components/Datepicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ interface Props extends DatepickerConfig {
onChange: (date: DatepickerValue) => void;
}

export default function Datepicker({ value, onChange, ...config }: Props) {
export default function Datepicker({ value, onChange, ...rest }: Props) {
const [month, setMonth] = React.useState({
month: new Date().getMonth(),
year: new Date().getFullYear()
});

const config = {
...rest,
dir: rest.dir || 'ltr',
type: rest.type || 'single'
};

const { label, hasValue } = displayDateValue(value, config);

return (
Expand All @@ -46,7 +52,7 @@ export default function Datepicker({ value, onChange, ...config }: Props) {
</Popover.Trigger>

<Popover.Portal>
<Popover.Content align="start" sideOffset={8} className="mx-2">
<Popover.Content align="start" sideOffset={8} className="">
{config.expand ? (
<DatepickerExpanded config={config} value={value} onChange={onChange} />
) : (
Expand Down
11 changes: 10 additions & 1 deletion src/components/DatepickerCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ interface Props extends VariantProps<typeof variants> {

export default function DatepickerCalendar({ config, value, onChange, month, onChangeMonth, ...v }: Props) {
const toPreviousMonth = () => {
if (config.dir === 'rtl') {
onChangeMonth(getNextMonthAndYear(month));
return;
}
onChangeMonth(getPreviousMonthAndYear(month));
};

const toNextMonth = () => {
if (config.dir === 'rtl') {
onChangeMonth(getPreviousMonthAndYear(month));
return;
}

onChangeMonth(getNextMonthAndYear(month));
};

return (
<div className="inline-flex">
<div className="inline-flex bg-white">
<div className={twMerge(variants(v))}>
<DatepickerCalendarMonth config={config} month={month} toNextMonth={toNextMonth} toPreviousMonth={toPreviousMonth} />
<DatepickerCalendarDates config={config} month={month} value={value} onChange={onChange} />
Expand Down
101 changes: 64 additions & 37 deletions src/components/DatepickerCalendarDates.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React from 'react';

import { DatepickerCalendarDate, DatepickerConfig, DatepickerMonth, DatepickerValue, DateRange } from '@types';
import { dateIsEq, dateIsLess, dateIsLessOrEq, dateIsMore, dateIsMoreOrEq, dateIsToday, days, generate42CalendarDates } from '@utils';
import { DatepickerConfig, DatepickerMonth, DatepickerValue, DateRange } from '@types';
import {
dateIsEq,
dateIsLess,
dateIsLessOrEq,
dateIsMore,
dateIsMoreOrEq,
dateIsToday,
dateValueType,
generate42CalendarDates
} from '@utils';

import DatepickerCalendarDatesItem from './DatepickerCalendarDatesItem';

Expand All @@ -13,56 +22,73 @@ interface Props {
}

export default function DatepickerCalendarDates({ config, month, value, onChange }: Props) {
const generatedDays = React.useMemo(() => generate42CalendarDates(month, config), [month, config]);
const generatedDates = React.useMemo(() => generate42CalendarDates(month, config), [month, config]);

const weekDays = React.useMemo(() => {
const today = new Date();
const days: {
short: string;
narrow: string;
long: string;
}[] = [];
for (let i = 0; i < 7; i++) {
const d = new Date(today.getFullYear(), today.getMonth(), i + (config.weeksStartOnMonday ? 1 : 0));
days.push({
short: d.toLocaleDateString(config.locale, { weekday: 'short' }),
narrow: d.toLocaleDateString(config.locale, { weekday: 'narrow' }),
long: d.toLocaleDateString(config.locale, { weekday: 'long' })
});
}

const d = React.useMemo(() => {
const d = [...days];
if (config.weeksStartOnMonday) {
const fd = d.shift();
if (fd) d.push(fd);
if (config.dir === 'rtl') {
days.reverse();
}
return d;
}, [config.weeksStartOnMonday]);
return days;
}, [config.locale, config.weeksStartOnMonday]);

const isSelected = React.useCallback(
(day: DatepickerCalendarDate) => {
if (config.type === 'single') {
return dateIsEq(day.date, value as Date);
(date: Date) => {
if (!value) return false;
const type = dateValueType(value, config);

if (type === 'single') {
return dateIsEq(date, value as Date);
}

if (config.type === 'multiple') {
return !!(value as Date[]).find((sd) => dateIsEq(sd, day.date));
if (type === 'multiple') {
return !!(value as Date[]).find((sd) => dateIsEq(sd, date));
}

if (config.type === 'range') {
if (type === 'range') {
const startDate = (value as DateRange)?.startDate;
const endDate = (value as DateRange)?.endDate;

if (startDate && endDate) {
return dateIsEq(day.date, startDate) || dateIsEq(day.date, endDate);
return dateIsEq(date, startDate) || dateIsEq(date, endDate);
}

if (startDate) {
return dateIsEq(day.date, startDate);
return dateIsEq(date, startDate);
}

if (endDate) {
return dateIsEq(day.date, endDate);
return dateIsEq(date, endDate);
}
}
return false;
},
[value, config.type]
[value, config]
);

const onClick = React.useCallback(
(d: Date) => {
if (config.type === 'single') {
const type = dateValueType(value, config);
if (type === 'single') {
return onChange(d);
}

if (config.type === 'multiple') {
const dates = value as Date[];
if (type === 'multiple') {
const dates = (value as Date[]) || [];
const f = dates.find((sd) => dateIsEq(sd, d));
if (f) {
return onChange(dates.filter((sd) => !dateIsEq(sd, d)));
Expand All @@ -71,7 +97,7 @@ export default function DatepickerCalendarDates({ config, month, value, onChange
return onChange([...dates, d]);
}

if (config.type === 'range') {
if (type === 'range') {
const startDate = (value as DateRange)?.startDate;
const endDate = (value as DateRange)?.endDate;

Expand Down Expand Up @@ -102,7 +128,7 @@ export default function DatepickerCalendarDates({ config, month, value, onChange
return onChange({ startDate: d, endDate: null });
}
},
[config.type, onChange, value]
[config, onChange, value]
);

const isInRange = React.useCallback(
Expand Down Expand Up @@ -148,27 +174,28 @@ export default function DatepickerCalendarDates({ config, month, value, onChange
<table className="border-collapse" role="grid">
<thead>
<tr className="mb-1 flex">
{d.map((day) => (
<th key={day.short} scope="col" className="w-8 text-xs font-medium text-gray-700">
{weekDays.map((day) => (
<th key={day.long} scope="col" className="w-8 text-xs font-medium text-gray-700">
{day.short}
</th>
))}
</tr>
</thead>
<tbody role="rowgroup">
{generatedDays.map((week) => (
<tr key={week[0].day} className="mt-1 flex">
{generatedDates.map((week) => (
<tr key={week[0].toString()} className="mt-1 flex">
{week.map((day) => (
<DatepickerCalendarDatesItem
key={day.date.toString()}
label={`${day.day}`}
focussed={day.isInSelectedMonth}
onClick={() => onClick(day.date)}
key={day.toString()}
label={day.getDate().toLocaleString(config.locale)}
focussed={day.getMonth() === month.month}
onClick={() => onClick(day)}
selected={isSelected(day)}
withDot={dateIsToday(day.date)}
isInRange={isInRange(day.date)}
isEndRange={isEndRange(day.date)}
isSameDay={isSameDay(day.date)}
withDot={dateIsToday(day)}
isInRange={isInRange(day)}
isEndRange={isEndRange(day)}
isSameDay={isSameDay(day)}
dir={config.dir}
/>
))}
</tr>
Expand Down
Loading

0 comments on commit c5722a1

Please sign in to comment.