From 4f2944a9746f9be96149239d187f6c5f31f165db Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:31:56 +0700
Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=94=A7=20chore:=20install=20`date-fns?=
=?UTF-8?q?`=20and=20`react-calendar`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/package.json | 3 +-
yarn.lock | 62 ++++++++++++++++++++++++++++++----
2 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index a2a77ba1..49736684 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -13,13 +13,14 @@
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"assert": "^2.1.0",
- "date-fns": "^3.0.1",
+ "date-fns": "^3.3.1",
"downshift": "^8.2.3",
"eslint-config-react-app": "^7.0.1",
"gql-client": "^1.0.0",
"luxon": "^3.4.4",
"octokit": "^3.1.2",
"react": "^18.2.0",
+ "react-calendar": "^4.8.0",
"react-code-blocks": "^0.1.6",
"react-day-picker": "^8.9.1",
"react-dom": "^18.2.0",
diff --git a/yarn.lock b/yarn.lock
index e4713492..8845c973 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3910,6 +3910,18 @@
dependencies:
"@types/node" "*"
+"@types/lodash.memoize@^4.1.7":
+ version "4.1.9"
+ resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz#9f8912d39b6e450c0d342a2b74c99d331bf2016b"
+ integrity sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*":
+ version "4.14.202"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8"
+ integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==
+
"@types/long@^4.0.0", "@types/long@^4.0.1":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
@@ -4687,6 +4699,11 @@
"@webassemblyjs/ast" "1.11.6"
"@xtuc/long" "4.2.2"
+"@wojtekmaj/date-utils@^1.1.3":
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b"
+ integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==
+
"@wry/caches@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52"
@@ -6144,6 +6161,11 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
+clsx@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
+ integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
+
cmd-shim@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
@@ -6821,10 +6843,10 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
-date-fns@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.0.1.tgz#a95b3e8296e72cf57c99819f37679aa27ca65ec4"
- integrity sha512-cr9igCUa0QSqgAMj7JOrYTY6Nh1rmyGrFDko7ADqfmaQqP/I2N4rlfrLl7AWuzDaoIpz6MNjoEcTPzgZYIrhnA==
+date-fns@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed"
+ integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==
dateformat@^3.0.3:
version "3.0.3"
@@ -8720,6 +8742,14 @@ get-tsconfig@^4.7.0:
dependencies:
resolve-pkg-maps "^1.0.0"
+get-user-locale@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2"
+ integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==
+ dependencies:
+ "@types/lodash.memoize" "^4.1.7"
+ lodash.memoize "^4.1.1"
+
git-raw-commits@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-3.0.0.tgz#5432f053a9744f67e8db03dbc48add81252cfdeb"
@@ -11012,7 +11042,7 @@ lodash.isstring@^4.0.1:
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
-lodash.memoize@^4.1.2:
+lodash.memoize@^4.1.1, lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
@@ -11065,7 +11095,7 @@ long@^5.2.0:
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -13281,7 +13311,7 @@ promzard@^1.0.0:
dependencies:
read "^2.0.0"
-prop-types@15.8.1, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@15.8.1, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -13440,6 +13470,17 @@ react-app-polyfill@^3.0.0:
regenerator-runtime "^0.13.9"
whatwg-fetch "^3.6.2"
+react-calendar@^4.8.0:
+ version "4.8.0"
+ resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.8.0.tgz#61edbba6d17e7ef8a8012de9143b5e5ff41104c8"
+ integrity sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==
+ dependencies:
+ "@wojtekmaj/date-utils" "^1.1.3"
+ clsx "^2.0.0"
+ get-user-locale "^2.2.1"
+ prop-types "^15.6.0"
+ warning "^4.0.0"
+
react-code-blocks@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/react-code-blocks/-/react-code-blocks-0.1.6.tgz#ec64e7899223d3e910eb916465a66d95ce1ae1b2"
@@ -15880,6 +15921,13 @@ walker@^1.0.7:
dependencies:
makeerror "1.0.12"
+warning@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
From 0a5a1b8c5550c8bca0acaa8a47673e19c60f54e5 Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:32:35 +0700
Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20vscode=20sett?=
=?UTF-8?q?ings=20to=20enable=20tailwind=20variant=20intellisense?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.vscode/settings.json | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..f3e08235
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ // IntelliSense for taiwind variants
+ "tailwindCSS.experimental.classRegex": [
+ ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
+ ]
+}
From cc97ddff9d828592d38827a981173ffcec6ce83b Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:33:12 +0700
Subject: [PATCH 3/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20chevr?=
=?UTF-8?q?on=20left,=20right,=20and=20grabbed=20horizontal=20component?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CustomIcon/ChevronGrabberHorizontal.tsx | 20 ++++++++++++++++++
.../shared/CustomIcon/ChevronLeft.tsx | 21 +++++++++++++++++++
.../shared/CustomIcon/ChevronRight.tsx | 21 +++++++++++++++++++
.../src/components/shared/CustomIcon/index.ts | 3 +++
4 files changed, 65 insertions(+)
create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx
create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx
create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx
diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx
new file mode 100644
index 00000000..b62f79d0
--- /dev/null
+++ b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { CustomIcon, CustomIconProps } from './CustomIcon';
+
+export const ChevronGrabberHorizontal = (props: CustomIconProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx
new file mode 100644
index 00000000..f9c4885c
--- /dev/null
+++ b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { CustomIcon, CustomIconProps } from './CustomIcon';
+
+export const ChevronLeft = (props: CustomIconProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx
new file mode 100644
index 00000000..75da0052
--- /dev/null
+++ b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { CustomIcon, CustomIconProps } from './CustomIcon';
+
+export const ChevronRight = (props: CustomIconProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts
index d50d942c..e4b51032 100644
--- a/packages/frontend/src/components/shared/CustomIcon/index.ts
+++ b/packages/frontend/src/components/shared/CustomIcon/index.ts
@@ -1,2 +1,5 @@
export * from './PlusIcon';
export * from './CustomIcon';
+export * from './ChevronGrabberHorizontal';
+export * from './ChevronLeft';
+export * from './ChevronRight';
From ad7dd1920a3a0f74d3f2520a6a284b49da7a7b97 Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:33:26 +0700
Subject: [PATCH 4/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20calen?=
=?UTF-8?q?dar=20component?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/shared/Calendar/Calendar.css | 128 ++++++++
.../shared/Calendar/Calendar.theme.ts | 49 +++
.../components/shared/Calendar/Calendar.tsx | 292 ++++++++++++++++++
.../src/components/shared/Calendar/index.ts | 1 +
4 files changed, 470 insertions(+)
create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.css
create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.tsx
create mode 100644 packages/frontend/src/components/shared/Calendar/index.ts
diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.css b/packages/frontend/src/components/shared/Calendar/Calendar.css
new file mode 100644
index 00000000..85ce4182
--- /dev/null
+++ b/packages/frontend/src/components/shared/Calendar/Calendar.css
@@ -0,0 +1,128 @@
+/* React Calendar */
+.react-calendar {
+ @apply border-none font-sans;
+}
+
+/* Weekdays -- START */
+.react-calendar__month-view__weekdays {
+ @apply p-0 flex items-center justify-center;
+}
+
+.react-calendar__month-view__weekdays__weekday {
+ @apply h-8 w-12 flex items-center justify-center p-0 font-medium text-xs text-elements-disabled mb-2;
+}
+
+abbr[title] {
+ text-decoration: none;
+}
+/* Weekdays -- END */
+
+/* Days -- START */
+.react-calendar__month-view__days {
+ @apply p-0 gap-0;
+}
+
+.react-calendar__month-view__days__day--neighboringMonth {
+ @apply !text-elements-disabled;
+}
+
+.react-calendar__month-view__days__day--neighboringMonth:hover {
+ @apply !text-elements-disabled !bg-transparent;
+}
+
+.react-calendar__month-view__days__day--neighboringMonth {
+ @apply !text-elements-disabled !bg-transparent;
+}
+
+/* For weekend days */
+.react-calendar__month-view__days__day--weekend {
+ /* color: ${colors.grey[950]} !important; */
+}
+
+.react-calendar__tile {
+ @apply h-12 w-12 text-elements-high-em;
+}
+
+.react-calendar__tile:hover {
+ @apply bg-base-bg-emphasized rounded-lg;
+}
+
+.react-calendar__tile:focus-visible {
+ @apply bg-base-bg-emphasized rounded-lg focus-ring z-10;
+}
+
+.react-calendar__tile--now {
+ @apply bg-base-bg-emphasized text-elements-high-em rounded-lg;
+}
+
+.react-calendar__tile--now:hover {
+ @apply bg-base-bg-emphasized text-elements-high-em rounded-lg;
+}
+
+.react-calendar__tile--now:focus-visible {
+ @apply bg-base-bg-emphasized text-elements-high-em rounded-lg focus-ring;
+}
+
+.react-calendar__tile--active {
+ @apply bg-controls-primary text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--active:hover {
+ @apply bg-controls-primary-hovered;
+}
+
+.react-calendar__tile--active:focus-visible {
+ @apply bg-controls-primary-hovered focus-ring;
+}
+
+/* Range -- START */
+.react-calendar__tile--range {
+ @apply bg-controls-secondary text-elements-on-secondary rounded-none;
+}
+
+.react-calendar__tile--range:hover {
+ @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-none;
+}
+
+.react-calendar__tile--range:focus-visible {
+ @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-lg;
+}
+
+.react-calendar__tile--rangeStart {
+ @apply bg-controls-primary text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--rangeStart:hover {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--rangeStart:focus-visible {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring;
+}
+
+.react-calendar__tile--rangeEnd {
+ @apply bg-controls-primary text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--rangeEnd:hover {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--rangeEnd:focus-visible {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring;
+}
+/* Range -- END */
+/* Days -- END */
+
+/* Months -- START */
+.react-calendar__tile--hasActive {
+ @apply bg-controls-primary text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--hasActive:hover {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg;
+}
+
+.react-calendar__tile--hasActive:focus-visible {
+ @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring;
+}
diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
new file mode 100644
index 00000000..e39e5925
--- /dev/null
+++ b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
@@ -0,0 +1,49 @@
+import { VariantProps, tv } from 'tailwind-variants';
+
+export const calendarTheme = tv({
+ slots: {
+ wrapper: [
+ 'max-w-[352px]',
+ 'bg-surface-floating',
+ 'shadow-calendar',
+ 'rounded-xl',
+ ],
+ calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'],
+ navigation: [
+ 'flex',
+ 'items-center',
+ 'justify-between',
+ 'gap-3',
+ 'py-2.5',
+ 'px-1',
+ ],
+ dropdowns: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'],
+ dropdown: [
+ 'flex',
+ 'items-center',
+ 'gap-2',
+ 'px-2',
+ 'py-2',
+ 'rounded-lg',
+ 'border',
+ 'border-border-interactive',
+ 'text-elements-high-em',
+ 'shadow-field',
+ 'bg-white',
+ 'hover:bg-base-bg-alternate',
+ 'focus-visible:bg-base-bg-alternate',
+ ],
+ footer: [
+ 'flex',
+ 'items-center',
+ 'justify-end',
+ 'py-3',
+ 'px-2',
+ 'gap-3',
+ 'border-t',
+ 'border-border-separator',
+ ],
+ },
+});
+
+export type CalendarTheme = VariantProps;
diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.tsx b/packages/frontend/src/components/shared/Calendar/Calendar.tsx
new file mode 100644
index 00000000..737eee75
--- /dev/null
+++ b/packages/frontend/src/components/shared/Calendar/Calendar.tsx
@@ -0,0 +1,292 @@
+import React, {
+ ComponentPropsWithRef,
+ MouseEvent,
+ ReactNode,
+ useCallback,
+ useState,
+} from 'react';
+import {
+ Calendar as ReactCalendar,
+ CalendarProps as ReactCalendarProps,
+} from 'react-calendar';
+import { Value } from 'react-calendar/dist/cjs/shared/types';
+import { CalendarTheme, calendarTheme } from './Calendar.theme';
+import { Button } from 'components/shared/Button';
+import {
+ ChevronGrabberHorizontal,
+ ChevronLeft,
+ ChevronRight,
+} from 'components/shared/CustomIcon';
+
+import './Calendar.css';
+import { format } from 'date-fns';
+
+const CALENDAR_VIEW = ['month', 'year', 'decade', 'century'] as const;
+export type CalendarView = (typeof CALENDAR_VIEW)[number];
+
+/**
+ * Defines a custom set of props for a React calendar component by excluding specific props
+ * from the original ReactCalendarProps type.
+ * @type {CustomReactCalendarProps}
+ */
+type CustomReactCalendarProps = Omit<
+ ReactCalendarProps,
+ 'view' | 'showNavigation' | 'onClickMonth' | 'onClickYear'
+>;
+
+export interface CalendarProps extends CustomReactCalendarProps, CalendarTheme {
+ /**
+ * Optional props for wrapping a component with a div element.
+ */
+ wrapperProps?: ComponentPropsWithRef<'div'>;
+ /**
+ * Props for the calendar wrapper component.
+ */
+ calendarWrapperProps?: ComponentPropsWithRef<'div'>;
+ /**
+ * Optional props for the footer component.
+ */
+ footerProps?: ComponentPropsWithRef<'div'>;
+ /**
+ * Optional custom actions to be rendered.
+ */
+ actions?: ReactNode;
+ /**
+ * Optional callback function that is called when a value is selected.
+ * @param {Value} value - The selected value
+ * @returns None
+ */
+ onSelect?: (value: Value) => void;
+ /**
+ * Optional callback function that is called when a cancel action is triggered.
+ * @returns None
+ */
+ onCancel?: () => void;
+}
+
+/**
+ * Calendar component that allows users to select dates and navigate through months and years.
+ * @param {Object} CalendarProps - Props for the Calendar component.
+ * @returns {JSX.Element} A calendar component with navigation, date selection, and actions.
+ */
+export const Calendar = ({
+ selectRange,
+ activeStartDate: activeStartDateProp,
+ value: valueProp,
+ wrapperProps,
+ calendarWrapperProps,
+ footerProps,
+ actions,
+ onSelect,
+ onCancel,
+ onChange: onChangeProp,
+ ...props
+}: CalendarProps): JSX.Element => {
+ const { wrapper, calendar, navigation, dropdowns, dropdown, footer } =
+ calendarTheme();
+
+ const today = new Date();
+ const currentMonth = format(today, 'MMM');
+ const currentyear = format(today, 'yyyy');
+
+ const [view, setView] = useState('month');
+ const [activeDate, setActiveDate] = useState(
+ activeStartDateProp ?? today,
+ );
+ const [value, setValue] = useState(valueProp as Value);
+ const [month, setMonth] = useState(currentMonth);
+ const [year, setYear] = useState(currentyear);
+
+ /**
+ * Update the navigation label based on the active date
+ */
+ const changeNavigationLabel = useCallback(
+ (date: Date) => {
+ setMonth(format(date, 'MMM'));
+ setYear(format(date, 'yyyy'));
+ },
+ [setMonth, setYear],
+ );
+
+ /**
+ * Change the active date base on the action and range
+ */
+ const handleNavigate = useCallback(
+ (action: 'previous' | 'next', range: 'month' | 'year' | 'decade') => {
+ setActiveDate((date) => {
+ const newDate = new Date(date);
+ switch (range) {
+ case 'month':
+ newDate.setMonth(
+ action === 'previous' ? date.getMonth() - 1 : date.getMonth() + 1,
+ );
+ break;
+ case 'year':
+ newDate.setFullYear(
+ action === 'previous'
+ ? date.getFullYear() - 1
+ : date.getFullYear() + 1,
+ );
+ break;
+ case 'decade':
+ newDate.setFullYear(
+ action === 'previous'
+ ? date.getFullYear() - 10
+ : date.getFullYear() + 10,
+ );
+ break;
+ }
+ changeNavigationLabel(newDate);
+ return newDate;
+ });
+ },
+ [setActiveDate, changeNavigationLabel],
+ );
+
+ /**
+ * Change the view of the calendar
+ */
+ const handleChangeView = useCallback(
+ (view: CalendarView) => {
+ setView(view);
+ },
+ [setView],
+ );
+
+ /**
+ * Change the active date and set the view to the selected type
+ * and also update the navigation label
+ */
+ const handleChangeNavigation = useCallback(
+ (type: 'month' | 'year', date: Date) => {
+ setActiveDate(date);
+ changeNavigationLabel(date);
+ setView(type);
+ },
+ [setActiveDate, changeNavigationLabel, setView],
+ );
+
+ const handlePrevious = useCallback(() => {
+ switch (view) {
+ case 'month':
+ return handleNavigate('previous', 'month');
+ case 'year':
+ return handleNavigate('previous', 'year');
+ case 'decade':
+ return handleNavigate('previous', 'decade');
+ }
+ }, [view]);
+
+ const handleNext = useCallback(() => {
+ switch (view) {
+ case 'month':
+ return handleNavigate('next', 'month');
+ case 'year':
+ return handleNavigate('next', 'year');
+ case 'decade':
+ return handleNavigate('next', 'decade');
+ }
+ }, [view]);
+
+ const handleChange = useCallback(
+ (newValue: Value, event: MouseEvent) => {
+ setValue(newValue);
+
+ // Call the onChange prop if it exists
+ onChangeProp?.(newValue, event);
+
+ /**
+ * Update the active date and navigation label
+ *
+ * NOTE:
+ * For range selection, the active date is not updated
+ * The user only can select multiple dates within the same month
+ */
+ if (!selectRange) {
+ setActiveDate(newValue as Date);
+ changeNavigationLabel(newValue as Date);
+ }
+ },
+ [setValue, setActiveDate, changeNavigationLabel, selectRange],
+ );
+
+ return (
+
+ {/* Calendar wrapper */}
+
+ {/* Navigation */}
+
+
+
+
+ }
+ onClick={() => handleChangeView('year')}
+ >
+ {month}
+
+
+ }
+ onClick={() => handleChangeView('decade')}
+ >
+ {year}
+
+
+
+
+
+ {/* Calendar */}
+
handleChangeNavigation('month', date)}
+ onClickYear={(date) => handleChangeNavigation('year', date)}
+ />
+
+
+ {/* Footer or CTA */}
+
+ {actions ? (
+ actions
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/Calendar/index.ts b/packages/frontend/src/components/shared/Calendar/index.ts
new file mode 100644
index 00000000..a7233805
--- /dev/null
+++ b/packages/frontend/src/components/shared/Calendar/index.ts
@@ -0,0 +1 @@
+export * from './Calendar';
From 4f7f9cf9143ddd60a1ab4fd91852d50216153924 Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:33:59 +0700
Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20calendar=20com?=
=?UTF-8?q?ponent=20to=20the=20example=20page?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/src/pages/components/index.tsx | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx
index 8c23f341..4ff2e58f 100644
--- a/packages/frontend/src/pages/components/index.tsx
+++ b/packages/frontend/src/pages/components/index.tsx
@@ -1,4 +1,5 @@
import { Button, ButtonOrLinkProps } from 'components/shared/Button';
+import { Calendar } from 'components/shared/Calendar';
import { PlusIcon } from 'components/shared/CustomIcon';
import React from 'react';
@@ -13,6 +14,7 @@ const Page = () => {
packages/frontend/src/pages/components/index.tsx
+
{/* Insert Components here */}
@@ -69,6 +71,15 @@ const Page = () => {
))}
+
+
+
);
From af0d28e6546d0b4f41d4f8a6a178fec04917d015 Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:34:15 +0700
Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20new=20shadows?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/frontend/tailwind.config.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js
index 92ee77d0..00ce21f9 100644
--- a/packages/frontend/tailwind.config.js
+++ b/packages/frontend/tailwind.config.js
@@ -144,6 +144,9 @@ export default withMT({
boxShadow: {
button:
'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)',
+ calendar:
+ '0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)',
+ field: '0px 1px 2px rgba(0, 0, 0, 0.04)',
},
spacing: {
2.5: '0.625rem',
From dc6bf3794a143dfc42d1e9dc99ccbc73c6c607f4 Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 16:54:32 +0700
Subject: [PATCH 7/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20add=20interact?=
=?UTF-8?q?ion=20on=20the=20example=20page?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../frontend/src/pages/components/index.tsx | 28 ++++++++++++++++---
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx
index 4ff2e58f..775acbb1 100644
--- a/packages/frontend/src/pages/components/index.tsx
+++ b/packages/frontend/src/pages/components/index.tsx
@@ -1,9 +1,13 @@
import { Button, ButtonOrLinkProps } from 'components/shared/Button';
import { Calendar } from 'components/shared/Calendar';
import { PlusIcon } from 'components/shared/CustomIcon';
-import React from 'react';
+import React, { useState } from 'react';
+import { Value } from 'react-calendar/dist/cjs/shared/types';
const Page = () => {
+ const [singleDate, setSingleDate] = useState();
+ const [dateRange, setDateRange] = useState();
+
return (
@@ -75,9 +79,25 @@ const Page = () => {
Calendar
-
-
-
+
+
+
Selected date: {singleDate?.toString()}
+
+
+
+
+ Start date:{' '}
+ {dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '}
+
+ End date:{' '}
+ {dateRange instanceof Array ? dateRange[1]?.toString() : ''}
+
+
+
From 5c5d759c10c8d891e854e0052da115ada6daccfd Mon Sep 17 00:00:00 2001
From: Wahyu Kurniawan
Date: Tue, 20 Feb 2024 23:08:04 +0700
Subject: [PATCH 8/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20fix=20typo?=
=?UTF-8?q?=20and=20change=20to=20readable=20name?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../shared/Calendar/Calendar.theme.ts | 4 +--
.../components/shared/Calendar/Calendar.tsx | 30 +++++++++++--------
2 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
index e39e5925..ce6e93d0 100644
--- a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
+++ b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts
@@ -17,8 +17,8 @@ export const calendarTheme = tv({
'py-2.5',
'px-1',
],
- dropdowns: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'],
- dropdown: [
+ actions: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'],
+ button: [
'flex',
'items-center',
'gap-2',
diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.tsx b/packages/frontend/src/components/shared/Calendar/Calendar.tsx
index 737eee75..dfc15e36 100644
--- a/packages/frontend/src/components/shared/Calendar/Calendar.tsx
+++ b/packages/frontend/src/components/shared/Calendar/Calendar.tsx
@@ -21,7 +21,7 @@ import {
import './Calendar.css';
import { format } from 'date-fns';
-const CALENDAR_VIEW = ['month', 'year', 'decade', 'century'] as const;
+const CALENDAR_VIEW = ['month', 'year', 'decade'] as const;
export type CalendarView = (typeof CALENDAR_VIEW)[number];
/**
@@ -82,12 +82,18 @@ export const Calendar = ({
onChange: onChangeProp,
...props
}: CalendarProps): JSX.Element => {
- const { wrapper, calendar, navigation, dropdowns, dropdown, footer } =
- calendarTheme();
+ const {
+ wrapper,
+ calendar,
+ navigation,
+ actions: actionsClass,
+ button,
+ footer,
+ } = calendarTheme();
const today = new Date();
const currentMonth = format(today, 'MMM');
- const currentyear = format(today, 'yyyy');
+ const currentYear = format(today, 'yyyy');
const [view, setView] = useState('month');
const [activeDate, setActiveDate] = useState(
@@ -95,7 +101,7 @@ export const Calendar = ({
);
const [value, setValue] = useState(valueProp as Value);
const [month, setMonth] = useState(currentMonth);
- const [year, setYear] = useState(currentyear);
+ const [year, setYear] = useState(currentYear);
/**
* Update the navigation label based on the active date
@@ -112,10 +118,10 @@ export const Calendar = ({
* Change the active date base on the action and range
*/
const handleNavigate = useCallback(
- (action: 'previous' | 'next', range: 'month' | 'year' | 'decade') => {
+ (action: 'previous' | 'next', view: CalendarView) => {
setActiveDate((date) => {
const newDate = new Date(date);
- switch (range) {
+ switch (view) {
case 'month':
newDate.setMonth(
action === 'previous' ? date.getMonth() - 1 : date.getMonth() + 1,
@@ -158,10 +164,10 @@ export const Calendar = ({
* and also update the navigation label
*/
const handleChangeNavigation = useCallback(
- (type: 'month' | 'year', date: Date) => {
+ (view: 'month' | 'year', date: Date) => {
setActiveDate(date);
changeNavigationLabel(date);
- setView(type);
+ setView(view);
},
[setActiveDate, changeNavigationLabel, setView],
);
@@ -225,10 +231,10 @@ export const Calendar = ({
-
+
}
@@ -238,7 +244,7 @@ export const Calendar = ({
}