├── .changeset
├── README.md
├── config.json
├── pink-impalas-say.md
├── pre.json
└── pretty-boxes-read.md
├── .github
└── FUNDING.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── apps
└── demo
│ ├── CHANGELOG.md
│ ├── components.json
│ ├── eslint.config.mjs
│ ├── messages
│ ├── en.json
│ └── es.json
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ ├── calendar-day.png
│ ├── calendar-month.png
│ ├── calendar-week.png
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── scene.splinecode
│ ├── scene2.splinecode
│ ├── scene3.splinecode
│ ├── scene4.splinecode
│ ├── vercel.svg
│ └── window.svg
│ ├── src
│ ├── app
│ │ ├── [locale]
│ │ │ ├── calendar
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── favicon.ico
│ │ ├── layout.tsx
│ │ ├── manifest.ts
│ │ ├── not-found.tsx
│ │ ├── robots.txt
│ │ └── sitemap.ts
│ ├── components
│ │ ├── app-header.tsx
│ │ ├── calendars.tsx
│ │ ├── date-picker.tsx
│ │ ├── flickering-grid.tsx
│ │ ├── locale-switcher.tsx
│ │ ├── sidebar-left.tsx
│ │ ├── theme-switcher.tsx
│ │ └── ui
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── sidebar.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── toggle-group.tsx
│ │ │ ├── toggle.tsx
│ │ │ └── tooltip.tsx
│ ├── config
│ │ ├── deploy.ts
│ │ └── meta.ts
│ ├── data
│ │ ├── events.json
│ │ ├── events.ts
│ │ └── seed.ts
│ ├── fonts.ts
│ ├── global.d.ts
│ ├── hooks
│ │ ├── use-fake-api.ts
│ │ ├── use-get-all-search-params.ts
│ │ ├── use-mobile.ts
│ │ ├── use-mounted.ts
│ │ └── use-swipe.ts
│ ├── i18n
│ │ ├── request.ts
│ │ └── routing.ts
│ ├── lib
│ │ └── utils.ts
│ ├── middleware.ts
│ ├── providers
│ │ └── query-provider.tsx
│ └── styles
│ │ └── globals.css
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── commitlint.config.mjs
├── docs
├── installation.md
├── overview.md
└── quick-start.md
├── package.json
├── packages
├── react-calendar-day
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── calendar-day-active-drag.tsx
│ │ │ ├── calendar-day-active-resize.tsx
│ │ │ ├── calendar-day-active-section.tsx
│ │ │ ├── calendar-day-active-selection.tsx
│ │ │ ├── calendar-day-axis.tsx
│ │ │ ├── calendar-day-context-menu.tsx
│ │ │ ├── calendar-day-event-card-content.tsx
│ │ │ ├── calendar-day-events.tsx
│ │ │ ├── calendar-day-header.tsx
│ │ │ ├── calendar-day-time-indicator.tsx
│ │ │ └── calendar-day-view.tsx
│ │ ├── hooks
│ │ │ ├── use-calendar-day-drag.ts
│ │ │ ├── use-calendar-day-interaction.ts
│ │ │ ├── use-calendar-day-resize.ts
│ │ │ ├── use-calendar-day-selection.ts
│ │ │ └── use-day-view-position.ts
│ │ ├── index.ts
│ │ └── lib
│ │ │ └── utils.ts
│ └── tsconfig.json
├── react-calendar-month
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── calendar-month-active-drag.tsx
│ │ │ ├── calendar-month-active-resize.tsx
│ │ │ ├── calendar-month-active-section.tsx
│ │ │ ├── calendar-month-active-selection.tsx
│ │ │ ├── calendar-month-context-menu.tsx
│ │ │ ├── calendar-month-day.tsx
│ │ │ ├── calendar-month-event-card-content.tsx
│ │ │ ├── calendar-month-header.tsx
│ │ │ └── calendar-month-view.tsx
│ │ ├── hooks
│ │ │ ├── use-calendar-month-drag.ts
│ │ │ ├── use-calendar-month-interaction.ts
│ │ │ ├── use-calendar-month-resize.ts
│ │ │ ├── use-calendar-month-selection.ts
│ │ │ └── use-month-view-position.ts
│ │ ├── index.ts
│ │ └── lib
│ │ │ └── utils.ts
│ └── tsconfig.json
├── react-calendar-range
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ └── calendar-range-view.tsx
│ │ └── index.ts
│ └── tsconfig.json
├── react-calendar-ui
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── button.tsx
│ │ │ ├── calendar-event-card.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── form.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── sheet.tsx
│ │ │ └── sonner.tsx
│ │ ├── hooks
│ │ │ ├── use-dialog.tsx
│ │ │ ├── use-media-query.tsx
│ │ │ └── use-sheet.tsx
│ │ ├── index.ts
│ │ └── lib
│ │ │ ├── utils.js
│ │ │ └── utils.ts
│ └── tsconfig.json
├── react-calendar-week
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ └── calendar-week-view.tsx
│ │ └── index.ts
│ └── tsconfig.json
└── react-calendar
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── calendar-event-card.tsx
│ │ ├── calendar-event-form.tsx
│ │ └── calendar.tsx
│ ├── hooks
│ │ ├── use-date-fragments.ts
│ │ └── use-react-calendar.tsx
│ ├── index.ts
│ ├── lib
│ │ ├── calendar.ts
│ │ ├── time.ts
│ │ └── utils.ts
│ └── types.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": true,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.changeset/pink-impalas-say.md:
--------------------------------------------------------------------------------
1 | ---
2 | "@illostack/react-calendar-month": patch
3 | "@illostack/react-calendar-range": patch
4 | "@illostack/react-calendar-week": patch
5 | "@illostack/react-calendar-day": patch
6 | "@illostack/react-calendar-ui": patch
7 | "@illostack/react-calendar": patch
8 | ---
9 |
10 | fix: useDateFragments behabiour
11 |
--------------------------------------------------------------------------------
/.changeset/pre.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "pre",
3 | "tag": "beta",
4 | "initialVersions": {
5 | "demo": "0.0.1-beta.1",
6 | "@illostack/react-calendar": "0.0.1-beta.1",
7 | "@illostack/react-calendar-day": "0.0.1-beta.1",
8 | "@illostack/react-calendar-month": "0.0.1-beta.1",
9 | "@illostack/react-calendar-range": "0.0.1-beta.1",
10 | "@illostack/react-calendar-ui": "0.0.1-beta.1",
11 | "@illostack/react-calendar-week": "0.0.1-beta.1"
12 | },
13 | "changesets": ["pink-impalas-say", "pretty-boxes-read"]
14 | }
15 |
--------------------------------------------------------------------------------
/.changeset/pretty-boxes-read.md:
--------------------------------------------------------------------------------
1 | ---
2 | "@illostack/react-calendar-month": patch
3 | "@illostack/react-calendar-range": patch
4 | "@illostack/react-calendar-week": patch
5 | "@illostack/react-calendar-day": patch
6 | "@illostack/react-calendar-ui": patch
7 | "@illostack/react-calendar": patch
8 | "demo": patch
9 | ---
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [illodev]
4 | patreon: IlloStack
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 | next-env.d.ts
15 |
16 | # Testing
17 | coverage
18 |
19 | # Turbo
20 | .turbo
21 |
22 | # Vercel
23 | .vercel
24 |
25 | # Build Outputs
26 | .next/
27 | out/
28 | build
29 | dist
30 |
31 |
32 | # Debug
33 | npm-debug.log*
34 | yarn-debug.log*
35 | yarn-error.log*
36 |
37 | # Misc
38 | .DS_Store
39 | *.pem
40 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no -- commitlint --edit $1
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | pnpm lint-staged
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # Configuración básica para npm
2 |
3 | # Establece el registro predeterminado
4 | registry=https://registry.npmjs.org/
5 |
6 | # Configura el almacenamiento en caché
7 | cache=~/.npm-cache
8 |
9 | # Configura el prefijo global (opcional)
10 | prefix=~/.npm-global
11 |
12 | # Configura el nivel de registro (opcional)
13 | loglevel=warn
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | public
3 | dist
4 | pnpm-lock.yaml
5 | pnpm-workspace.yaml
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "arrowParens": "always",
4 | "trailingComma": "none",
5 | "printWidth": 80,
6 | "tabWidth": 2,
7 | "plugins": ["prettier-plugin-tailwindcss"]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll": "explicit",
4 | "source.organizeImports": "explicit",
5 | "source.sortMembers": "explicit"
6 | },
7 | "eslint.workingDirectories": [
8 | {
9 | "mode": "auto"
10 | }
11 | ],
12 | "typescript.enablePromptUseWorkspaceTsdk": true,
13 | "typescript.tsdk": "node_modules/typescript/lib"
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Álvaro Jáuregui
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IlloStack Calendar
2 |
3 | > [!IMPORTANT]
4 | > **This project is currently in development and not yet stable.**
5 |
6 |
7 | IlloStack Calendar is an open-source calendar with advanced features like drag & drop, customizable events, and multiple views (day, week, custom range, and month). It is designed to be highly flexible and extensible, allowing integration with other tools and full customization of events and styles.
8 |
9 |
10 | ## Features
11 |
12 | ### 📅 **Multiple and Modular Views**
13 | - **Day View**: Display a single day’s events.
14 | - **Week View**: See the events of an entire week.
15 | - **Custom Range View**: Define a custom date range for more flexibility.
16 | - **Month View**: View events for an entire month at a glance.
17 | - **More Views Coming Soon**: Stay tuned for additional viewing options!
18 |
19 | ### 🗓️ **Grid Selection**
20 | - Easily create events by selecting a time slot on the calendar grid.
21 | - Tooltips or previews will guide you during the selection process.
22 |
23 | ### ✨ **Drag & Drop**
24 | - Move events around with ease by dragging and dropping them into new time slots.
25 | - Constraints to avoid placing events outside of working hours or unavailable slots.
26 |
27 | ### 📋 **Context Menu**
28 | - Right-click on events for quick actions like:
29 | - Create, Edit, Delete, Copy, Cut, Paste, or Duplicate events.
30 | - Reschedule, Set Recurring, and View Event Details for better management.
31 |
32 | ### ⌨️ **Keyboard Shortcuts**
33 | - Navigate and manage events more efficiently with keyboard shortcuts.
34 | - Quick access to common actions like creating, editing, or deleting events.
35 | - Shortcuts guide within the UI for easy reference.
36 |
37 | ### 🌍 **Localization Support**
38 | - Full support for different languages and regional date/time formats.
39 | - Adapt the app to various time zones for global use.
40 |
41 | ### 🎨 **Custom Styling**
42 | - Modify the look and feel of your calendar with custom styles, colors, and layouts.
43 | - Import/export styling presets for easy customization across different instances.
44 |
45 | ### 🔗 **API for State & Events**
46 | - Centralized event and state management through an API.
47 |
48 | ### ⏰ **Working Hours Restrictions**
49 | - Configure working hours and hide non-working hours from view.
50 | - **Flexible Exceptions**: Define custom exceptions like holidays or special working hours.
51 |
52 | ### 🖌️ **Custom Decorators**
53 | - Add extra elements to the calendar to enrich the user experience.
54 | - Interactive decorators that can display event details upon clicking.
55 |
56 | ### 🔌 **Integration with External Services**
57 | - **Google Calendar Integration** (coming soon).
58 | - Future updates will include integrations with popular task management tools (e.g., Trello, Asana) for seamless event synchronization.
59 |
--------------------------------------------------------------------------------
/apps/demo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [ee62f92]
8 | - @illostack/react-calendar-month@0.0.1-beta.2
9 | - @illostack/react-calendar-range@0.0.1-beta.2
10 | - @illostack/react-calendar-week@0.0.1-beta.2
11 | - @illostack/react-calendar-day@0.0.1-beta.2
12 | - @illostack/react-calendar@0.0.1-beta.2
13 |
14 | ## 0.0.1-beta.1
15 |
16 | ### Patch Changes
17 |
18 | -
19 | - Updated dependencies
20 | - @illostack/react-calendar-month@0.0.1-beta.1
21 | - @illostack/react-calendar-range@0.0.1-beta.1
22 | - @illostack/react-calendar-week@0.0.1-beta.1
23 | - @illostack/react-calendar-day@0.0.1-beta.1
24 | - @illostack/react-calendar@0.0.1-beta.1
25 |
--------------------------------------------------------------------------------
/apps/demo/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
22 |
--------------------------------------------------------------------------------
/apps/demo/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { nextJsConfig } from "@illostack/eslint-config/next";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default nextJsConfig;
5 |
--------------------------------------------------------------------------------
/apps/demo/messages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Manifest": {
3 | "name": "Name"
4 | },
5 | "calendar": {
6 | "days": {
7 | "0": {
8 | "short": "Sun",
9 | "long": "Sunday"
10 | },
11 | "1": {
12 | "short": "Mon",
13 | "long": "Monday"
14 | },
15 | "2": {
16 | "short": "Tue",
17 | "long": "Tuesday"
18 | },
19 | "3": {
20 | "short": "Wed",
21 | "long": "Wednesday"
22 | },
23 | "4": {
24 | "short": "Thu",
25 | "long": "Thursday"
26 | },
27 | "5": {
28 | "short": "Fri",
29 | "long": "Friday"
30 | },
31 | "6": {
32 | "short": "Sat",
33 | "long": "Saturday"
34 | }
35 | },
36 | "months": {
37 | "0": {
38 | "short": "Jan",
39 | "long": "January"
40 | },
41 | "1": {
42 | "short": "Feb",
43 | "long": "February"
44 | },
45 | "2": {
46 | "short": "Mar",
47 | "long": "March"
48 | },
49 | "3": {
50 | "short": "Apr",
51 | "long": "April"
52 | },
53 | "4": {
54 | "short": "May",
55 | "long": "May"
56 | },
57 | "5": {
58 | "short": "Jun",
59 | "long": "June"
60 | },
61 | "6": {
62 | "short": "Jul",
63 | "long": "July"
64 | },
65 | "7": {
66 | "short": "Aug",
67 | "long": "August"
68 | },
69 | "8": {
70 | "short": "Sep",
71 | "long": "September"
72 | },
73 | "9": {
74 | "short": "Oct",
75 | "long": "October"
76 | },
77 | "10": {
78 | "short": "Nov",
79 | "long": "November"
80 | },
81 | "11": {
82 | "short": "Dec",
83 | "long": "December"
84 | }
85 | }
86 | },
87 | "literals": {
88 | "day": "Day",
89 | "days": "Days",
90 | "week": "Week",
91 | "month": "Month",
92 | "year": "Year",
93 | "range": "Range",
94 | "today": "Today",
95 | "previous": "Previous",
96 | "next": "Next",
97 | "more": "More",
98 | "go-to": "Go to"
99 | },
100 | "form": {
101 | "save": "Save"
102 | },
103 | "action": {
104 | "create-event": "Create event",
105 | "update-event": "Update event",
106 | "delete-event": "Delete event",
107 | "duplicate-event": "Duplicate event",
108 | "copy-event": "Copy event",
109 | "cut-event": "Cut event",
110 | "paste-event": "Paste event",
111 | "undo": "Undo"
112 | },
113 | "message": {
114 | "event-created": "Event created",
115 | "event-updated": "Event updated",
116 | "event-deleted": "Event deleted",
117 | "event-restored": "Event restored",
118 | "event-duplicated": "Event duplicated",
119 | "event-copied": "Event copied",
120 | "event-cutted": "Event cutted",
121 | "event-pasted": "Event pasted",
122 | "event-not-found": "Event not found"
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/apps/demo/messages/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "Manifest": {
3 | "name": "Nombre"
4 | },
5 | "calendar": {
6 | "days": {
7 | "0": {
8 | "short": "Dom",
9 | "long": "Domingo"
10 | },
11 | "1": {
12 | "short": "Lun",
13 | "long": "Lunes"
14 | },
15 | "2": {
16 | "short": "Mar",
17 | "long": "Martes"
18 | },
19 | "3": {
20 | "short": "Mié",
21 | "long": "Miércoles"
22 | },
23 | "4": {
24 | "short": "Jue",
25 | "long": "Jueves"
26 | },
27 | "5": {
28 | "short": "Vie",
29 | "long": "Viernes"
30 | },
31 | "6": {
32 | "short": "Sáb",
33 | "long": "Sábado"
34 | }
35 | },
36 | "months": {
37 | "0": {
38 | "short": "Ene",
39 | "long": "Enero"
40 | },
41 | "1": {
42 | "short": "Feb",
43 | "long": "Febrero"
44 | },
45 | "2": {
46 | "short": "Mar",
47 | "long": "Marzo"
48 | },
49 | "3": {
50 | "short": "Abr",
51 | "long": "Abril"
52 | },
53 | "4": {
54 | "short": "May",
55 | "long": "Mayo"
56 | },
57 | "5": {
58 | "short": "Jun",
59 | "long": "Junio"
60 | },
61 | "6": {
62 | "short": "Jul",
63 | "long": "Julio"
64 | },
65 | "7": {
66 | "short": "Ago",
67 | "long": "Agosto"
68 | },
69 | "8": {
70 | "short": "Sep",
71 | "long": "Septiembre"
72 | },
73 | "9": {
74 | "short": "Oct",
75 | "long": "Octubre"
76 | },
77 | "10": {
78 | "short": "Nov",
79 | "long": "Noviembre"
80 | },
81 | "11": {
82 | "short": "Dic",
83 | "long": "Diciembre"
84 | }
85 | }
86 | },
87 | "literals": {
88 | "day": "Día",
89 | "days": "Días",
90 | "week": "Semana",
91 | "month": "Mes",
92 | "year": "Año",
93 | "range": "Rango",
94 | "today": "Hoy",
95 | "previous": "Anterior",
96 | "next": "Siguiente",
97 | "more": "Más",
98 | "go-to": "Ir a"
99 | },
100 | "form": {
101 | "save": "Guardar"
102 | },
103 | "action": {
104 | "create-event": "Crear evento",
105 | "update-event": "Actualizar evento",
106 | "delete-event": "Eliminar evento",
107 | "duplicate-event": "Duplicar evento",
108 | "copy-event": "Copiar evento",
109 | "cut-event": "Cortar evento",
110 | "paste-event": "Pegar evento",
111 | "undo": "Deshacer"
112 | },
113 | "message": {
114 | "event-created": "Evento creado",
115 | "event-updated": "Evento actualizado",
116 | "event-deleted": "Evento eliminado",
117 | "event-restored": "Evento restaurado",
118 | "event-duplicated": "Evento duplicado",
119 | "event-copied": "Evento copiado",
120 | "event-cutted": "Evento cortado",
121 | "event-pasted": "Evento pegado",
122 | "event-not-found": "Evento no encontrado"
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/apps/demo/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 | import createNextIntlPlugin from "next-intl/plugin";
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const nextConfig: NextConfig = {
7 | images: {
8 | remotePatterns: [
9 | {
10 | hostname: "images.unsplash.com"
11 | }
12 | ]
13 | }
14 | };
15 |
16 | export default withNextIntl(nextConfig);
17 |
--------------------------------------------------------------------------------
/apps/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.0.1-beta.2",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint --max-warnings 0",
10 | "check-types": "tsc --noEmit"
11 | },
12 | "dependencies": {
13 | "@illostack/react-calendar": "workspace:*",
14 | "@illostack/react-calendar-day": "workspace:*",
15 | "@illostack/react-calendar-month": "workspace:*",
16 | "@illostack/react-calendar-range": "workspace:*",
17 | "@illostack/react-calendar-week": "workspace:*",
18 | "@radix-ui/react-avatar": "^1.1.4",
19 | "@radix-ui/react-collapsible": "^1.1.4",
20 | "@radix-ui/react-dialog": "^1.1.7",
21 | "@radix-ui/react-dropdown-menu": "^2.1.7",
22 | "@radix-ui/react-label": "^2.1.3",
23 | "@radix-ui/react-select": "^2.1.7",
24 | "@radix-ui/react-separator": "^1.1.3",
25 | "@radix-ui/react-slot": "^1.2.0",
26 | "@radix-ui/react-tabs": "^1.1.4",
27 | "@radix-ui/react-toggle": "^1.1.3",
28 | "@radix-ui/react-toggle-group": "^1.1.3",
29 | "@radix-ui/react-tooltip": "^1.2.0",
30 | "@react-native-async-storage/async-storage": "^2.1.2",
31 | "@tanstack/query-async-storage-persister": "^5.73.1",
32 | "@tanstack/react-query": "^5.72.2",
33 | "@tanstack/react-query-devtools": "^5.72.2",
34 | "@tanstack/react-query-next-experimental": "^5.72.2",
35 | "@tanstack/react-query-persist-client": "^5.73.1",
36 | "class-variance-authority": "^0.7.1",
37 | "clsx": "^2.1.1",
38 | "lucide-react": "^0.474.0",
39 | "next": "^15.3.0",
40 | "next-intl": "^4.0.2",
41 | "next-themes": "^0.4.6",
42 | "nuqs": "^2.4.1",
43 | "react": "^19.1.0",
44 | "react-day-picker": "^8.10.1",
45 | "react-dom": "^19.1.0",
46 | "tailwind-merge": "^2.6.0",
47 | "tailwindcss-animate": "^1.0.7"
48 | },
49 | "devDependencies": {
50 | "@faker-js/faker": "^9.6.0",
51 | "@illostack/eslint-config": "^0.0.3",
52 | "@illostack/typescript-config": "^0.0.3",
53 | "@types/node": "^22.14.0",
54 | "@types/react": "19.0.10",
55 | "@types/react-dom": "19.0.4",
56 | "eslint": "^9.24.0",
57 | "postcss": "^8.5.3",
58 | "tailwindcss": "^3.4.17",
59 | "typescript": "5.8.2"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/apps/demo/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {}
5 | }
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/apps/demo/public/calendar-day.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/calendar-day.png
--------------------------------------------------------------------------------
/apps/demo/public/calendar-month.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/calendar-month.png
--------------------------------------------------------------------------------
/apps/demo/public/calendar-week.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/calendar-week.png
--------------------------------------------------------------------------------
/apps/demo/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/public/scene.splinecode:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/scene.splinecode
--------------------------------------------------------------------------------
/apps/demo/public/scene2.splinecode:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/scene2.splinecode
--------------------------------------------------------------------------------
/apps/demo/public/scene3.splinecode:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/scene3.splinecode
--------------------------------------------------------------------------------
/apps/demo/public/scene4.splinecode:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/public/scene4.splinecode
--------------------------------------------------------------------------------
/apps/demo/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/demo/src/app/[locale]/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { NextIntlClientProvider } from "next-intl";
3 | import { getMessages, setRequestLocale } from "next-intl/server";
4 | import { ThemeProvider } from "next-themes";
5 | import { Geist } from "next/font/google";
6 | import { notFound } from "next/navigation";
7 | import { NuqsAdapter } from "nuqs/adapters/next/app";
8 |
9 | import meta from "@/config/meta";
10 | import { Locale, routing } from "@/i18n/routing";
11 | import { QueryProvider } from "@/providers/query-provider";
12 | import "@/styles/globals.css";
13 |
14 | const geistSans = Geist({
15 | variable: "--font-geist-sans",
16 | subsets: ["latin"]
17 | });
18 |
19 | export const metadata: Metadata = {
20 | title: meta.title,
21 | description: meta.description,
22 | keywords: meta.keywords
23 | };
24 |
25 | export default async function RootLayout({
26 | children,
27 | params
28 | }: Readonly<{
29 | children: React.ReactNode;
30 | params: Promise<{
31 | locale: string;
32 | }>;
33 | }>) {
34 | const { locale } = await params;
35 |
36 | if (!routing.locales.includes(locale as Locale)) {
37 | notFound();
38 | }
39 |
40 | setRequestLocale(locale);
41 |
42 | const messages = await getMessages();
43 |
44 | return (
45 |
46 |
49 |
50 |
51 |
52 |
58 | {children}
59 |
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/apps/demo/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/apps/demo/src/app/favicon.ico
--------------------------------------------------------------------------------
/apps/demo/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "@/styles/globals.css";
2 | import { ReactNode } from "react";
3 |
4 | type Props = {
5 | children: ReactNode;
6 | };
7 |
8 | export default function RootLayout({ children }: Props) {
9 | return children;
10 | }
11 |
--------------------------------------------------------------------------------
/apps/demo/src/app/manifest.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from "next";
2 | import { getTranslations } from "next-intl/server";
3 |
4 | export default async function manifest(): Promise {
5 | const locale = "en";
6 | const t = await getTranslations({ locale, namespace: "Manifest" });
7 |
8 | return {
9 | name: t("name"),
10 | start_url: "/",
11 | theme_color: "#101E33"
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/apps/demo/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import { NextIntlClientProvider } from "next-intl";
2 | import { getMessages } from "next-intl/server";
3 | import { ThemeProvider } from "next-themes";
4 | import { Geist } from "next/font/google";
5 |
6 | const geistSans = Geist({
7 | variable: "--font-geist-sans",
8 | subsets: ["latin"]
9 | });
10 |
11 | export default async function GlobalNotFound() {
12 | const messages = await getMessages({
13 | locale: "en"
14 | });
15 |
16 | return (
17 |
18 |
19 |
22 |
23 |
24 |
25 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 | Not Found
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/apps/demo/src/app/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: *
--------------------------------------------------------------------------------
/apps/demo/src/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { host } from "@/config/deploy";
2 | import { Locale, getPathname, routing } from "@/i18n/routing";
3 | import { MetadataRoute } from "next";
4 |
5 | export default function sitemap(): MetadataRoute.Sitemap {
6 | return [getEntry("/")];
7 | }
8 |
9 | type Href = Parameters[0]["href"];
10 |
11 | function getEntry(href: Href) {
12 | return {
13 | url: getUrl(href, routing.defaultLocale),
14 | alternates: {
15 | languages: Object.fromEntries(
16 | routing.locales.map((locale) => [locale, getUrl(href, locale)])
17 | )
18 | }
19 | };
20 | }
21 |
22 | function getUrl(href: Href, locale: Locale) {
23 | const pathname = getPathname({ locale, href });
24 | return host + pathname;
25 | }
26 |
--------------------------------------------------------------------------------
/apps/demo/src/components/calendars.tsx:
--------------------------------------------------------------------------------
1 | import { Check, ChevronRight } from "lucide-react";
2 | import * as React from "react";
3 |
4 | import {
5 | Collapsible,
6 | CollapsibleContent,
7 | CollapsibleTrigger
8 | } from "@/components/ui/collapsible";
9 | import {
10 | SidebarGroup,
11 | SidebarGroupContent,
12 | SidebarGroupLabel,
13 | SidebarMenu,
14 | SidebarMenuButton,
15 | SidebarMenuItem,
16 | SidebarSeparator
17 | } from "@/components/ui/sidebar";
18 |
19 | export function Calendars({
20 | calendars
21 | }: {
22 | calendars: {
23 | name: string;
24 | items: string[];
25 | }[];
26 | }) {
27 | return (
28 | <>
29 | {calendars.map((calendar, index) => (
30 |
31 |
32 |
36 |
40 |
41 | {calendar.name}
42 |
43 |
44 |
45 |
46 |
47 |
48 | {calendar.items.map((item, index) => (
49 |
50 |
51 |
55 |
56 |
57 | {item}
58 |
59 |
60 | ))}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ))}
69 | >
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/apps/demo/src/components/date-picker.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import { useLocale, useTranslations } from "next-intl";
5 | import * as React from "react";
6 |
7 | import { Calendar } from "@/components/ui/calendar";
8 | import { SidebarGroup, SidebarGroupContent } from "@/components/ui/sidebar";
9 |
10 | export function DatePicker() {
11 | const locale = useLocale();
12 | const t = useTranslations();
13 | const calendar = useCalendar();
14 |
15 | const dates = calendar
16 | .useWatch((state) => state.dates)
17 | .filter((d) => !d.isOutside);
18 | const date = calendar.useWatch((state) => state.date);
19 | const view = calendar.useWatch((state) => state.view);
20 |
21 | const selected = React.useMemo(() => {
22 | const firstDay = dates.at(0)?.date as Date;
23 | const lastDay = dates.at(-1)?.date as Date;
24 |
25 | return {
26 | from: firstDay,
27 | to: lastDay
28 | };
29 | }, [dates]);
30 |
31 | return (
32 |
33 |
34 | t(`calendar.days.${day}.short`),
44 | month: (month) => t(`calendar.months.${month}.long`),
45 | ordinalNumber: (n) => n.toString(),
46 | era: () => "",
47 | quarter: () => "",
48 | dayPeriod: () => ""
49 | }
50 | }}
51 | defaultMonth={date}
52 | weekStartsOn={locale === "es" ? 1 : 0}
53 | onDayClick={(day) => calendar.changeDate(day)}
54 | className="[&_td:has([role=gridcell].bg-primary)]:bg-sidebar-foreground/10 [&_[role=gridcell].bg-primary]:text-sidebar-foreground [&_[role=gridcell].bg-accent]:!bg-sidebar-primary [&_[role=gridcell].bg-accent]:!text-sidebar-primary-foreground [&_[role=gridcell].bg-accent]:!rounded-md [&_[role=gridcell].bg-primary]:rounded-none [&_[role=gridcell]]:!bg-transparent"
55 | />
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/apps/demo/src/components/locale-switcher.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useLocale } from "next-intl";
4 | import { useParams } from "next/navigation";
5 | import * as React from "react";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | Select,
10 | SelectContent,
11 | SelectGroup,
12 | SelectItem,
13 | SelectTrigger,
14 | SelectValue
15 | } from "@/components/ui/select";
16 | import { useGetAllSearchParams } from "@/hooks/use-get-all-search-params";
17 | import { Locale, localesMap, usePathname, useRouter } from "@/i18n/routing";
18 | import { cn } from "@/lib/utils";
19 |
20 | type LocaleSwitcherProps = React.ComponentProps;
21 |
22 | const LocaleSwitcher = React.forwardRef(
23 | ({ className, ...props }, ref) => {
24 | const locale = useLocale();
25 | const router = useRouter();
26 | const pathname = usePathname();
27 | const params = useParams();
28 | const query = useGetAllSearchParams();
29 | const [isPending, startTransition] = React.useTransition();
30 |
31 | function onSelectChange(value: Locale) {
32 | startTransition(() => {
33 | router.replace(
34 | // @ts-expect-error -- TypeScript will validate that only known `params`
35 | // are used in combination with a given `pathname`. Since the two will
36 | // always match for the current route, we can skip runtime checks.
37 | { pathname, params, query },
38 | { locale: value }
39 | );
40 | });
41 | }
42 |
43 | return (
44 |
71 | );
72 | }
73 | );
74 |
75 | LocaleSwitcher.displayName = "LocaleSwitcher";
76 |
77 | export { LocaleSwitcher };
78 |
--------------------------------------------------------------------------------
/apps/demo/src/components/theme-switcher.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { MoonIcon, SunIcon } from "lucide-react";
4 | import { useTheme } from "next-themes";
5 | import * as React from "react";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import { Skeleton } from "@/components/ui/skeleton";
9 | import { useMounted } from "@/hooks/use-mounted";
10 | import { cn } from "@/lib/utils";
11 |
12 | const themes = [
13 | {
14 | label: "Claro",
15 | value: "light",
16 | icon: SunIcon
17 | },
18 | {
19 | label: "Oscuro",
20 | value: "dark",
21 | icon: MoonIcon
22 | }
23 | ];
24 |
25 | type ThemeSwitcherProps = React.ComponentProps;
26 |
27 | const ThemeSwitcher = React.forwardRef(
28 | ({ className, ...props }, ref) => {
29 | const mounted = useMounted();
30 | const { theme, setTheme } = useTheme();
31 |
32 | const selectedTheme = React.useMemo(
33 | () => themes.find((t) => t.value !== theme) || themes[0],
34 | [theme]
35 | ) as (typeof themes)[number];
36 |
37 | if (!mounted) {
38 | return ;
39 | }
40 |
41 | return (
42 |
55 | );
56 | }
57 | );
58 |
59 | ThemeSwitcher.displayName = "ThemeSwitcher";
60 |
61 | export { ThemeSwitcher };
62 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ));
21 | Avatar.displayName = AvatarPrimitive.Root.displayName;
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ));
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ));
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
49 |
50 | export { Avatar, AvatarImage, AvatarFallback };
51 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17 | outline: "text-foreground"
18 | }
19 | },
20 | defaultVariants: {
21 | variant: "default"
22 | }
23 | }
24 | );
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | );
34 | }
35 |
36 | export { Badge, badgeVariants };
37 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { ChevronRight, MoreHorizontal } from "lucide-react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const Breadcrumb = React.forwardRef<
8 | HTMLElement,
9 | React.ComponentPropsWithoutRef<"nav"> & {
10 | separator?: React.ReactNode;
11 | }
12 | >(({ ...props }, ref) => );
13 | Breadcrumb.displayName = "Breadcrumb";
14 |
15 | const BreadcrumbList = React.forwardRef<
16 | HTMLOListElement,
17 | React.ComponentPropsWithoutRef<"ol">
18 | >(({ className, ...props }, ref) => (
19 |
27 | ));
28 | BreadcrumbList.displayName = "BreadcrumbList";
29 |
30 | const BreadcrumbItem = React.forwardRef<
31 | HTMLLIElement,
32 | React.ComponentPropsWithoutRef<"li">
33 | >(({ className, ...props }, ref) => (
34 |
39 | ));
40 | BreadcrumbItem.displayName = "BreadcrumbItem";
41 |
42 | const BreadcrumbLink = React.forwardRef<
43 | HTMLAnchorElement,
44 | React.ComponentPropsWithoutRef<"a"> & {
45 | asChild?: boolean;
46 | }
47 | >(({ asChild, className, ...props }, ref) => {
48 | const Comp = asChild ? Slot : "a";
49 |
50 | return (
51 |
56 | );
57 | });
58 | BreadcrumbLink.displayName = "BreadcrumbLink";
59 |
60 | const BreadcrumbPage = React.forwardRef<
61 | HTMLSpanElement,
62 | React.ComponentPropsWithoutRef<"span">
63 | >(({ className, ...props }, ref) => (
64 |
72 | ));
73 | BreadcrumbPage.displayName = "BreadcrumbPage";
74 |
75 | const BreadcrumbSeparator = ({
76 | children,
77 | className,
78 | ...props
79 | }: React.ComponentProps<"li">) => (
80 | svg]:h-3.5 [&>svg]:w-3.5", className)}
84 | {...props}
85 | >
86 | {children ?? }
87 |
88 | );
89 | BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
90 |
91 | const BreadcrumbEllipsis = ({
92 | className,
93 | ...props
94 | }: React.ComponentProps<"span">) => (
95 |
101 |
102 | More
103 |
104 | );
105 | BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
106 |
107 | export {
108 | Breadcrumb,
109 | BreadcrumbList,
110 | BreadcrumbItem,
111 | BreadcrumbLink,
112 | BreadcrumbPage,
113 | BreadcrumbSeparator,
114 | BreadcrumbEllipsis
115 | };
116 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline"
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9"
28 | }
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default"
33 | }
34 | }
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | }
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ChevronLeft, ChevronRight } from "lucide-react";
4 | import * as React from "react";
5 | import { DayPicker } from "react-day-picker";
6 |
7 | import { buttonVariants } from "@/components/ui/button";
8 | import { cn } from "@/lib/utils";
9 |
10 | export type CalendarProps = React.ComponentProps;
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
43 | : "[&:has([aria-selected])]:rounded-md"
44 | ),
45 | day: cn(
46 | buttonVariants({ variant: "ghost" }),
47 | "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
48 | ),
49 | day_range_start: "day-range-start",
50 | day_range_end: "day-range-end",
51 | day_selected:
52 | "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
53 | day_today: "bg-accent text-accent-foreground",
54 | day_outside:
55 | "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
56 | day_disabled: "text-muted-foreground opacity-50",
57 | day_range_middle:
58 | "aria-selected:bg-accent aria-selected:text-accent-foreground",
59 | day_hidden: "invisible",
60 | ...classNames
61 | }}
62 | components={{
63 | IconLeft: ({ className, ...props }) => (
64 |
65 | ),
66 | IconRight: ({ className, ...props }) => (
67 |
68 | )
69 | }}
70 | {...props}
71 | />
72 | );
73 | }
74 | Calendar.displayName = "Calendar";
75 |
76 | export { Calendar };
77 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ));
42 | CardTitle.displayName = "CardTitle";
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLDivElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ));
54 | CardDescription.displayName = "CardDescription";
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ));
62 | CardContent.displayName = "CardContent";
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ));
74 | CardFooter.displayName = "CardFooter";
75 |
76 | export {
77 | Card,
78 | CardHeader,
79 | CardFooter,
80 | CardTitle,
81 | CardDescription,
82 | CardContent
83 | };
84 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4 |
5 | const Collapsible = CollapsiblePrimitive.Root;
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent };
12 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | );
18 | }
19 | );
20 | Input.displayName = "Input";
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as LabelPrimitive from "@radix-ui/react-label";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 |
26 | export { Label };
27 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | );
29 | Separator.displayName = SeparatorPrimitive.Root.displayName;
30 |
31 | export { Separator };
32 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TabsPrimitive from "@radix-ui/react-tabs";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Tabs = TabsPrimitive.Root;
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ));
23 | TabsList.displayName = TabsPrimitive.List.displayName;
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ));
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ));
53 | TabsContent.displayName = TabsPrimitive.Content.displayName;
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent };
56 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5 | import { type VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 | import { toggleVariants } from "@/components/ui/toggle";
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default"
15 | });
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ));
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext);
41 |
42 | return (
43 |
54 | {children}
55 |
56 | );
57 | });
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
60 |
61 | export { ToggleGroup, ToggleGroupItem };
62 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TogglePrimitive from "@radix-ui/react-toggle";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground"
17 | },
18 | size: {
19 | default: "h-9 px-2 min-w-9",
20 | sm: "h-8 px-1.5 min-w-8",
21 | lg: "h-10 px-2.5 min-w-10"
22 | }
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default"
27 | }
28 | }
29 | );
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ));
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName;
44 |
45 | export { Toggle, toggleVariants };
46 |
--------------------------------------------------------------------------------
/apps/demo/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider;
9 |
10 | const Tooltip = TooltipPrimitive.Root;
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger;
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
19 |
28 |
29 | ));
30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
31 |
32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
33 |
--------------------------------------------------------------------------------
/apps/demo/src/config/deploy.ts:
--------------------------------------------------------------------------------
1 | export const port = process.env.PORT || 3000;
2 | export const host = process.env.VERCEL_PROJECT_PRODUCTION_URL
3 | ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
4 | : `http://localhost:${port}`;
5 |
--------------------------------------------------------------------------------
/apps/demo/src/config/meta.ts:
--------------------------------------------------------------------------------
1 | const meta = {
2 | title: "IlloStack Calendar",
3 | description:
4 | "IlloStack Calendar is an open-source calendar with customizable views, external integrations, and a flexible API for event management.",
5 | keywords:
6 | "calendar, open-source, events, scheduling, react, integration, API, IlloStack",
7 | author: "IlloStack Team",
8 | "og:title": "IlloStack Calendar",
9 | "og:description":
10 | "An advanced open-source calendar with customizable views and a flexible API.",
11 | "og:image": "https://illostack.com/calendar-demo-thumbnail.jpg",
12 | "og:url": "https://illostack.com/calendar",
13 | "twitter:card": "summary_large_image"
14 | };
15 | export default meta;
16 |
--------------------------------------------------------------------------------
/apps/demo/src/data/events.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "e0e42853-711a-4be2-8e4f-040338cf690d",
4 | "summary": "aurum tergeo sum",
5 | "startAt": "2025-03-15T04:54:03.423Z",
6 | "endAt": "2025-03-15T06:54:03.423Z",
7 | "color": "green"
8 | },
9 | {
10 | "id": "9cb26b3e-922b-4418-9995-910420c8fe4f",
11 | "summary": "teneo candidus copia",
12 | "startAt": "2025-03-09T22:07:39.346Z",
13 | "endAt": "2025-03-10T00:07:39.346Z",
14 | "color": "indigo"
15 | },
16 | {
17 | "id": "e608bd41-8d9d-43ad-a3ab-b0404a0574c0",
18 | "summary": "quia substantia celer",
19 | "startAt": "2025-03-08T09:36:09.221Z",
20 | "endAt": "2025-03-08T11:06:09.221Z",
21 | "color": "red"
22 | },
23 | {
24 | "id": "8485fdda-d481-4139-8e0f-3b06ad5679a1",
25 | "summary": "vindico claudeo sono",
26 | "startAt": "2025-02-27T13:24:13.935Z",
27 | "endAt": "2025-02-27T14:54:13.935Z",
28 | "color": "blue"
29 | },
30 | {
31 | "id": "dac4fc04-bb7f-452e-a5a4-42de0f8943dd",
32 | "summary": "balbus at molestias",
33 | "startAt": "2025-03-15T11:45:01.249Z",
34 | "endAt": "2025-03-15T13:15:01.249Z",
35 | "color": "cyan"
36 | },
37 | {
38 | "id": "b25efd4e-850c-4212-a5ab-332da132c8c7",
39 | "summary": "callide teres pecus",
40 | "startAt": "2025-03-12T12:00:45.417Z",
41 | "endAt": "2025-03-12T13:30:45.417Z",
42 | "color": "blue"
43 | },
44 | {
45 | "id": "cf021abf-cf96-4c42-9949-28f3e8cf95ff",
46 | "summary": "cur asper perspiciatis",
47 | "startAt": "2025-02-26T19:22:19.586Z",
48 | "endAt": "2025-02-26T20:22:19.586Z",
49 | "color": "purple"
50 | },
51 | {
52 | "id": "27f81ce4-65f7-4fe4-9b9f-b4d77e90aade",
53 | "summary": "vitiosus surgo conscendo",
54 | "startAt": "2025-03-16T12:41:04.340Z",
55 | "endAt": "2025-03-16T13:26:04.340Z",
56 | "color": "cyan"
57 | },
58 | {
59 | "id": "c765e831-20a8-4c60-8e35-f73fc95d5ccf",
60 | "summary": "adfectus veritatis adinventitias",
61 | "startAt": "2025-03-02T18:00:53.602Z",
62 | "endAt": "2025-03-02T20:00:53.602Z",
63 | "color": "green"
64 | },
65 | {
66 | "id": "b005a392-5b92-4cdc-a612-da2eacb97954",
67 | "summary": "deprecator arcesso defluo",
68 | "startAt": "2025-03-14T16:38:08.742Z",
69 | "endAt": "2025-03-14T18:23:08.742Z",
70 | "color": "pink"
71 | }
72 | ]
73 |
--------------------------------------------------------------------------------
/apps/demo/src/data/events.ts:
--------------------------------------------------------------------------------
1 | import { CalendarProvidedEvent } from "@illostack/react-calendar";
2 |
3 | export const events: CalendarProvidedEvent[] = [
4 | {
5 | id: "e0e42853-711a-4be2-8e4f-040338cf690d",
6 | summary: "aurum tergeo sum",
7 | startAt: "2025-03-15T04:54:03.423Z",
8 | endAt: "2025-03-15T06:54:03.423Z",
9 | color: "green"
10 | },
11 | {
12 | id: "9cb26b3e-922b-4418-9995-910420c8fe4f",
13 | summary: "teneo candidus copia",
14 | startAt: "2025-03-09T22:07:39.346Z",
15 | endAt: "2025-03-10T00:07:39.346Z",
16 | color: "indigo"
17 | },
18 | {
19 | id: "e608bd41-8d9d-43ad-a3ab-b0404a0574c0",
20 | summary: "quia substantia celer",
21 | startAt: "2025-03-08T09:36:09.221Z",
22 | endAt: "2025-03-08T11:06:09.221Z",
23 | color: "red"
24 | },
25 | {
26 | id: "8485fdda-d481-4139-8e0f-3b06ad5679a1",
27 | summary: "vindico claudeo sono",
28 | startAt: "2025-02-27T13:24:13.935Z",
29 | endAt: "2025-02-27T14:54:13.935Z",
30 | color: "blue"
31 | },
32 | {
33 | id: "dac4fc04-bb7f-452e-a5a4-42de0f8943dd",
34 | summary: "balbus at molestias",
35 | startAt: "2025-03-15T11:45:01.249Z",
36 | endAt: "2025-03-15T13:15:01.249Z",
37 | color: "cyan"
38 | },
39 | {
40 | id: "b25efd4e-850c-4212-a5ab-332da132c8c7",
41 | summary: "callide teres pecus",
42 | startAt: "2025-03-12T12:00:45.417Z",
43 | endAt: "2025-03-12T13:30:45.417Z",
44 | color: "blue"
45 | },
46 | {
47 | id: "cf021abf-cf96-4c42-9949-28f3e8cf95ff",
48 | summary: "cur asper perspiciatis",
49 | startAt: "2025-02-26T19:22:19.586Z",
50 | endAt: "2025-02-26T20:22:19.586Z",
51 | color: "purple"
52 | },
53 | {
54 | id: "27f81ce4-65f7-4fe4-9b9f-b4d77e90aade",
55 | summary: "vitiosus surgo conscendo",
56 | startAt: "2025-03-16T12:41:04.340Z",
57 | endAt: "2025-03-16T13:26:04.340Z",
58 | color: "cyan"
59 | },
60 | {
61 | id: "c765e831-20a8-4c60-8e35-f73fc95d5ccf",
62 | summary: "adfectus veritatis adinventitias",
63 | startAt: "2025-03-02T18:00:53.602Z",
64 | endAt: "2025-03-02T20:00:53.602Z",
65 | color: "green"
66 | },
67 | {
68 | id: "b005a392-5b92-4cdc-a612-da2eacb97954",
69 | summary: "deprecator arcesso defluo",
70 | startAt: "2025-03-14T16:38:08.742Z",
71 | endAt: "2025-03-14T18:23:08.742Z",
72 | color: "pink"
73 | }
74 | ];
75 |
--------------------------------------------------------------------------------
/apps/demo/src/data/seed.ts:
--------------------------------------------------------------------------------
1 | import { faker } from "@faker-js/faker";
2 | import {
3 | CALENDAR_COLORS,
4 | CalendarProvidedEvent,
5 | addDays
6 | } from "@illostack/react-calendar";
7 | import fs from "fs";
8 | import path from "path";
9 |
10 | const NUM_EVENTS = 10;
11 | const minutes = [45, 60, 75, 90, 105, 120];
12 | const refDate = addDays(new Date(), 10);
13 |
14 | const events: CalendarProvidedEvent[] = Array.from(
15 | { length: NUM_EVENTS },
16 | () => {
17 | const startAt = faker.date.recent({ days: 20, refDate: refDate });
18 | const endAt = new Date(startAt);
19 | endAt.setMinutes(
20 | startAt.getMinutes() +
21 | minutes[Math.floor(Math.random() * minutes.length)]!
22 | );
23 | const color = faker.helpers.arrayElement(CALENDAR_COLORS);
24 |
25 | return {
26 | id: faker.string.uuid(),
27 | summary: faker.lorem.words(3),
28 | startAt: startAt.toISOString(),
29 | endAt: endAt.toISOString(),
30 | color
31 | };
32 | }
33 | );
34 |
35 | fs.writeFileSync(
36 | path.join(__dirname, "events.json"),
37 | JSON.stringify(events, null, 2)
38 | );
39 |
40 | fs.writeFileSync(
41 | path.join(__dirname, "events.ts"),
42 | `import { CalendarProvidedEvent } from "@illostack/react-calendar";
43 |
44 | export const events: CalendarProvidedEvent[] = ${JSON.stringify(events)};`
45 | );
46 |
47 | console.log(`Generated ${NUM_EVENTS} events`);
48 |
--------------------------------------------------------------------------------
/apps/demo/src/fonts.ts:
--------------------------------------------------------------------------------
1 | import { Butterfly_Kids, DM_Sans, Space_Grotesk } from "next/font/google";
2 |
3 | export const dmSans = DM_Sans({
4 | subsets: ["latin"],
5 | display: "swap",
6 | preload: true
7 | });
8 |
9 | export const spaceGrotesk = Space_Grotesk({
10 | subsets: ["latin"],
11 | display: "swap",
12 | preload: true
13 | });
14 |
15 | export const butterflyKids = Butterfly_Kids({
16 | subsets: ["latin"],
17 | display: "swap",
18 | weight: "400",
19 | preload: true
20 | });
21 |
--------------------------------------------------------------------------------
/apps/demo/src/global.d.ts:
--------------------------------------------------------------------------------
1 | import en from "../messages/en.json";
2 |
3 | type Messages = typeof en;
4 |
5 | declare global {
6 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type
7 | interface IntlMessages extends Messages {}
8 | }
9 |
--------------------------------------------------------------------------------
/apps/demo/src/hooks/use-fake-api.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | CalendarProvidedEvent
6 | } from "@illostack/react-calendar";
7 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8 |
9 | const API_DELAY = 300;
10 |
11 | const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
12 |
13 | export const useFakeApi = () => {
14 | const queryClient = useQueryClient();
15 |
16 | const { data } = useQuery<
17 | CalendarProvidedEvent[],
18 | Error,
19 | CalendarProvidedEvent[]
20 | >({
21 | queryKey: ["events"],
22 | queryFn: async () => {
23 | return [];
24 | }
25 | });
26 |
27 | const { mutateAsync: createEvent } = useMutation<
28 | CalendarEvent,
29 | Error,
30 | CalendarEvent
31 | >({
32 | mutationKey: ["createEvent"],
33 | mutationFn: async (event) => {
34 | queryClient.setQueryData(["events"], (data: CalendarEvent[]) => {
35 | return [...data, event];
36 | });
37 |
38 | await wait(API_DELAY);
39 |
40 | return event;
41 | }
42 | });
43 |
44 | const { mutateAsync: updateEvent } = useMutation<
45 | CalendarEvent,
46 | Error,
47 | CalendarEvent
48 | >({
49 | mutationKey: ["updateEvent"],
50 | mutationFn: async (event) => {
51 | queryClient.setQueryData(["events"], (data: CalendarEvent[]) => {
52 | return data.map((e) => (e.id === event.id ? event : e));
53 | });
54 |
55 | await wait(API_DELAY);
56 |
57 | return event;
58 | }
59 | });
60 |
61 | const { mutateAsync: deleteEvent } = useMutation<
62 | CalendarEvent,
63 | Error,
64 | CalendarEvent
65 | >({
66 | mutationKey: ["deleteEvent"],
67 | mutationFn: async (event) => {
68 | queryClient.setQueryData(["events"], (data: CalendarEvent[]) => {
69 | return data.filter((e) => e.id !== event.id);
70 | });
71 |
72 | await wait(API_DELAY);
73 |
74 | return event;
75 | }
76 | });
77 |
78 | return {
79 | events: data,
80 | createEvent,
81 | updateEvent,
82 | deleteEvent
83 | };
84 | };
85 |
--------------------------------------------------------------------------------
/apps/demo/src/hooks/use-get-all-search-params.ts:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from "next/navigation";
2 |
3 | export function useGetAllSearchParams() {
4 | const searchParams = useSearchParams();
5 | const params: { [anyProp: string]: string } = {};
6 |
7 | searchParams.forEach((value, key) => {
8 | params[key] = value;
9 | });
10 |
11 | return params;
12 | }
13 |
--------------------------------------------------------------------------------
/apps/demo/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(
7 | undefined
8 | );
9 |
10 | React.useEffect(() => {
11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12 | const onChange = () => {
13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14 | };
15 | mql.addEventListener("change", onChange);
16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17 | return () => mql.removeEventListener("change", onChange);
18 | }, []);
19 |
20 | return !!isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/apps/demo/src/hooks/use-mounted.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 |
5 | export const useMounted = () => {
6 | const [isMounted, setIsMounted] = React.useState(false);
7 |
8 | React.useEffect(() => {
9 | setIsMounted(true);
10 |
11 | return () => setIsMounted(false);
12 | }, []);
13 |
14 | return isMounted;
15 | };
16 |
--------------------------------------------------------------------------------
/apps/demo/src/hooks/use-swipe.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | const useSwipe = (
4 | onSwipeLeft?: () => void,
5 | onSwipeRight?: () => void,
6 | tolerance = 50
7 | ) => {
8 | const [touchStart, setTouchStart] = useState(null);
9 | const [touchEnd, setTouchEnd] = useState(null);
10 |
11 | useEffect(() => {
12 | const handleTouchStart = (e: TouchEvent) => {
13 | if (e.touches.length > 0) {
14 | setTouchStart(e.touches[0]!.clientX);
15 | }
16 | setTouchEnd(null);
17 | };
18 |
19 | const handleTouchMove = (e: TouchEvent) => {
20 | setTouchEnd(e.touches[0]!.clientX);
21 | };
22 |
23 | const handleTouchEnd = () => {
24 | if (!touchStart || !touchEnd) return;
25 | const distance = touchStart - touchEnd;
26 |
27 | if (distance > tolerance) {
28 | onSwipeLeft?.();
29 | } else if (distance < -tolerance) {
30 | onSwipeRight?.();
31 | }
32 | };
33 |
34 | window.addEventListener("touchstart", handleTouchStart);
35 | window.addEventListener("touchmove", handleTouchMove);
36 | window.addEventListener("touchend", handleTouchEnd);
37 |
38 | return () => {
39 | window.removeEventListener("touchstart", handleTouchStart);
40 | window.removeEventListener("touchmove", handleTouchMove);
41 | window.removeEventListener("touchend", handleTouchEnd);
42 | };
43 | }, [touchStart, touchEnd, onSwipeLeft, onSwipeRight, tolerance]);
44 | };
45 |
46 | export default useSwipe;
47 |
--------------------------------------------------------------------------------
/apps/demo/src/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import { getRequestConfig } from "next-intl/server";
2 |
3 | import { Locale, routing } from "@/i18n/routing";
4 |
5 | export default getRequestConfig(async ({ requestLocale }) => {
6 | // This typically corresponds to the `[locale]` segment
7 | let locale = await requestLocale;
8 |
9 | // Ensure that a valid locale is used
10 | if (!locale || !routing.locales.includes(locale as Locale)) {
11 | locale = routing.defaultLocale;
12 | }
13 |
14 | return {
15 | locale,
16 | messages: (
17 | await (locale === "en"
18 | ? // When using Turbopack, this will enable HMR for `en`
19 | import("../../messages/en.json")
20 | : import(`../../messages/${locale}.json`))
21 | ).default
22 | };
23 | });
24 |
--------------------------------------------------------------------------------
/apps/demo/src/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import { createNavigation } from "next-intl/navigation";
2 | import { defineRouting } from "next-intl/routing";
3 |
4 | export const routing = defineRouting({
5 | locales: ["en", "es"],
6 | defaultLocale: "en",
7 | localeDetection: true,
8 | pathnames: {
9 | "/": "/",
10 | "/login": "/login",
11 | "/calendar": "/calendar"
12 | }
13 | });
14 |
15 | export const localesMap = {
16 | en: {
17 | name: "English",
18 | flag: "🇺🇸"
19 | },
20 | es: {
21 | name: "Español",
22 | flag: "🇪🇸"
23 | }
24 | };
25 |
26 | // Lightweight wrappers around Next.js' navigation APIs
27 | // that will consider the routing configuration
28 | export const { Link, redirect, usePathname, useRouter, getPathname } =
29 | createNavigation(routing);
30 |
31 | export type Pathnames = keyof typeof routing.pathnames;
32 | export type Locale = (typeof routing.locales)[number];
33 |
--------------------------------------------------------------------------------
/apps/demo/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/demo/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from "next-intl/middleware";
2 |
3 | import { routing } from "@/i18n/routing";
4 |
5 | export default createMiddleware(routing);
6 |
7 | export const config = {
8 | matcher: ["/", "/(es|en)/:path*", "/((?!_next|_vercel|api|.*\\..*).*)"]
9 | };
10 |
--------------------------------------------------------------------------------
/apps/demo/src/providers/query-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import AsyncStorage from "@react-native-async-storage/async-storage";
4 | import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
5 | import { isServer, QueryClient } from "@tanstack/react-query";
6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
7 | import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
8 | import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
9 | import * as React from "react";
10 |
11 | const QUERY_CACHE_PREFIX = "queryClient";
12 |
13 | function makeQueryClient() {
14 | return new QueryClient({
15 | defaultOptions: {
16 | queries: {
17 | staleTime: 1000 * 60 * 60, // 1 hour
18 | gcTime: 1000 * 60 * 60 * 24 // 24 hours
19 | }
20 | }
21 | });
22 | }
23 |
24 | let browserQueryClient: QueryClient | undefined = undefined;
25 |
26 | function getQueryClient() {
27 | if (isServer) {
28 | return makeQueryClient();
29 | } else {
30 | if (!browserQueryClient) browserQueryClient = makeQueryClient();
31 | return browserQueryClient;
32 | }
33 | }
34 |
35 | const asyncStoragePersister = createAsyncStoragePersister({
36 | storage: AsyncStorage,
37 | key: QUERY_CACHE_PREFIX
38 | });
39 |
40 | const QueryProvider = (props: { children: React.ReactNode }) => {
41 | const queryClient = getQueryClient();
42 |
43 | return (
44 |
48 |
49 | {props.children}
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export { QueryProvider };
57 |
--------------------------------------------------------------------------------
/apps/demo/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 5.9% 10%;
26 | --radius: 0.5rem;
27 | --chart-1: 12 76% 61%;
28 | --chart-2: 173 58% 39%;
29 | --chart-3: 197 37% 24%;
30 | --chart-4: 43 74% 66%;
31 | --chart-5: 27 87% 67%;
32 |
33 | --sidebar-background: 0 0% 98%;
34 | --sidebar-foreground: 240 5.3% 26.1%;
35 | --sidebar-primary: 224.3 76.3% 48%;
36 | --sidebar-primary-foreground: 0 0% 98%;
37 | --sidebar-accent: 240 4.8% 95.9%;
38 | --sidebar-accent-foreground: 240 5.9% 10%;
39 | --sidebar-border: 220 13% 91%;
40 | --sidebar-ring: 240 5% 64.9%;
41 |
42 | --calendar-background: var(--background);
43 | --calendar-foreground: var(--foreground);
44 | --calendar-primary: 217.2 91.2% 59.8%;
45 | --calendar-primary-foreground: var(--primary-foreground);
46 | --calendar-accent: var(--accent);
47 | --calendar-accent-foreground: var(--accent-foreground);
48 | --calendar-border: var(--border);
49 | --calendar-ring: var(--ring);
50 | --calendar-radius: var(--radius);
51 | }
52 |
53 | .dark {
54 | --background: 240 10% 3.9%;
55 | --foreground: 0 0% 98%;
56 | --card: 240 10% 3.9%;
57 | --card-foreground: 0 0% 98%;
58 | --popover: 240 10% 3.9%;
59 | --popover-foreground: 0 0% 98%;
60 | --primary: 0 0% 98%;
61 | --primary-foreground: 240 5.9% 10%;
62 | --secondary: 240 3.7% 15.9%;
63 | --secondary-foreground: 0 0% 98%;
64 | --muted: 240 3.7% 15.9%;
65 | --muted-foreground: 240 5% 64.9%;
66 | --accent: 240 3.7% 15.9%;
67 | --accent-foreground: 0 0% 98%;
68 | --destructive: 0 62.8% 30.6%;
69 | --destructive-foreground: 0 0% 98%;
70 | --border: 240 3.7% 15.9%;
71 | --input: 240 3.7% 15.9%;
72 | --ring: 240 4.9% 83.9%;
73 | --chart-1: 220 70% 50%;
74 | --chart-2: 160 60% 45%;
75 | --chart-3: 30 80% 55%;
76 | --chart-4: 280 65% 60%;
77 | --chart-5: 340 75% 55%;
78 |
79 | --sidebar-background: 240 5.9% 10%;
80 | --sidebar-foreground: 240 4.8% 95.9%;
81 | --sidebar-primary: 224.3 76.3% 48%;
82 | --sidebar-primary-foreground: 0 0% 100%;
83 | --sidebar-accent: 240 3.7% 15.9%;
84 | --sidebar-accent-foreground: 240 4.8% 95.9%;
85 | --sidebar-border: 240 3.7% 15.9%;
86 | --sidebar-ring: 240 4.9% 83.9%;
87 | }
88 |
89 | * {
90 | @apply border-border;
91 | }
92 |
93 | body {
94 | @apply bg-background text-foreground;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/apps/demo/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import animate from "tailwindcss-animate";
3 |
4 | export default {
5 | darkMode: ["class"],
6 | content: [
7 | "./node_modules/@illostack/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
9 | "./src/providers/**/*.{js,ts,jsx,tsx,mdx}",
10 | "./src/lib/**/*.{js,ts,jsx,tsx,mdx}",
11 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}"
12 | ],
13 | theme: {
14 | container: {
15 | center: true,
16 | padding: {
17 | DEFAULT: "1rem",
18 | sm: "1rem",
19 | lg: "2rem",
20 | xl: "3rem",
21 | "2xl": "4rem"
22 | }
23 | },
24 | extend: {
25 | borderRadius: {
26 | lg: "var(--radius)",
27 | md: "calc(var(--radius) - 2px)",
28 | sm: "calc(var(--radius) - 4px)"
29 | },
30 | colors: {
31 | background: "hsl(var(--background))",
32 | foreground: "hsl(var(--foreground))",
33 | card: {
34 | DEFAULT: "hsl(var(--card))",
35 | foreground: "hsl(var(--card-foreground))"
36 | },
37 | popover: {
38 | DEFAULT: "hsl(var(--popover))",
39 | foreground: "hsl(var(--popover-foreground))"
40 | },
41 | primary: {
42 | DEFAULT: "hsl(var(--primary))",
43 | foreground: "hsl(var(--primary-foreground))"
44 | },
45 | secondary: {
46 | DEFAULT: "hsl(var(--secondary))",
47 | foreground: "hsl(var(--secondary-foreground))"
48 | },
49 | muted: {
50 | DEFAULT: "hsl(var(--muted))",
51 | foreground: "hsl(var(--muted-foreground))"
52 | },
53 | accent: {
54 | DEFAULT: "hsl(var(--accent))",
55 | foreground: "hsl(var(--accent-foreground))"
56 | },
57 | destructive: {
58 | DEFAULT: "hsl(var(--destructive))",
59 | foreground: "hsl(var(--destructive-foreground))"
60 | },
61 | border: "hsl(var(--border))",
62 | input: "hsl(var(--input))",
63 | ring: "hsl(var(--ring))",
64 | chart: {
65 | "1": "hsl(var(--chart-1))",
66 | "2": "hsl(var(--chart-2))",
67 | "3": "hsl(var(--chart-3))",
68 | "4": "hsl(var(--chart-4))",
69 | "5": "hsl(var(--chart-5))"
70 | },
71 | sidebar: {
72 | DEFAULT: "hsl(var(--sidebar-background))",
73 | foreground: "hsl(var(--sidebar-foreground))",
74 | primary: "hsl(var(--sidebar-primary))",
75 | "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
76 | accent: "hsl(var(--sidebar-accent))",
77 | "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
78 | border: "hsl(var(--sidebar-border))",
79 | ring: "hsl(var(--sidebar-ring))"
80 | }
81 | },
82 | animation: {
83 | ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.5s) infinite"
84 | },
85 | keyframes: {
86 | ripple: {
87 | "0%": {},
88 | to: {
89 | transform: "translate(-50%,-50%) scale(1)"
90 | },
91 | "50%": {
92 | transform: "translate(-50%,-50%) scale(1.5)"
93 | }
94 | }
95 | }
96 | }
97 | },
98 | plugins: [animate]
99 | } satisfies Config;
100 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/nextjs.json",
3 | "compilerOptions": {
4 | "plugins": [
5 | {
6 | "name": "next"
7 | }
8 | ],
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": ["./src/*"]
12 | }
13 | },
14 | "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"],
15 | "exclude": ["node_modules"]
16 | }
17 |
--------------------------------------------------------------------------------
/commitlint.config.mjs:
--------------------------------------------------------------------------------
1 | const commitlintConfig = { extends: ["@commitlint/config-conventional"] };
2 |
3 | export default commitlintConfig;
4 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/docs/installation.md
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/docs/overview.md
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IlloStack/calendar/013ef14f8db4949184cb5a146787417c7fc6c4c7/docs/quick-start.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "calendar",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "build": "turbo run build",
7 | "dev": "turbo run dev",
8 | "lint": "turbo run lint",
9 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
10 | "check-types": "turbo run check-types",
11 | "publish-packages": "turbo run build lint && changeset version && changeset publish"
12 | },
13 | "lint-staged": {
14 | "**/*": "prettier --write --ignore-unknown"
15 | },
16 | "devDependencies": {
17 | "@changesets/cli": "^2.28.1",
18 | "@commitlint/cli": "^19.8.0",
19 | "@commitlint/config-conventional": "^19.8.0",
20 | "husky": "^9.1.7",
21 | "lint-staged": "^15.5.1",
22 | "prettier": "^3.5.3",
23 | "prettier-plugin-tailwindcss": "^0.6.11",
24 | "tsup": "^8.4.0",
25 | "turbo": "^2.5.0",
26 | "typescript": "5.8.2"
27 | },
28 | "packageManager": "pnpm@9.0.0",
29 | "engines": {
30 | "node": ">=18"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar-day
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 | - Updated dependencies [ee62f92]
9 | - @illostack/react-calendar-ui@0.0.1-beta.2
10 | - @illostack/react-calendar@0.0.1-beta.2
11 |
12 | ## 0.0.1-beta.1
13 |
14 | ### Patch Changes
15 |
16 | -
17 | - Updated dependencies
18 | - @illostack/react-calendar-ui@0.0.1-beta.1
19 | - @illostack/react-calendar@0.0.1-beta.1
20 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar-day",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar Day",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": "./src/index.ts",
9 | "main": "./dist/index.js",
10 | "module": "./dist/index.mjs",
11 | "types": "./dist/index.d.ts",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/illostack/calendar.git",
15 | "directory": "packages/react-calendar-day"
16 | },
17 | "homepage": "https://github.com/illostack/calendar",
18 | "scripts": {
19 | "build": "tsup src/index.ts --format cjs,esm --dts",
20 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
21 | },
22 | "dependencies": {
23 | "@illostack/react-calendar-ui": "workspace:*",
24 | "@illostack/react-calendar": "workspace:*"
25 | },
26 | "devDependencies": {
27 | "@illostack/eslint-config": "^0.0.3",
28 | "@illostack/typescript-config": "^0.0.3",
29 | "@types/react": "^19.0.0",
30 | "react": "^19.0.0",
31 | "react-dom": "^19.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-active-drag.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | DateFragmentType,
6 | useCalendar,
7 | useDateFragments
8 | } from "@illostack/react-calendar";
9 | import React from "react";
10 |
11 | import { cn } from "@illostack/react-calendar-ui";
12 | import { useDayViewPosition } from "../hooks/use-day-view-position";
13 | import { CalendarDayEventCardContent } from "./calendar-day-event-card-content";
14 |
15 | interface CalendarDayActiveDragContentProps {
16 | startAt: Date;
17 | endAt: Date;
18 | draggingEvent: CalendarEvent;
19 | fragmentType: DateFragmentType;
20 | }
21 |
22 | const CalendarDayActiveDragContent =
23 | React.memo(
24 | ({ startAt, endAt, draggingEvent, fragmentType }) => {
25 | const position = useDayViewPosition(startAt, endAt);
26 |
27 | return (
28 |
29 |
39 |
40 | );
41 | }
42 | );
43 | CalendarDayActiveDragContent.displayName = "CalendarDayActiveDragContent";
44 |
45 | interface CalendarDayActiveDragProps {}
46 |
47 | const CalendarDayActiveDrag = React.memo(() => {
48 | const calendar = useCalendar();
49 | const draggingEvent = calendar.useWatch((s) => s.draggingEvent);
50 | const fragments = useDateFragments(
51 | draggingEvent?.startAt,
52 | draggingEvent?.endAt
53 | );
54 |
55 | if (!draggingEvent) {
56 | return null;
57 | }
58 |
59 | return (
60 |
61 | {fragments.map((fragment) => (
62 |
69 | ))}
70 |
71 | );
72 | });
73 | CalendarDayActiveDrag.displayName = "CalendarDayActiveDrag";
74 |
75 | export { CalendarDayActiveDrag };
76 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-active-resize.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | useCalendar,
6 | useDateFragments
7 | } from "@illostack/react-calendar";
8 | import * as React from "react";
9 |
10 | import { useDayViewPosition } from "../hooks/use-day-view-position";
11 | import { CalendarDayEventCardContent } from "./calendar-day-event-card-content";
12 |
13 | interface CalendarDayActiveResizeContentProps {
14 | startAt: Date;
15 | endAt: Date;
16 | resizingEvent: CalendarEvent;
17 | isFirstFragment?: boolean;
18 | }
19 |
20 | const CalendarDayActiveResizeContent =
21 | React.memo(
22 | ({ startAt, endAt, resizingEvent, isFirstFragment }) => {
23 | const position = useDayViewPosition(startAt, endAt);
24 |
25 | return (
26 |
27 |
28 |
29 | );
30 | }
31 | );
32 | CalendarDayActiveResizeContent.displayName = "CalendarDayActiveResizeContent";
33 |
34 | interface CalendarDayActiveResizeProps {}
35 |
36 | const CalendarDayActiveResize = React.memo(() => {
37 | const calendar = useCalendar();
38 | const resizingEvent = calendar.useWatch((s) => s.resizingEvent);
39 | const fragments = useDateFragments(
40 | resizingEvent?.startAt,
41 | resizingEvent?.endAt
42 | );
43 |
44 | if (!resizingEvent) {
45 | return null;
46 | }
47 |
48 | return (
49 |
50 | {fragments.map((fragment, fragmentIdx) => (
51 |
58 | ))}
59 |
60 | );
61 | });
62 | CalendarDayActiveResize.displayName = "CalendarDayActiveResize";
63 |
64 | export { CalendarDayActiveResize };
65 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-active-section.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarSection,
5 | useCalendar,
6 | useDateFragments
7 | } from "@illostack/react-calendar";
8 | import * as React from "react";
9 | import { useDayViewPosition } from "../hooks/use-day-view-position";
10 |
11 | interface CalendarDayActiveSectionIndicatorProps {
12 | startAt: Date;
13 | endAt: Date;
14 | activeSection: CalendarSection;
15 | }
16 |
17 | const CalendarDayActiveSectionIndicator =
18 | React.memo(({ startAt, endAt }) => {
19 | const position = useDayViewPosition(startAt, endAt);
20 |
21 | return (
22 |
26 | );
27 | });
28 | CalendarDayActiveSectionIndicator.displayName =
29 | "CalendarDayActiveSectionIndicator";
30 |
31 | interface CalendarDayActiveSectionProps {}
32 |
33 | const CalendarDayActiveSection = React.memo(
34 | () => {
35 | const calendar = useCalendar();
36 | const activeSection = calendar.useWatch((s) => s.activeSection);
37 | const fragments = useDateFragments(
38 | activeSection?.startAt,
39 | activeSection?.endAt
40 | );
41 |
42 | if (!activeSection) {
43 | return null;
44 | }
45 |
46 | return (
47 |
48 | {fragments.map((fragment) => (
49 |
55 | ))}
56 |
57 | );
58 | }
59 | );
60 | CalendarDayActiveSection.displayName = "CalendarDayActiveSection";
61 |
62 | export { CalendarDayActiveSection };
63 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-active-selection.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarSection,
5 | useCalendar,
6 | useDateFragments
7 | } from "@illostack/react-calendar";
8 | import React from "react";
9 | import { useDayViewPosition } from "../hooks/use-day-view-position";
10 |
11 | interface CalendarDayActiveSelectionContentProps {
12 | startAt: Date;
13 | endAt: Date;
14 | selection: CalendarSection;
15 | }
16 |
17 | const CalendarDayActiveSelectionContent =
18 | React.memo(({ startAt, endAt }) => {
19 | const position = useDayViewPosition(startAt, endAt);
20 |
21 | return (
22 |
26 | );
27 | });
28 | CalendarDayActiveSelectionContent.displayName =
29 | "CalendarDayActiveSelectionContent";
30 |
31 | interface CalendarDayActiveSelectionProps {}
32 |
33 | const CalendarDayActiveSelection = React.memo(
34 | () => {
35 | const calendar = useCalendar();
36 | const selection = calendar.useWatch((s) => s.selection);
37 | const fragments = useDateFragments(selection?.startAt, selection?.endAt);
38 |
39 | if (!selection) {
40 | return null;
41 | }
42 |
43 | return (
44 |
45 | {fragments.map((fragment) => (
46 |
52 | ))}
53 |
54 | );
55 | }
56 | );
57 | CalendarDayActiveSelection.displayName = "CalendarDayActiveSelection";
58 |
59 | export { CalendarDayActiveSelection };
60 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-axis.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { isSameDay, useCalendar, ViewDate } from "@illostack/react-calendar";
4 | import { cn } from "@illostack/react-calendar-ui";
5 | import React from "react";
6 |
7 | interface CalendarDayAxisProps extends React.HTMLAttributes {
8 | dates: ViewDate[];
9 | }
10 |
11 | const CalendarDayAxis = React.forwardRef(
12 | ({ dates, className, ...props }, ref) => {
13 | const calendar = useCalendar();
14 | const { hours } = calendar.getLayout();
15 | const formatters = calendar.getFormatters();
16 |
17 | return (
18 |
27 | {hours.map((hour) => (
28 |
29 |
30 |
31 |
32 | {formatters.time(new Date(0, 0, 0, hour, 0))}
33 |
34 |
35 |
36 |
37 |
38 |
44 | {dates.map(({ date }, index) => (
45 |
53 | ))}
54 |
55 |
56 |
57 | ))}
58 |
59 | );
60 | }
61 | );
62 | CalendarDayAxis.displayName = "CalendarDayAxis";
63 |
64 | export { CalendarDayAxis };
65 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-event-card-content.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CalendarEvent, useCalendar } from "@illostack/react-calendar";
4 | import {
5 | calendarEventCardVariants,
6 | cn,
7 | VariantProps
8 | } from "@illostack/react-calendar-ui";
9 | import * as React from "react";
10 |
11 | interface CalendarDayEventCardContentProps
12 | extends React.ButtonHTMLAttributes,
13 | Omit, "color"> {
14 | event: CalendarEvent;
15 | }
16 |
17 | const CalendarDayEventCardContent = React.forwardRef<
18 | HTMLDivElement,
19 | CalendarDayEventCardContentProps
20 | >(({ event, className, ...props }, ref) => {
21 | const calendar = useCalendar();
22 | const formatters = calendar.getFormatters();
23 |
24 | return (
25 |
36 |
37 |
38 | {event.summary || "(Untitled)"}
39 |
40 |
41 |
42 |
43 | {formatters.time(event.startAt)} - {formatters.time(event.endAt)}
44 |
45 |
46 |
47 | );
48 | });
49 | CalendarDayEventCardContent.displayName = "CalendarDayEventCardContent";
50 |
51 | export { CalendarDayEventCardContent };
52 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-events.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEventCard,
5 | CalendarEventWithOverlap,
6 | useCalendar,
7 | useDateFragments
8 | } from "@illostack/react-calendar";
9 | import * as React from "react";
10 |
11 | import { useDayViewPosition } from "../hooks/use-day-view-position";
12 |
13 | interface CalendarDayEventProps {
14 | startAt: Date;
15 | endAt: Date;
16 | event: CalendarEventWithOverlap;
17 | }
18 |
19 | const CalendarDayEnvent = React.memo(
20 | ({ startAt, endAt, event }) => {
21 | const position = useDayViewPosition(startAt, endAt);
22 | const calendar = useCalendar();
23 | const Chip = React.useMemo(
24 | () => calendar.getCurrentView().meta.chip as typeof CalendarEventCard,
25 | [calendar]
26 | );
27 |
28 | return (
29 |
38 |
39 |
40 | );
41 | }
42 | );
43 |
44 | interface CalendarDayEnventFragmentsProps {
45 | event: CalendarEventWithOverlap;
46 | }
47 |
48 | const CalendarDayEnventFragments = React.memo(
49 | ({ event }) => {
50 | const fragments = useDateFragments(event.startAt, event.endAt);
51 |
52 | return (
53 |
54 | {fragments.map((fragment) => (
55 |
61 | ))}
62 |
63 | );
64 | }
65 | );
66 |
67 | interface CalendarDayEventsProps {}
68 |
69 | const CalendarDayEvents = React.memo(() => {
70 | const calendar = useCalendar();
71 | const events = calendar.useViewEvents();
72 |
73 | return (
74 |
75 | {events.map((event) => (
76 |
77 | ))}
78 |
79 | );
80 | });
81 | CalendarDayEvents.displayName = "CalendarDayEvents";
82 |
83 | export { CalendarDayEvents };
84 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { isSameDay, useCalendar, ViewDate } from "@illostack/react-calendar";
4 | import { Button, cn } from "@illostack/react-calendar-ui";
5 | import React from "react";
6 |
7 | interface CalendarDayHeaderProps extends React.HTMLAttributes {
8 | dates: ViewDate[];
9 | }
10 |
11 | const CalendarDayHeader = React.forwardRef<
12 | HTMLDivElement,
13 | CalendarDayHeaderProps
14 | >(({ dates, className, ...props }, ref) => {
15 | const calendar = useCalendar();
16 | const formatters = calendar.getFormatters();
17 | const translations = calendar.getTranslations();
18 |
19 | return (
20 |
29 | {dates.map(({ date }, index) => (
30 |
34 |
54 |
55 | ))}
56 |
57 | );
58 | });
59 | CalendarDayHeader.displayName = "CalendarDayHeader";
60 |
61 | export { CalendarDayHeader };
62 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-time-indicator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { isSameDay, useCalendar } from "@illostack/react-calendar";
4 | import { cn } from "@illostack/react-calendar-ui";
5 | import * as React from "react";
6 | import { useDayViewPosition } from "../hooks/use-day-view-position";
7 |
8 | const useCurrentDate = () => {
9 | const [currentDate, setCurrentDate] = React.useState(new Date());
10 | React.useEffect(() => {
11 | const interval = setInterval(() => {
12 | setCurrentDate(new Date());
13 | }, 60000);
14 | return () => clearInterval(interval);
15 | }, []);
16 | return currentDate;
17 | };
18 |
19 | type CalendarDayTimeIndicatorProps = object;
20 |
21 | const CalendarDayTimeIndicator = React.memo(
22 | () => {
23 | const lineRef = React.useRef(null);
24 | const currentDate = useCurrentDate();
25 | const calendar = useCalendar();
26 | const date = calendar.useWatch((s) => s.date);
27 | const formatters = calendar.getFormatters();
28 | const position = useDayViewPosition(currentDate, currentDate);
29 |
30 | const isCurrentDay = React.useMemo(
31 | () => isSameDay(currentDate, date),
32 | [currentDate, date]
33 | );
34 |
35 | return (
36 |
47 | );
48 | }
49 | );
50 |
51 | CalendarDayTimeIndicator.displayName = "CalendarDayTimeIndicator";
52 |
53 | export { CalendarDayTimeIndicator };
54 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/components/calendar-day-view.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | addDays,
6 | createCalendarView,
7 | mergeRefs,
8 | useCalendar
9 | } from "@illostack/react-calendar";
10 | import React from "react";
11 |
12 | import { useCalendarDayDrag } from "../hooks/use-calendar-day-drag";
13 | import { useCalendarDayInteraction } from "../hooks/use-calendar-day-interaction";
14 | import { useCalendarDayResize } from "../hooks/use-calendar-day-resize";
15 | import { useCalendarDaySelection } from "../hooks/use-calendar-day-selection";
16 | import { CalendarDayActiveDrag } from "./calendar-day-active-drag";
17 | import { CalendarDayActiveResize } from "./calendar-day-active-resize";
18 | import { CalendarDayActiveSection } from "./calendar-day-active-section";
19 | import { CalendarDayActiveSelection } from "./calendar-day-active-selection";
20 | import { CalendarDayAxis } from "./calendar-day-axis";
21 | import { CalendarDayContextMenu } from "./calendar-day-context-menu";
22 | import { CalendarDayEventCardContent } from "./calendar-day-event-card-content";
23 | import { CalendarDayEvents } from "./calendar-day-events";
24 | import { CalendarDayHeader } from "./calendar-day-header";
25 | import { CalendarDayTimeIndicator } from "./calendar-day-time-indicator";
26 |
27 | interface CalendarDayViewProps extends React.HTMLAttributes {}
28 |
29 | const CalendarDaysViewTemplate = React.forwardRef<
30 | HTMLDivElement,
31 | CalendarDayViewProps
32 | >((props, ref) => {
33 | const calendar = useCalendar();
34 | const dates = calendar.useWatch((s) => s.dates);
35 | const selectionRef = useCalendarDaySelection();
36 | const resizeRef = useCalendarDayResize();
37 | const interactionRef = useCalendarDayInteraction();
38 | const dragRef = useCalendarDayDrag();
39 |
40 | calendar.useViewAnimation();
41 | calendar.useViewAutoScroll();
42 |
43 | return (
44 |
45 |
46 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | });
70 | CalendarDaysViewTemplate.displayName = "CalendarDaysViewTemplate";
71 |
72 | const VIEW_ID = "day";
73 | type CalendarDayMeta = {
74 | chip: React.ComponentType<{ event: CalendarEvent }>;
75 | };
76 | type CalendarDayConfiguration = {
77 | chip?: React.ComponentType<{ event: CalendarEvent }>;
78 | };
79 |
80 | const view = createCalendarView<
81 | typeof VIEW_ID,
82 | CalendarDayMeta,
83 | CalendarDayConfiguration
84 | >({
85 | id: VIEW_ID,
86 | content: CalendarDaysViewTemplate,
87 | viewDatesFn(date) {
88 | return [{ date: date, isOutside: false }];
89 | },
90 | increaseFn(date) {
91 | return addDays(date, 1);
92 | },
93 | decreaseFn(date) {
94 | return addDays(date, -1);
95 | },
96 | meta: {
97 | chip: CalendarDayEventCardContent
98 | },
99 | configure(props) {
100 | const { chip } = props;
101 |
102 | if (chip) {
103 | this.meta.chip = chip;
104 | }
105 |
106 | return this;
107 | }
108 | });
109 |
110 | export { view as CalendarDayView, CalendarDaysViewTemplate };
111 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/hooks/use-calendar-day-drag.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarDayDrag = () => {
9 | const containerRef = React.useRef(null);
10 | const initOffetRef = React.useRef(null);
11 | const calendar = useCalendar();
12 |
13 | calendar.useEffect(
14 | (s) => s.isDragging,
15 | (state) => {
16 | const container = containerRef.current;
17 |
18 | if (!container) {
19 | return;
20 | }
21 |
22 | if (!state.isDragging) {
23 | const handleDragStart = (event: MouseEvent) => {
24 | const eventId = (event.target as HTMLElement).dataset.eventId;
25 |
26 | if (!eventId) {
27 | return;
28 | }
29 |
30 | const calendarEvent = calendar.getEvent(eventId);
31 |
32 | if (!calendarEvent) {
33 | return;
34 | }
35 |
36 | const { startAt } = computeEventTimeRangeFromPointer(
37 | event,
38 | container,
39 | calendar
40 | );
41 |
42 | initOffetRef.current =
43 | startAt.getTime() - calendarEvent.startAt.getTime();
44 |
45 | calendar.startDragging(calendarEvent);
46 | };
47 |
48 | container.addEventListener("dragstart", handleDragStart);
49 |
50 | return () => {
51 | container.removeEventListener("dragstart", handleDragStart);
52 | };
53 | }
54 |
55 | const handleDrag = (event: MouseEvent) => {
56 | const draggingEvent = calendar.getDraggingEvent();
57 |
58 | if (!draggingEvent) {
59 | return;
60 | }
61 |
62 | const offset = initOffetRef.current;
63 |
64 | if (offset === null) {
65 | return;
66 | }
67 |
68 | const { startAt } = computeEventTimeRangeFromPointer(
69 | event,
70 | container,
71 | calendar
72 | );
73 |
74 | const duration =
75 | draggingEvent.endAt.getTime() - draggingEvent.startAt.getTime();
76 |
77 | const newStartAt = new Date(startAt.getTime() - offset);
78 | const newEndAt = new Date(startAt.getTime() + duration - offset);
79 |
80 | calendar.updateDragging({
81 | ...draggingEvent,
82 | startAt: newStartAt,
83 | endAt: newEndAt
84 | });
85 | };
86 |
87 | const handleDragEnd = (event: MouseEvent) => {
88 | const draggingEvent = calendar.getDraggingEvent();
89 |
90 | if (!draggingEvent) {
91 | return;
92 | }
93 |
94 | calendar.updateEvent(draggingEvent);
95 | calendar.stopDragging();
96 | };
97 |
98 | container.addEventListener("dragover", handleDrag);
99 | container.addEventListener("dragend", handleDragEnd);
100 |
101 | return () => {
102 | container.removeEventListener("dragover", handleDrag);
103 | container.removeEventListener("dragend", handleDragEnd);
104 | };
105 | }
106 | );
107 |
108 | return containerRef;
109 | };
110 |
111 | export { useCalendarDayDrag };
112 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/hooks/use-calendar-day-resize.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarDayResize = () => {
9 | const containerRef = React.useRef(null);
10 | const calendar = useCalendar();
11 |
12 | calendar.useEffect(
13 | (s) => s.resizingEvent,
14 | (s) => {
15 | const container = containerRef.current;
16 |
17 | if (!container) {
18 | return;
19 | }
20 |
21 | if (!s.resizingEvent) {
22 | const handleContainerMouseDown = (event: MouseEvent) => {
23 | const { resizeTopEventId, resizeBottomEventId } = (
24 | event.target as HTMLElement
25 | ).dataset;
26 |
27 | if (resizeTopEventId) {
28 | event.preventDefault();
29 | event.stopPropagation();
30 | const calendarEvent = calendar.getEvent(resizeTopEventId);
31 |
32 | if (!calendarEvent) {
33 | return;
34 | }
35 |
36 | calendar.startResizingTop(calendarEvent);
37 | }
38 |
39 | if (resizeBottomEventId) {
40 | event.preventDefault();
41 | event.stopPropagation();
42 | const calendarEvent = calendar.getEvent(resizeBottomEventId);
43 |
44 | if (!calendarEvent) {
45 | return;
46 | }
47 |
48 | calendar.startResizingBottom(calendarEvent);
49 | }
50 | };
51 |
52 | container.addEventListener("mousedown", handleContainerMouseDown);
53 |
54 | return () => {
55 | container.removeEventListener("mousedown", handleContainerMouseDown);
56 | };
57 | }
58 |
59 | const handleResize = (event: MouseEvent) => {
60 | const resizingEvent = calendar.getResizingEvent();
61 |
62 | if (!resizingEvent) {
63 | return;
64 | }
65 |
66 | const isResizingTop = calendar.getIsResizingTop();
67 |
68 | if (isResizingTop) {
69 | const { startAt } = computeEventTimeRangeFromPointer(
70 | event,
71 | container,
72 | calendar
73 | );
74 |
75 | if (startAt >= resizingEvent.endAt) {
76 | return;
77 | }
78 |
79 | calendar.updateResizing({
80 | ...resizingEvent,
81 | startAt
82 | });
83 | }
84 |
85 | const isResizingBottom = calendar.getIsResizingBottom();
86 |
87 | if (isResizingBottom) {
88 | const { endAt } = computeEventTimeRangeFromPointer(
89 | event,
90 | container,
91 | calendar
92 | );
93 |
94 | if (endAt <= resizingEvent.startAt) {
95 | return;
96 | }
97 |
98 | calendar.updateResizing({
99 | ...resizingEvent,
100 | endAt
101 | });
102 | }
103 | };
104 |
105 | const handleStopResize = () => {
106 | const resizingEvent = calendar.getResizingEvent();
107 |
108 | if (!resizingEvent) {
109 | return;
110 | }
111 |
112 | const originalEvent = calendar.getEvent(resizingEvent.id);
113 |
114 | if (!originalEvent) {
115 | return;
116 | }
117 |
118 | if (
119 | resizingEvent.startAt.getTime() === originalEvent.startAt.getTime() &&
120 | resizingEvent.endAt.getTime() === originalEvent.endAt.getTime()
121 | ) {
122 | calendar.stopResizing();
123 | return;
124 | }
125 |
126 | calendar.updateEvent(resizingEvent);
127 | calendar.stopResizing();
128 | };
129 |
130 | window.addEventListener("mousemove", handleResize);
131 | window.addEventListener("mouseup", handleStopResize);
132 |
133 | return () => {
134 | window.removeEventListener("mousemove", handleResize);
135 | window.removeEventListener("mouseup", handleStopResize);
136 | };
137 | }
138 | );
139 |
140 | return containerRef;
141 | };
142 |
143 | export { useCalendarDayResize };
144 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/hooks/use-calendar-day-selection.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { addMinutes, useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarDaySelection = () => {
9 | const containerRef = React.useRef(null);
10 | const initSelectionRef = React.useRef(null);
11 | const calendar = useCalendar();
12 |
13 | calendar.useEffect(
14 | (s) => s.isSelecting,
15 | (state) => {
16 | const container = containerRef.current;
17 |
18 | if (!container) {
19 | return;
20 | }
21 |
22 | if (!state.isSelecting) {
23 | const handleContainerMouseDown = (e: MouseEvent) => {
24 | if (e.target !== container) {
25 | return;
26 | }
27 |
28 | const isDragging = calendar.getIsDragging();
29 | const isResizingTop = calendar.getIsResizingTop();
30 | const isResizingBottom = calendar.getIsResizingBottom();
31 |
32 | if (isDragging || isResizingTop || isResizingBottom) {
33 | return;
34 | }
35 |
36 | if (e.button !== 0) {
37 | return;
38 | }
39 |
40 | calendar.clearActiveSection();
41 | calendar.startSelection(null);
42 | };
43 |
44 | container.addEventListener("mousedown", handleContainerMouseDown);
45 |
46 | return () => {
47 | container.removeEventListener("mousedown", handleContainerMouseDown);
48 | };
49 | }
50 |
51 | const handleContainerMouseUp = () => {
52 | const isSelecting = calendar.getIsSelecting();
53 |
54 | if (!isSelecting) {
55 | return;
56 | }
57 |
58 | const selection = calendar.getSelection();
59 |
60 | if (
61 | !selection ||
62 | selection.startAt.getTime() === selection.endAt.getTime()
63 | ) {
64 | calendar.stopSelection();
65 | initSelectionRef.current = null;
66 | return;
67 | }
68 |
69 | calendar.openCreationForm(selection, () => {
70 | initSelectionRef.current = null;
71 | calendar.stopSelection();
72 | });
73 | };
74 |
75 | const handleContainerMouseMove = (event: MouseEvent) => {
76 | const isDragging = calendar.getIsDragging();
77 | const isResizingTop = calendar.getIsResizingTop();
78 | const isResizingBottom = calendar.getIsResizingBottom();
79 | const isSelecting = calendar.getIsSelecting();
80 |
81 | if (isDragging || isResizingTop || isResizingBottom || !isSelecting) {
82 | return;
83 | }
84 |
85 | const { startAt } = computeEventTimeRangeFromPointer(
86 | event,
87 | container,
88 | calendar
89 | );
90 |
91 | const selection = calendar.getSelection();
92 |
93 | if (!selection) {
94 | initSelectionRef.current = startAt;
95 | calendar.startSelection({ startAt, endAt: startAt });
96 |
97 | return;
98 | }
99 |
100 | const initDate = initSelectionRef.current;
101 |
102 | if (!initDate) {
103 | return;
104 | }
105 |
106 | const { minutesPerRow } = calendar.getLayout();
107 |
108 | if (startAt < initDate) {
109 | const endAt = new Date(initDate);
110 | endAt.setMinutes(endAt.getMinutes() + minutesPerRow);
111 | calendar.updateSelection({ startAt, endAt });
112 | } else if (startAt > initDate) {
113 | const endAt = addMinutes(startAt, minutesPerRow);
114 | calendar.updateSelection({ startAt: initDate, endAt });
115 | } else {
116 | const endAt = addMinutes(startAt, minutesPerRow);
117 | calendar.updateSelection({ startAt, endAt });
118 | }
119 | };
120 |
121 | container.addEventListener("mousemove", handleContainerMouseMove);
122 | container.addEventListener("mouseup", handleContainerMouseUp);
123 |
124 | return () => {
125 | container.removeEventListener("mousemove", handleContainerMouseMove);
126 | container.removeEventListener("mouseup", handleContainerMouseUp);
127 | };
128 | }
129 | );
130 |
131 | return containerRef;
132 | };
133 |
134 | export { useCalendarDaySelection };
135 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/hooks/use-day-view-position.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computePositionFromTime } from "../lib/utils";
7 |
8 | const useDayViewPosition = (startAt: Date, endAt: Date) => {
9 | const calendar = useCalendar();
10 | const view = calendar.useWatch((s) =>
11 | s.currentView.compositeId ? s.currentView.compositeId() : s.currentView.id
12 | );
13 |
14 | return React.useMemo(() => {
15 | return computePositionFromTime(startAt, endAt, calendar);
16 | }, [startAt, endAt, calendar, view]);
17 | };
18 |
19 | export { useDayViewPosition };
20 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components/calendar-day-active-drag";
2 | export * from "./components/calendar-day-axis";
3 | export * from "./components/calendar-day-event-card-content";
4 | export * from "./components/calendar-day-events";
5 | export * from "./components/calendar-day-header";
6 | export * from "./components/calendar-day-view";
7 |
8 | export * from "./hooks/use-calendar-day-interaction";
9 | export * from "./hooks/use-calendar-day-resize";
10 | export * from "./hooks/use-calendar-day-selection";
11 |
12 | export * from "./lib/utils";
13 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | addMinutes,
3 | CalendarApi,
4 | CalendarProvidedEvent,
5 | CalendarView,
6 | CalendarViewConfiguration,
7 | CalendarViewId,
8 | CalendarViewMeta
9 | } from "@illostack/react-calendar";
10 |
11 | const minMax = (value: number, min: number, max: number) =>
12 | Math.min(Math.max(value, min), max);
13 |
14 | const getTimeFromRow = (
15 | date: Date,
16 | row: number,
17 | calendar: CalendarApi<
18 | CalendarProvidedEvent,
19 | CalendarView[]
20 | >
21 | ) => {
22 | const { rowsPerHour, minutesPerRow, startHour } = calendar.getLayout();
23 |
24 | const hour = Math.floor(row / rowsPerHour);
25 | const minutes = (row % rowsPerHour) * minutesPerRow;
26 | const newDate = new Date(date);
27 | newDate.setHours(startHour + hour);
28 | newDate.setMinutes(minutes);
29 | newDate.setSeconds(0);
30 | newDate.setMilliseconds(0);
31 |
32 | return newDate;
33 | };
34 |
35 | const getRowFromTime = (
36 | date: Date,
37 | calendar: CalendarApi<
38 | CalendarProvidedEvent,
39 | CalendarView[]
40 | >
41 | ) => {
42 | const { rowsPerHour, minutesPerRow, startHour } = calendar.getLayout();
43 |
44 | return Math.floor(
45 | (date.getHours() - startHour) * rowsPerHour +
46 | (date.getMinutes() + 1) / minutesPerRow
47 | );
48 | };
49 |
50 | const computeEventTimeRangeFromPointer = (
51 | e: MouseEvent,
52 | container: HTMLDivElement,
53 | calendar: CalendarApi<
54 | CalendarProvidedEvent,
55 | CalendarView[]
56 | >
57 | ) => {
58 | const { clientX, clientY } = e;
59 | const { width, height, top, left } = container.getBoundingClientRect();
60 | const pointerY = clientY - top;
61 | const pointerX = clientX - left;
62 |
63 | const { minutesPerRow, totalRows } = calendar.getLayout();
64 |
65 | const row = minMax(Math.floor((pointerY / height) * totalRows), 0, totalRows);
66 |
67 | const colDate = getDateFromXPosition(pointerX, width, calendar);
68 | const startAt = getTimeFromRow(colDate, row, calendar);
69 | const endAt = addMinutes(startAt, minutesPerRow);
70 |
71 | return { startAt, endAt };
72 | };
73 |
74 | const getDateFromXPosition = (
75 | positionX: number,
76 | containerWidth: number,
77 | calendar: CalendarApi<
78 | CalendarProvidedEvent,
79 | CalendarView[]
80 | >
81 | ) => {
82 | const dates = calendar.getDates();
83 |
84 | const index = Math.floor((positionX / containerWidth) * dates.length);
85 |
86 | return dates[index]!.date;
87 | };
88 |
89 | const computePositionFromTime = (
90 | startAt: Date,
91 | endAt: Date,
92 | calendar: CalendarApi<
93 | CalendarProvidedEvent,
94 | CalendarView[]
95 | >
96 | ) => {
97 | const { totalRows, minutesPerRow } = calendar.getLayout();
98 | const dates = calendar.getDates();
99 | const totalDates = dates.length;
100 | const startRow = getRowFromTime(startAt, calendar);
101 | const duration = endAt.getTime() - startAt.getTime();
102 | const endRow = startRow + Math.floor(duration / (minutesPerRow * 60 * 1000));
103 | const dateIndex = dates.findIndex(
104 | (date) => date.date.toDateString() === startAt.toDateString()
105 | );
106 |
107 | return {
108 | top: `${((startRow / totalRows) * 100).toFixed(2)}%`,
109 | height: `${(((endRow - startRow) / totalRows) * 100).toFixed(2)}%`,
110 | left: `${((dateIndex / totalDates) * 100).toFixed(2)}%`,
111 | width: `${((1 / totalDates) * 100).toFixed(2)}%`
112 | };
113 | };
114 |
115 | export {
116 | computeEventTimeRangeFromPointer,
117 | computePositionFromTime,
118 | getDateFromXPosition
119 | };
120 |
--------------------------------------------------------------------------------
/packages/react-calendar-day/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar-month
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 | - Updated dependencies [ee62f92]
9 | - @illostack/react-calendar-ui@0.0.1-beta.2
10 | - @illostack/react-calendar@0.0.1-beta.2
11 |
12 | ## 0.0.1-beta.1
13 |
14 | ### Patch Changes
15 |
16 | -
17 | - Updated dependencies
18 | - @illostack/react-calendar-ui@0.0.1-beta.1
19 | - @illostack/react-calendar@0.0.1-beta.1
20 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar-month",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar Month",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": "./src/index.ts",
9 | "main": "./dist/index.js",
10 | "module": "./dist/index.mjs",
11 | "types": "./dist/index.d.ts",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/illostack/calendar.git",
15 | "directory": "packages/react-calendar-month"
16 | },
17 | "homepage": "https://github.com/illostack/calendar",
18 | "scripts": {
19 | "build": "tsup src/index.ts --format cjs,esm --dts",
20 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
21 | },
22 | "dependencies": {
23 | "@illostack/react-calendar-ui": "workspace:*",
24 | "@illostack/react-calendar": "workspace:*"
25 | },
26 | "devDependencies": {
27 | "@illostack/eslint-config": "^0.0.3",
28 | "@illostack/typescript-config": "^0.0.3",
29 | "@types/react": "^19.0.0",
30 | "react": "^19.0.0",
31 | "react-dom": "^19.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-active-drag.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | DateFragmentType,
6 | useCalendar,
7 | useDateFragments
8 | } from "@illostack/react-calendar";
9 | import React from "react";
10 |
11 | import { cn } from "@illostack/react-calendar-ui";
12 | import { useMonthViewPosition } from "../hooks/use-month-view-position";
13 | import { CalendarMonthEventCardContent } from "./calendar-month-event-card-content";
14 |
15 | interface CalendarMonthActiveDragContentProps {
16 | date: Date;
17 | draggingEvent: CalendarEvent;
18 | fragmentType: DateFragmentType;
19 | }
20 |
21 | const CalendarMonthActiveDragContent =
22 | React.memo(
23 | ({ date, draggingEvent, fragmentType }) => {
24 | const position = useMonthViewPosition(date);
25 |
26 | return (
27 |
34 |
44 |
45 | );
46 | }
47 | );
48 | CalendarMonthActiveDragContent.displayName = "CalendarMonthActiveDragContent";
49 |
50 | interface CalendarMonthActiveDragProps {}
51 |
52 | const CalendarMonthActiveDrag = React.memo(() => {
53 | const calendar = useCalendar();
54 | const draggingEvent = calendar.useWatch((s) => s.draggingEvent);
55 | const fragments = useDateFragments(
56 | draggingEvent?.startAt,
57 | draggingEvent?.endAt
58 | );
59 |
60 | if (!draggingEvent) {
61 | return null;
62 | }
63 |
64 | return (
65 |
66 | {fragments.map((fragment) => (
67 |
73 | ))}
74 |
75 | );
76 | });
77 | CalendarMonthActiveDrag.displayName = "CalendarMonthActiveDrag";
78 |
79 | export { CalendarMonthActiveDrag };
80 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-active-resize.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEvent,
5 | DateFragmentType,
6 | useCalendar,
7 | useDateFragments
8 | } from "@illostack/react-calendar";
9 | import React from "react";
10 |
11 | import { cn } from "@illostack/react-calendar-ui";
12 | import { useMonthViewPosition } from "../hooks/use-month-view-position";
13 | import { CalendarMonthEventCardContent } from "./calendar-month-event-card-content";
14 |
15 | interface CalendarMonthActiveResizeContentProps {
16 | date: Date;
17 | resizingEvent: CalendarEvent;
18 | fragmentType: DateFragmentType;
19 | }
20 |
21 | const CalendarMonthActiveResizeContent =
22 | React.memo(
23 | ({ date, resizingEvent, fragmentType }) => {
24 | const position = useMonthViewPosition(date);
25 |
26 | return (
27 |
31 |
41 |
42 | );
43 | }
44 | );
45 | CalendarMonthActiveResizeContent.displayName =
46 | "CalendarMonthActiveResizeContent";
47 |
48 | interface CalendarMonthActiveResizeProps {}
49 |
50 | const CalendarMonthActiveResize = React.memo(
51 | () => {
52 | const calendar = useCalendar();
53 | const resizingEvent = calendar.useWatch((s) => s.resizingEvent);
54 | const fragments = useDateFragments(
55 | resizingEvent?.startAt,
56 | resizingEvent?.endAt
57 | );
58 |
59 | if (!resizingEvent) {
60 | return null;
61 | }
62 |
63 | return (
64 |
65 | {fragments.map((fragment) => (
66 |
72 | ))}
73 |
74 | );
75 | }
76 | );
77 | CalendarMonthActiveResize.displayName = "CalendarMonthActiveResize";
78 |
79 | export { CalendarMonthActiveResize };
80 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-active-section.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarSection,
5 | useCalendar,
6 | useDateFragments
7 | } from "@illostack/react-calendar";
8 | import * as React from "react";
9 |
10 | import { useMonthViewPosition } from "../hooks/use-month-view-position";
11 |
12 | interface CalendarMonthActiveSectionIndicatorProps {
13 | date: Date;
14 | activeSection: CalendarSection;
15 | }
16 |
17 | const CalendarMonthActiveSectionIndicator =
18 | React.memo(({ date }) => {
19 | const position = useMonthViewPosition(date);
20 |
21 | return (
22 |
26 | );
27 | });
28 | CalendarMonthActiveSectionIndicator.displayName =
29 | "CalendarMonthActiveSectionIndicator";
30 |
31 | interface CalendarMonthActiveSectionProps {}
32 |
33 | const CalendarMonthActiveSection = React.memo(
34 | () => {
35 | const calendar = useCalendar();
36 | const activeSection = calendar.useWatch((s) => s.activeSection);
37 | const fragments = useDateFragments(
38 | activeSection?.startAt,
39 | activeSection?.endAt
40 | );
41 |
42 | if (!activeSection) {
43 | return null;
44 | }
45 |
46 | return (
47 |
48 | {fragments.map((fragment) => (
49 |
54 | ))}
55 |
56 | );
57 | }
58 | );
59 | CalendarMonthActiveSection.displayName = "CalendarMonthActiveSection";
60 |
61 | export { CalendarMonthActiveSection };
62 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-active-selection.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarSection,
5 | useCalendar,
6 | useDateFragments
7 | } from "@illostack/react-calendar";
8 | import React from "react";
9 | import { useMonthViewPosition } from "../hooks/use-month-view-position";
10 |
11 | interface CalendarMonthActiveSelectionContentProps {
12 | date: Date;
13 | selection: CalendarSection;
14 | }
15 |
16 | const CalendarMonthActiveSelectionContent =
17 | React.memo(({ date }) => {
18 | const position = useMonthViewPosition(date);
19 |
20 | return (
21 |
25 | );
26 | });
27 | CalendarMonthActiveSelectionContent.displayName =
28 | "CalendarMonthActiveSelectionContent";
29 |
30 | interface CalendarMonthActiveSelectionProps {}
31 |
32 | const CalendarMonthActiveSelection =
33 | React.memo(() => {
34 | const calendar = useCalendar();
35 | const selection = calendar.useWatch((s) => s.selection);
36 | const fragments = useDateFragments(selection?.startAt, selection?.endAt);
37 |
38 | if (!selection) {
39 | return null;
40 | }
41 |
42 | return (
43 |
44 | {fragments.map((fragment) => (
45 |
50 | ))}
51 |
52 | );
53 | });
54 | CalendarMonthActiveSelection.displayName = "CalendarMonthActiveSelection";
55 |
56 | export { CalendarMonthActiveSelection };
57 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-day.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CalendarEventCard,
5 | isSameDay,
6 | useCalendar
7 | } from "@illostack/react-calendar";
8 | import { Button, cn } from "@illostack/react-calendar-ui";
9 | import * as React from "react";
10 |
11 | import { CalendarMonthEventCardContent } from "./calendar-month-event-card-content";
12 |
13 | interface CalendarMonthViewEventsPanelProps {
14 | date: Date;
15 | }
16 |
17 | const CalendarMonthViewEventsPanel =
18 | React.memo(({ date }) => {
19 | const calendar = useCalendar();
20 | const events = calendar.useDayEvents(date);
21 | const firtsEvents = React.useMemo(() => events.slice(0, 3), [events]);
22 | const otherEventsLength = React.useMemo(() => events.length - 3, [events]);
23 |
24 | return (
25 |
26 | {firtsEvents.map((event) => (
27 |
32 |
33 |
34 | ))}
35 | {events.length > 3 && (
36 |
45 | )}
46 |
47 | );
48 | });
49 | CalendarMonthViewEventsPanel.displayName = "CalendarMonthViewEventsPanel";
50 |
51 | interface CalendarMonthDayButtonProps {
52 | date: Date;
53 | }
54 |
55 | const CalendarMonthDayButton = React.memo(
56 | ({ date }) => {
57 | const calendar = useCalendar();
58 | const formatters = calendar.getFormatters();
59 |
60 | const isCurrentDay = React.useMemo(
61 | () => isSameDay(date, new Date()),
62 | [date]
63 | );
64 |
65 | return (
66 |
80 | );
81 | }
82 | );
83 | CalendarMonthDayButton.displayName = "CalendarMonthDayButton";
84 |
85 | interface CalendarMonthDayProps {
86 | date: Date;
87 | isOutside: boolean;
88 | }
89 |
90 | const CalendarMonthDay: React.FC = React.memo(
91 | ({ date, isOutside }) => {
92 | return (
93 |
99 |
100 |
101 |
102 |
107 |
108 | );
109 | }
110 | );
111 | CalendarMonthDay.displayName = "CalendarMonthDay";
112 |
113 | export { CalendarMonthDay };
114 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-event-card-content.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CalendarEvent, useCalendar } from "@illostack/react-calendar";
4 | import {
5 | calendarEventCardVariants,
6 | cn,
7 | VariantProps
8 | } from "@illostack/react-calendar-ui";
9 | import * as React from "react";
10 |
11 | interface CalendarMonthEventCardContentProps
12 | extends React.ButtonHTMLAttributes,
13 | Omit, "color"> {
14 | event: CalendarEvent;
15 | }
16 |
17 | const CalendarMonthEventCardContent = React.forwardRef<
18 | HTMLDivElement,
19 | CalendarMonthEventCardContentProps
20 | >(({ event, className, ...props }, ref) => {
21 | const calendar = useCalendar();
22 | const formatters = calendar.getFormatters();
23 |
24 | return (
25 |
36 |
37 |
38 | {event.summary || "(Untitled)"}
39 |
40 |
41 |
42 |
43 | {formatters.time(event.startAt)} - {formatters.time(event.endAt)}
44 |
45 |
46 |
47 | );
48 | });
49 | CalendarMonthEventCardContent.displayName = "CalendarMonthEventCardContent";
50 |
51 | export { CalendarMonthEventCardContent };
52 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 |
5 | import { useCalendar } from "@illostack/react-calendar";
6 | import { cn } from "@illostack/react-calendar-ui";
7 |
8 | interface CalendarMonthHeaderProps
9 | extends React.HTMLAttributes {
10 | className?: string;
11 | }
12 |
13 | const CalendarMonthHeader = React.forwardRef<
14 | HTMLDivElement,
15 | CalendarMonthHeaderProps
16 | >(({ className, ...props }, ref) => {
17 | const calendar = useCalendar();
18 | const dates = React.useMemo(() => Array.from({ length: 7 }, (_, i) => i), []);
19 | const translations = calendar.getTranslations();
20 | const weekStartsOn = calendar.getWeekStartsOn();
21 |
22 | return (
23 |
31 | {dates.map((_, i) => (
32 |
33 |
34 | {translations.calendar.days[((weekStartsOn + i) % 7) as 0]}
35 |
36 |
37 | ))}
38 |
39 | );
40 | });
41 | CalendarMonthHeader.displayName = "CalendarMonthHeader";
42 |
43 | export { CalendarMonthHeader };
44 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/components/calendar-month-view.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | addMonths,
5 | createCalendarView,
6 | getMonthDays,
7 | mergeRefs,
8 | useCalendar
9 | } from "@illostack/react-calendar";
10 | import { cn } from "@illostack/react-calendar-ui";
11 | import * as React from "react";
12 |
13 | import { useCalendarMonthDrag } from "../hooks/use-calendar-month-drag";
14 | import { useCalendarMonthInteraction } from "../hooks/use-calendar-month-interaction";
15 | import { useCalendarMonthResize } from "../hooks/use-calendar-month-resize";
16 | import { useCalendarMonthSelection } from "../hooks/use-calendar-month-selection";
17 | import { CalendarMonthActiveDrag } from "./calendar-month-active-drag";
18 | import { CalendarMonthActiveResize } from "./calendar-month-active-resize";
19 | import { CalendarMonthActiveSection } from "./calendar-month-active-section";
20 | import { CalendarMonthActiveSelection } from "./calendar-month-active-selection";
21 | import { CalendarMonthContextMenu } from "./calendar-month-context-menu";
22 | import { CalendarMonthDay } from "./calendar-month-day";
23 | import { CalendarMonthHeader } from "./calendar-month-header";
24 |
25 | interface CalendarMonthViewProps extends React.HTMLAttributes {
26 | className?: string;
27 | }
28 |
29 | const CalendarMonthView = React.forwardRef<
30 | HTMLDivElement,
31 | CalendarMonthViewProps
32 | >(({ className, ...props }, ref) => {
33 | const calendar = useCalendar();
34 | const dates = calendar.useWatch((s) => s.dates);
35 | const selectionRef = useCalendarMonthSelection();
36 | const resizeRef = useCalendarMonthResize();
37 | const interactionRef = useCalendarMonthInteraction();
38 | const dragRef = useCalendarMonthDrag();
39 |
40 | calendar.useViewAnimation();
41 |
42 | return (
43 |
48 |
49 |
50 |
51 |
58 |
59 |
60 | {dates.map((day, index) => {
61 | return (
62 |
67 | );
68 | })}
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | });
77 | CalendarMonthView.displayName = "CalendarMonthView";
78 |
79 | const VIEW_ID = "month";
80 | type CalendarMonthMeta = Record;
81 | type CalendarMonthConfiguration = Record;
82 |
83 | const view = createCalendarView<
84 | typeof VIEW_ID,
85 | CalendarMonthMeta,
86 | CalendarMonthConfiguration
87 | >({
88 | id: VIEW_ID,
89 | content: CalendarMonthView,
90 | viewDatesFn(date, weekStartsOn) {
91 | return getMonthDays(date, true, weekStartsOn);
92 | },
93 | increaseFn(date) {
94 | return addMonths(date, 1);
95 | },
96 | decreaseFn(date) {
97 | return addMonths(date, -1);
98 | },
99 | meta: {},
100 | configure() {
101 | return this!;
102 | }
103 | });
104 |
105 | export { view as CalendarMonthView };
106 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/hooks/use-calendar-month-drag.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarMonthDrag = () => {
9 | const containerRef = React.useRef(null);
10 | const initOffetRef = React.useRef(null);
11 | const calendar = useCalendar();
12 |
13 | calendar.useEffect(
14 | (s) => s.isDragging,
15 | (state) => {
16 | const container = containerRef.current;
17 |
18 | if (!container) {
19 | return;
20 | }
21 |
22 | if (!state.isDragging) {
23 | const handleDragStart = (event: MouseEvent) => {
24 | const eventId = (event.target as HTMLElement).dataset.eventId;
25 |
26 | if (!eventId) {
27 | return;
28 | }
29 |
30 | const calendarEvent = calendar.getEvent(eventId);
31 |
32 | if (!calendarEvent) {
33 | return;
34 | }
35 |
36 | const { startAt } = computeEventTimeRangeFromPointer(
37 | event,
38 | container,
39 | calendar
40 | );
41 |
42 | initOffetRef.current =
43 | startAt.getTime() - calendarEvent.startAt.getTime();
44 |
45 | calendar.startDragging(calendarEvent);
46 | };
47 |
48 | container.addEventListener("dragstart", handleDragStart);
49 |
50 | return () => {
51 | container.removeEventListener("dragstart", handleDragStart);
52 | };
53 | }
54 |
55 | const handleDrag = (event: MouseEvent) => {
56 | const draggingEvent = calendar.getDraggingEvent();
57 |
58 | if (!draggingEvent) {
59 | return;
60 | }
61 |
62 | const offset = initOffetRef.current;
63 |
64 | if (offset === null) {
65 | return;
66 | }
67 |
68 | const { startAt } = computeEventTimeRangeFromPointer(
69 | event,
70 | container,
71 | calendar
72 | );
73 |
74 | const duration =
75 | draggingEvent.endAt.getTime() - draggingEvent.startAt.getTime();
76 |
77 | const newStartAt = new Date(startAt.getTime() - offset);
78 | const newEndAt = new Date(startAt.getTime() + duration - offset);
79 |
80 | calendar.updateDragging({
81 | ...draggingEvent,
82 | startAt: newStartAt,
83 | endAt: newEndAt
84 | });
85 | };
86 |
87 | const handleDragEnd = (event: MouseEvent) => {
88 | const draggingEvent = calendar.getDraggingEvent();
89 |
90 | if (!draggingEvent) {
91 | return;
92 | }
93 |
94 | calendar.updateEvent(draggingEvent);
95 | calendar.stopDragging();
96 | };
97 |
98 | container.addEventListener("dragover", handleDrag);
99 | container.addEventListener("dragend", handleDragEnd);
100 |
101 | return () => {
102 | container.removeEventListener("dragover", handleDrag);
103 | container.removeEventListener("dragend", handleDragEnd);
104 | };
105 | }
106 | );
107 |
108 | return containerRef;
109 | };
110 |
111 | export { useCalendarMonthDrag };
112 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/hooks/use-calendar-month-resize.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarMonthResize = () => {
9 | const containerRef = React.useRef(null);
10 | const calendar = useCalendar();
11 |
12 | calendar.useEffect(
13 | (s) => s.resizingEvent,
14 | (s) => {
15 | const container = containerRef.current;
16 |
17 | if (!container) {
18 | return;
19 | }
20 |
21 | if (!s.resizingEvent) {
22 | const handleContainerMouseDown = (event: MouseEvent) => {
23 | const { resizeTopEventId, resizeBottomEventId } = (
24 | event.target as HTMLElement
25 | ).dataset;
26 |
27 | if (resizeTopEventId) {
28 | event.preventDefault();
29 | event.stopPropagation();
30 | const calendarEvent = calendar.getEvent(resizeTopEventId);
31 |
32 | if (!calendarEvent) {
33 | return;
34 | }
35 |
36 | calendar.startResizingTop(calendarEvent);
37 | }
38 |
39 | if (resizeBottomEventId) {
40 | event.preventDefault();
41 | event.stopPropagation();
42 | const calendarEvent = calendar.getEvent(resizeBottomEventId);
43 |
44 | if (!calendarEvent) {
45 | return;
46 | }
47 |
48 | calendar.startResizingBottom(calendarEvent);
49 | }
50 | };
51 |
52 | container.addEventListener("mousedown", handleContainerMouseDown);
53 |
54 | return () => {
55 | container.removeEventListener("mousedown", handleContainerMouseDown);
56 | };
57 | }
58 |
59 | const handleResize = (event: MouseEvent) => {
60 | const resizingEvent = calendar.getResizingEvent();
61 |
62 | if (!resizingEvent) {
63 | return;
64 | }
65 |
66 | const isResizingTop = calendar.getIsResizingTop();
67 |
68 | if (isResizingTop) {
69 | const { startAt } = computeEventTimeRangeFromPointer(
70 | event,
71 | container,
72 | calendar
73 | );
74 |
75 | if (startAt >= resizingEvent.endAt) {
76 | return;
77 | }
78 |
79 | calendar.updateResizing({
80 | ...resizingEvent,
81 | startAt
82 | });
83 | }
84 |
85 | const isResizingBottom = calendar.getIsResizingBottom();
86 |
87 | if (isResizingBottom) {
88 | const { endAt } = computeEventTimeRangeFromPointer(
89 | event,
90 | container,
91 | calendar
92 | );
93 |
94 | if (endAt <= resizingEvent.startAt) {
95 | return;
96 | }
97 |
98 | calendar.updateResizing({
99 | ...resizingEvent,
100 | endAt
101 | });
102 | }
103 | };
104 |
105 | const handleStopResize = () => {
106 | const resizingEvent = calendar.getResizingEvent();
107 |
108 | if (!resizingEvent) {
109 | return;
110 | }
111 |
112 | const originalEvent = calendar.getEvent(resizingEvent.id);
113 |
114 | if (!originalEvent) {
115 | return;
116 | }
117 |
118 | if (
119 | resizingEvent.startAt.getTime() === originalEvent.startAt.getTime() &&
120 | resizingEvent.endAt.getTime() === originalEvent.endAt.getTime()
121 | ) {
122 | calendar.stopResizing();
123 | return;
124 | }
125 |
126 | calendar.updateEvent(resizingEvent);
127 | calendar.stopResizing();
128 | };
129 |
130 | window.addEventListener("mousemove", handleResize);
131 | window.addEventListener("mouseup", handleStopResize);
132 |
133 | return () => {
134 | window.removeEventListener("mousemove", handleResize);
135 | window.removeEventListener("mouseup", handleStopResize);
136 | };
137 | }
138 | );
139 |
140 | return containerRef;
141 | };
142 |
143 | export { useCalendarMonthResize };
144 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/hooks/use-calendar-month-selection.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { addDays, useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computeEventTimeRangeFromPointer } from "../lib/utils";
7 |
8 | const useCalendarMonthSelection = () => {
9 | const containerRef = React.useRef(null);
10 | const initSelectionRef = React.useRef(null);
11 | const calendar = useCalendar();
12 |
13 | calendar.useEffect(
14 | (s) => s.isSelecting,
15 | (state) => {
16 | const container = containerRef.current;
17 |
18 | if (!container) {
19 | return;
20 | }
21 |
22 | if (!state.isSelecting) {
23 | const handleContainerMouseDown = (e: MouseEvent) => {
24 | if (e.target !== container) {
25 | return;
26 | }
27 |
28 | const isDragging = calendar.getIsDragging();
29 | const isResizingTop = calendar.getIsResizingTop();
30 | const isResizingBottom = calendar.getIsResizingBottom();
31 |
32 | if (isDragging || isResizingTop || isResizingBottom) {
33 | return;
34 | }
35 |
36 | if (e.button !== 0) {
37 | return;
38 | }
39 |
40 | calendar.clearActiveSection();
41 | calendar.startSelection(null);
42 | };
43 |
44 | container.addEventListener("mousedown", handleContainerMouseDown);
45 |
46 | return () => {
47 | container.removeEventListener("mousedown", handleContainerMouseDown);
48 | };
49 | }
50 |
51 | const handleContainerMouseUp = () => {
52 | const isSelecting = calendar.getIsSelecting();
53 |
54 | if (!isSelecting) {
55 | return;
56 | }
57 |
58 | const selection = calendar.getSelection();
59 |
60 | if (
61 | !selection ||
62 | selection.startAt.getTime() === selection.endAt.getTime()
63 | ) {
64 | calendar.stopSelection();
65 | initSelectionRef.current = null;
66 | return;
67 | }
68 |
69 | calendar.openCreationForm(selection, () => {
70 | initSelectionRef.current = null;
71 | calendar.stopSelection();
72 | });
73 | };
74 |
75 | const handleContainerMouseMove = (event: MouseEvent) => {
76 | const isDragging = calendar.getIsDragging();
77 | const isResizingTop = calendar.getIsResizingTop();
78 | const isResizingBottom = calendar.getIsResizingBottom();
79 | const isSelecting = calendar.getIsSelecting();
80 |
81 | if (isDragging || isResizingTop || isResizingBottom || !isSelecting) {
82 | return;
83 | }
84 |
85 | const { startAt, endAt } = computeEventTimeRangeFromPointer(
86 | event,
87 | container,
88 | calendar
89 | );
90 |
91 | const selection = calendar.getSelection();
92 |
93 | if (!selection) {
94 | initSelectionRef.current = startAt;
95 | calendar.startSelection({ startAt, endAt });
96 |
97 | return;
98 | }
99 |
100 | const initDate = initSelectionRef.current;
101 |
102 | if (!initDate) {
103 | return;
104 | }
105 |
106 | if (startAt < initDate) {
107 | const endAt = addDays(new Date(initDate), 1);
108 | calendar.updateSelection({ startAt, endAt });
109 | } else if (startAt > initDate) {
110 | calendar.updateSelection({ startAt: initDate, endAt });
111 | } else {
112 | calendar.updateSelection({ startAt, endAt });
113 | }
114 | };
115 |
116 | container.addEventListener("mousemove", handleContainerMouseMove);
117 | container.addEventListener("mouseup", handleContainerMouseUp);
118 |
119 | return () => {
120 | container.removeEventListener("mousemove", handleContainerMouseMove);
121 | container.removeEventListener("mouseup", handleContainerMouseUp);
122 | };
123 | }
124 | );
125 |
126 | return containerRef;
127 | };
128 |
129 | export { useCalendarMonthSelection };
130 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/hooks/use-month-view-position.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCalendar } from "@illostack/react-calendar";
4 | import * as React from "react";
5 |
6 | import { computePositionFromTime } from "../lib/utils";
7 |
8 | const useMonthViewPosition = (date: Date) => {
9 | const calendar = useCalendar();
10 | const view = calendar.useWatch((s) =>
11 | s.currentView.compositeId ? s.currentView.compositeId() : s.currentView.id
12 | );
13 |
14 | return React.useMemo(() => {
15 | return computePositionFromTime(date, calendar);
16 | }, [date, calendar, view]);
17 | };
18 |
19 | export { useMonthViewPosition };
20 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components/calendar-month-view";
2 |
3 | export * from "./lib/utils";
4 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CalendarApi,
3 | CalendarProvidedEvent,
4 | CalendarView,
5 | CalendarViewConfiguration,
6 | CalendarViewId,
7 | CalendarViewMeta
8 | } from "@illostack/react-calendar";
9 |
10 | const computeEventTimeRangeFromPointer = (
11 | e: MouseEvent,
12 | container: HTMLDivElement,
13 | calendar: CalendarApi<
14 | CalendarProvidedEvent,
15 | CalendarView[]
16 | >
17 | ) => {
18 | const { clientX, clientY } = e;
19 | const { width, height, top, left } = container.getBoundingClientRect();
20 | const pointerY = clientY - top;
21 | const pointerX = clientX - left;
22 |
23 | const dates = calendar.getDates();
24 | const monthRows = Math.floor(dates.length / 7);
25 | const monthCols = 7;
26 |
27 | const row = Math.floor((pointerY / height) * monthRows);
28 | const col = Math.floor((pointerX / width) * monthCols);
29 |
30 | const date = dates.at(row * monthCols + col)?.date as Date;
31 |
32 | const startAt = new Date(
33 | date.getFullYear(),
34 | date.getMonth(),
35 | date.getDate(),
36 | 0,
37 | 0
38 | );
39 | const endAt = new Date(
40 | date.getFullYear(),
41 | date.getMonth(),
42 | date.getDate(),
43 | 24,
44 | 0
45 | );
46 |
47 | return { startAt, endAt };
48 | };
49 |
50 | const computePositionFromTime = (
51 | date: Date,
52 | calendar: CalendarApi<
53 | CalendarProvidedEvent,
54 | CalendarView[]
55 | >
56 | ) => {
57 | const dates = calendar.getDates();
58 | const monthRows = Math.floor(dates.length / 7);
59 | const monthCols = 7;
60 |
61 | const dateIndex = dates.findIndex((d) => {
62 | return d.date.toDateString() === date.toDateString();
63 | });
64 |
65 | const dateRowIndex = Math.floor(dateIndex / monthCols);
66 | const dateColIndex = dateIndex % monthCols;
67 |
68 | return {
69 | top: `${((dateRowIndex / monthRows) * 100).toFixed(2)}%`,
70 | height: `${((1 / monthRows) * 100).toFixed(2)}%`,
71 | left: `${((dateColIndex / monthCols) * 100).toFixed(2)}%`,
72 | width: `${((1 / monthCols) * 100).toFixed(2)}%`
73 | };
74 | };
75 |
76 | export { computeEventTimeRangeFromPointer, computePositionFromTime };
77 |
--------------------------------------------------------------------------------
/packages/react-calendar-month/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar-range
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 | - Updated dependencies [ee62f92]
9 | - @illostack/react-calendar-day@0.0.1-beta.2
10 | - @illostack/react-calendar-ui@0.0.1-beta.2
11 | - @illostack/react-calendar@0.0.1-beta.2
12 |
13 | ## 0.0.1-beta.1
14 |
15 | ### Patch Changes
16 |
17 | -
18 | - Updated dependencies
19 | - @illostack/react-calendar-day@0.0.1-beta.1
20 | - @illostack/react-calendar-ui@0.0.1-beta.1
21 | - @illostack/react-calendar@0.0.1-beta.1
22 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar-range",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar Range",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": "./src/index.ts",
9 | "main": "./dist/index.js",
10 | "module": "./dist/index.mjs",
11 | "types": "./dist/index.d.ts",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/illostack/calendar.git",
15 | "directory": "packages/react-calendar-range"
16 | },
17 | "homepage": "https://github.com/illostack/calendar",
18 | "scripts": {
19 | "build": "tsup src/index.ts --format cjs,esm --dts",
20 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
21 | },
22 | "dependencies": {
23 | "@illostack/react-calendar-day": "workspace:*",
24 | "@illostack/react-calendar-ui": "workspace:*",
25 | "@illostack/react-calendar": "workspace:*"
26 | },
27 | "devDependencies": {
28 | "@illostack/eslint-config": "^0.0.3",
29 | "@illostack/typescript-config": "^0.0.3",
30 | "@types/react": "^19.0.0",
31 | "react": "^19.0.0",
32 | "react-dom": "^19.0.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/src/components/calendar-range-view.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | addDays,
5 | CalendarEvent,
6 | createCalendarView,
7 | getRangeDays
8 | } from "@illostack/react-calendar";
9 | import {
10 | CalendarDayEventCardContent,
11 | CalendarDaysViewTemplate
12 | } from "@illostack/react-calendar-day";
13 |
14 | const VIEW_ID = "range";
15 | type CalendarRangeMeta = {
16 | days: number;
17 | chip: React.ComponentType<{ event: CalendarEvent }>;
18 | };
19 | type CalendarRangeConfiguration = {
20 | days?: number;
21 | chip?: React.ComponentType<{ event: CalendarEvent }>;
22 | };
23 |
24 | const view = createCalendarView<
25 | typeof VIEW_ID,
26 | CalendarRangeMeta,
27 | CalendarRangeConfiguration
28 | >({
29 | id: VIEW_ID,
30 | compositeId() {
31 | return `${this.id}-${this.meta.days}`;
32 | },
33 | content: CalendarDaysViewTemplate,
34 | viewDatesFn(date) {
35 | return getRangeDays(date, this.meta.days).map((date) => ({
36 | date,
37 | isOutside: false
38 | }));
39 | },
40 | increaseFn(date) {
41 | return addDays(date, this.meta.days);
42 | },
43 | decreaseFn(date) {
44 | return addDays(date, -this.meta.days);
45 | },
46 | meta: {
47 | days: 1,
48 | chip: CalendarDayEventCardContent
49 | },
50 | configure(props) {
51 | const { chip, days } = props;
52 |
53 | if (chip) {
54 | this.meta.chip = chip;
55 | }
56 |
57 | if (days) {
58 | this.meta.days = days;
59 | }
60 |
61 | return this;
62 | }
63 | });
64 |
65 | export { view as CalendarRangeView };
66 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components/calendar-range-view";
2 |
--------------------------------------------------------------------------------
/packages/react-calendar-range/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar-ui
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 |
9 | ## 0.0.1-beta.1
10 |
11 | ### Patch Changes
12 |
13 | -
14 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar-ui",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar UI",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": {
9 | ".": "./src/index.ts"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/illostack/calendar.git",
14 | "directory": "packages/ui"
15 | },
16 | "homepage": "https://github.com/illostack/calendar",
17 | "scripts": {
18 | "build": "tsup src/index.ts --format cjs,esm",
19 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
20 | },
21 | "dependencies": {
22 | "@illostack/react-store": "^0.0.1",
23 | "@radix-ui/react-context-menu": "^2.2.6",
24 | "@radix-ui/react-dialog": "^1.1.6",
25 | "@radix-ui/react-label": "^2.1.2",
26 | "@radix-ui/react-radio-group": "^1.2.3",
27 | "@radix-ui/react-slot": "^1.1.2",
28 | "class-variance-authority": "^0.7.1",
29 | "clsx": "^2.1.1",
30 | "lucide-react": "^0.474.0",
31 | "react-hook-form": "^7.55.0",
32 | "sonner": "^1.7.4",
33 | "tailwind-merge": "^2.6.0",
34 | "vaul": "^1.1.2"
35 | },
36 | "devDependencies": {
37 | "@illostack/eslint-config": "^0.0.3",
38 | "@illostack/typescript-config": "^0.0.3",
39 | "@types/react": "^19.0.0",
40 | "react": "^19.0.0",
41 | "react-dom": "^19.0.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "../lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline"
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9"
28 | }
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default"
33 | }
34 | }
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | }
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/calendar-event-card.tsx:
--------------------------------------------------------------------------------
1 | import { cva } from "class-variance-authority";
2 |
3 | const calendarEventCardVariants = cva(
4 | "bg-muted/20 before:absolute before:shadow-xl border group-data-[event-state=active]:ring-2 group-data-[event-state=active]:ring-ring ring-inset pl-4 before:w-1 before:inset-y-1 before:rounded-lg before:left-1",
5 | {
6 | variants: {
7 | color: {
8 | green:
9 | "before:bg-green-500 dark:before:bg-green-300 before:shadow-green-500 bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200",
10 | blue: "before:bg-blue-500 dark:before:bg-blue-300 before:shadow-blue-500 bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200",
11 | red: "before:bg-red-500 dark:before:bg-red-300 before:shadow-red-500 bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200",
12 | yellow:
13 | "before:bg-yellow-500 dark:before:bg-yellow-300 before:shadow-yellow-500 bg-yellow-100 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200",
14 | purple:
15 | "before:bg-purple-500 dark:before:bg-purple-300 before:shadow-purple-500 bg-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200",
16 | pink: "before:bg-pink-500 dark:before:bg-pink-300 before:shadow-pink-500 bg-pink-100 dark:bg-pink-800 text-pink-800 dark:text-pink-200",
17 | indigo:
18 | "before:bg-indigo-500 dark:before:bg-indigo-300 before:shadow-indigo-500 bg-indigo-100 dark:bg-indigo-800 text-indigo-800 dark:text-indigo-200",
19 | cyan: "before:bg-cyan-500 dark:before:bg-cyan-300 before:shadow-cyan-500 bg-cyan-100 dark:bg-cyan-800 text-cyan-800 dark:text-cyan-200"
20 | }
21 | },
22 | defaultVariants: {
23 | color: "blue"
24 | }
25 | }
26 | );
27 |
28 | export { calendarEventCardVariants };
29 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/drawer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { Drawer as DrawerPrimitive } from "vaul";
5 |
6 | import { cn } from "../lib/utils";
7 |
8 | const Drawer = ({
9 | shouldScaleBackground = true,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
16 | );
17 | Drawer.displayName = "Drawer";
18 |
19 | const DrawerTrigger = DrawerPrimitive.Trigger;
20 |
21 | const DrawerPortal = DrawerPrimitive.Portal;
22 |
23 | const DrawerClose = DrawerPrimitive.Close;
24 |
25 | const DrawerOverlay = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
34 | ));
35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
36 |
37 | const DrawerContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, children, ...props }, ref) => (
41 |
42 |
43 |
51 |
52 | {children}
53 |
54 |
55 | ));
56 | DrawerContent.displayName = "DrawerContent";
57 |
58 | const DrawerHeader = ({
59 | className,
60 | ...props
61 | }: React.HTMLAttributes) => (
62 |
66 | );
67 | DrawerHeader.displayName = "DrawerHeader";
68 |
69 | const DrawerFooter = ({
70 | className,
71 | ...props
72 | }: React.HTMLAttributes) => (
73 |
77 | );
78 | DrawerFooter.displayName = "DrawerFooter";
79 |
80 | const DrawerTitle = React.forwardRef<
81 | React.ElementRef,
82 | React.ComponentPropsWithoutRef
83 | >(({ className, ...props }, ref) => (
84 |
92 | ));
93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
94 |
95 | const DrawerDescription = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ));
105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
106 |
107 | export {
108 | Drawer,
109 | DrawerClose,
110 | DrawerContent,
111 | DrawerDescription,
112 | DrawerFooter,
113 | DrawerHeader,
114 | DrawerOverlay,
115 | DrawerPortal,
116 | DrawerTitle,
117 | DrawerTrigger
118 | };
119 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "../lib/utils";
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | );
18 | }
19 | );
20 | Input.displayName = "Input";
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { cva, type VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "../lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 |
26 | export { Label };
27 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
4 | import { Circle } from "lucide-react";
5 | import * as React from "react";
6 |
7 | import { cn } from "../lib/utils";
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | );
20 | });
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | );
41 | });
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
43 |
44 | export { RadioGroup, RadioGroupItem };
45 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/components/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Toaster as Sonner, toast } from "sonner";
4 |
5 | type ToasterProps = React.ComponentProps;
6 |
7 | const Toaster = ({ ...props }: ToasterProps) => {
8 | return (
9 |
24 | );
25 | };
26 |
27 | export { toast, Toaster };
28 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/hooks/use-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createStore, useStore } from "@illostack/react-store";
4 | import * as React from "react";
5 |
6 | import {
7 | Dialog,
8 | DialogContent,
9 | DialogDescription,
10 | DialogHeader,
11 | DialogTitle
12 | } from "../components/dialog";
13 | import {
14 | Drawer,
15 | DrawerContent,
16 | DrawerDescription,
17 | DrawerHeader,
18 | DrawerTitle
19 | } from "../components/drawer";
20 | import { cn } from "../lib/utils";
21 | import { useMediaQuery } from "./use-media-query";
22 |
23 | type Dialogs = {
24 | [key: string]: DialogContent;
25 | };
26 |
27 | const generateId = () => {
28 | return Math.random().toString(36).substring(2, 15);
29 | };
30 |
31 | type DialogContent = {
32 | title?: string;
33 | description?: string;
34 | render: (onClose: () => void) => React.ReactNode;
35 | onClose?: () => void;
36 | } & React.HTMLAttributes;
37 |
38 | const store = createStore({});
39 |
40 | export const showDialog = (content: DialogContent) => {
41 | store.update((prev) => ({
42 | ...prev,
43 | [generateId()]: content
44 | }));
45 | };
46 |
47 | export const hideDialog = (key: string) => {
48 | store.update((prev) => {
49 | const newState = { ...prev };
50 | delete newState[key];
51 | return newState;
52 | });
53 | };
54 |
55 | export const DialogContainer = React.memo(() => {
56 | const dialogs = useStore(store);
57 |
58 | return (
59 |
60 | {Object.entries(dialogs).map(([key, sheetContent]) => (
61 |
62 | ))}
63 |
64 | );
65 | });
66 |
67 | DialogContainer.displayName = "DialogContainer";
68 |
69 | const DialogComponent = ({
70 | id,
71 | dialogContent: {
72 | title,
73 | description,
74 | render,
75 | onClose: _onClose,
76 | className,
77 | ...props
78 | }
79 | }: {
80 | id: string;
81 | dialogContent: DialogContent;
82 | }) => {
83 | const [open, setOpen] = React.useState(true);
84 | const isDesktop = useMediaQuery("(min-width: 768px)");
85 |
86 | React.useEffect(() => {
87 | if (!open) {
88 | _onClose?.();
89 | const timeout = setTimeout(() => {
90 | hideDialog(id);
91 | }, 600);
92 |
93 | return () => {
94 | clearTimeout(timeout);
95 | };
96 | }
97 |
98 | return;
99 | }, [_onClose, id, open]);
100 |
101 | function toggle(state: boolean) {
102 | setOpen(state);
103 | }
104 |
105 | const onClose = () => {
106 | toggle(false);
107 | };
108 |
109 | if (isDesktop) {
110 | return (
111 |
122 | );
123 | }
124 |
125 | return (
126 |
127 |
128 |
129 | {title}
130 | {description && {description}}
131 |
132 |
133 | {render(onClose)}
134 |
135 |
136 |
137 | );
138 | };
139 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/hooks/use-media-query.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | const useMediaQuery = (query: string) => {
4 | const [matches, setMatches] = useState(false);
5 |
6 | useEffect(() => {
7 | const media = window.matchMedia(query);
8 | if (media.matches !== matches) {
9 | setMatches(media.matches);
10 | }
11 | const listener = () => setMatches(media.matches);
12 | media.addListener(listener);
13 | return () => media.removeListener(listener);
14 | }, [matches, query]);
15 |
16 | return matches;
17 | };
18 |
19 | export { useMediaQuery };
20 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/hooks/use-sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createStore, useStore } from "@illostack/react-store";
4 | import * as React from "react";
5 |
6 | import {
7 | Drawer,
8 | DrawerContent,
9 | DrawerDescription,
10 | DrawerHeader,
11 | DrawerTitle
12 | } from "../components/drawer";
13 | import {
14 | Sheet,
15 | SheetContent,
16 | SheetDescription,
17 | SheetHeader,
18 | SheetTitle
19 | } from "../components/sheet";
20 | import { cn } from "../lib/utils";
21 | import { useMediaQuery } from "./use-media-query";
22 |
23 | type Sheets = {
24 | [key: string]: SheetContent;
25 | };
26 |
27 | type SheetContent = {
28 | title?: string;
29 | description?: string;
30 | render: (onClose: () => void) => React.ReactNode;
31 | onClose?: () => void;
32 | } & React.HTMLAttributes;
33 |
34 | const generateId = () => {
35 | return Math.random().toString(36).substring(2, 15);
36 | };
37 |
38 | const store = createStore({});
39 |
40 | export const showSheet = (content: SheetContent) => {
41 | store.update((prev) => ({
42 | ...prev,
43 | [generateId()]: content
44 | }));
45 | };
46 |
47 | export const hideSheet = (key: string) => {
48 | store.update((prev) => {
49 | const newState = { ...prev };
50 | delete newState[key];
51 | return newState;
52 | });
53 | };
54 |
55 | export const SheetContainer = React.memo(() => {
56 | const sheets = useStore(store);
57 |
58 | return (
59 |
60 | {Object.entries(sheets).map(([key, sheetContent]) => (
61 |
62 | ))}
63 |
64 | );
65 | });
66 |
67 | SheetContainer.displayName = "SheetContainer";
68 |
69 | const SheetComponent = ({
70 | id,
71 | sheetContent: {
72 | title,
73 | description,
74 | render,
75 | onClose: _onClose,
76 | className,
77 | ...props
78 | }
79 | }: {
80 | id: string;
81 | sheetContent: SheetContent;
82 | }) => {
83 | const [open, setOpen] = React.useState(true);
84 | const isDesktop = useMediaQuery("(min-width: 768px)");
85 |
86 | React.useEffect(() => {
87 | if (!open) {
88 | _onClose?.();
89 | const timeout = setTimeout(() => {
90 | hideSheet(id);
91 | }, 600);
92 | return () => {
93 | clearTimeout(timeout);
94 | };
95 | }
96 | }, [_onClose, id, open]);
97 |
98 | function toggle(state: boolean) {
99 | setOpen(state);
100 | }
101 |
102 | const onClose = () => {
103 | toggle(false);
104 | };
105 |
106 | if (isDesktop) {
107 | return (
108 |
109 |
110 |
111 | {title}
112 | {description && {description}}
113 |
114 | {render(onClose)}
115 |
116 |
117 | );
118 | }
119 |
120 | return (
121 |
122 |
123 |
124 | {title}
125 | {description && {description}}
126 |
127 |
128 | {render(onClose)}
129 |
130 |
131 |
132 | );
133 | };
134 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/index.ts:
--------------------------------------------------------------------------------
1 | export { cva } from "class-variance-authority";
2 | export type { VariantProps } from "class-variance-authority";
3 | export {
4 | CalendarPlusIcon,
5 | CheckIcon,
6 | ClipboardIcon,
7 | CopyIcon,
8 | CopyPlusIcon,
9 | PencilIcon,
10 | SquareDashedIcon,
11 | TrashIcon
12 | } from "lucide-react";
13 |
14 | export * from "./components/button";
15 | export * from "./components/calendar-event-card";
16 | export * from "./components/context-menu";
17 | export * from "./components/form";
18 | export * from "./components/input";
19 | export * from "./components/label";
20 | export * from "./components/radio-group";
21 | export * from "./components/sonner";
22 |
23 | export * from "./hooks/use-dialog";
24 | export * from "./hooks/use-sheet";
25 |
26 | export * from "./lib/utils";
27 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/lib/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.cn = cn;
4 | var clsx_1 = require("clsx");
5 | var tailwind_merge_1 = require("tailwind-merge");
6 | function cn() {
7 | var inputs = [];
8 | for (var _i = 0; _i < arguments.length; _i++) {
9 | inputs[_i] = arguments[_i];
10 | }
11 | return (0, tailwind_merge_1.twMerge)((0, clsx_1.clsx)(inputs));
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export { cn };
9 |
--------------------------------------------------------------------------------
/packages/react-calendar-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar-week
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 | - Updated dependencies [ee62f92]
9 | - @illostack/react-calendar-day@0.0.1-beta.2
10 | - @illostack/react-calendar-ui@0.0.1-beta.2
11 | - @illostack/react-calendar@0.0.1-beta.2
12 |
13 | ## 0.0.1-beta.1
14 |
15 | ### Patch Changes
16 |
17 | -
18 | - Updated dependencies
19 | - @illostack/react-calendar-day@0.0.1-beta.1
20 | - @illostack/react-calendar-ui@0.0.1-beta.1
21 | - @illostack/react-calendar@0.0.1-beta.1
22 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar-week",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar Week",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": "./src/index.ts",
9 | "main": "./dist/index.js",
10 | "module": "./dist/index.mjs",
11 | "types": "./dist/index.d.ts",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/illostack/calendar.git",
15 | "directory": "packages/react-calendar-week"
16 | },
17 | "homepage": "https://github.com/illostack/calendar",
18 | "scripts": {
19 | "build": "tsup src/index.ts --format cjs,esm --dts",
20 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
21 | },
22 | "dependencies": {
23 | "@illostack/react-calendar-day": "workspace:*",
24 | "@illostack/react-calendar-ui": "workspace:*",
25 | "@illostack/react-calendar": "workspace:*"
26 | },
27 | "devDependencies": {
28 | "@illostack/eslint-config": "^0.0.3",
29 | "@illostack/typescript-config": "^0.0.3",
30 | "@types/react": "^19.0.0",
31 | "react": "^19.0.0",
32 | "react-dom": "^19.0.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/src/components/calendar-week-view.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | addWeeks,
5 | CalendarEvent,
6 | createCalendarView,
7 | getWeekDays
8 | } from "@illostack/react-calendar";
9 | import {
10 | CalendarDayEventCardContent,
11 | CalendarDaysViewTemplate
12 | } from "@illostack/react-calendar-day";
13 |
14 | const VIEW_ID = "week";
15 | type CalendarWeekMeta = {
16 | chip: React.ComponentType<{ event: CalendarEvent }>;
17 | };
18 | type CalendarWeekConfiguration = {
19 | chip?: React.ComponentType<{ event: CalendarEvent }>;
20 | };
21 |
22 | const view = createCalendarView<
23 | typeof VIEW_ID,
24 | CalendarWeekMeta,
25 | CalendarWeekConfiguration
26 | >({
27 | id: VIEW_ID,
28 | content: CalendarDaysViewTemplate,
29 | viewDatesFn(date, weekStartsOn) {
30 | return getWeekDays(date, weekStartsOn).map((date) => ({
31 | date,
32 | isOutside: false
33 | }));
34 | },
35 | increaseFn(date) {
36 | return addWeeks(date, 1);
37 | },
38 | decreaseFn(date) {
39 | return addWeeks(date, -1);
40 | },
41 | meta: {
42 | chip: CalendarDayEventCardContent
43 | },
44 | configure(props) {
45 | const { chip } = props;
46 |
47 | if (chip) {
48 | this.meta.chip = chip;
49 | }
50 |
51 | return this;
52 | }
53 | });
54 |
55 | export { view as CalendarWeekView };
56 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components/calendar-week-view";
2 |
--------------------------------------------------------------------------------
/packages/react-calendar-week/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-calendar/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @illostack/react-calendar
2 |
3 | ## 0.0.1-beta.2
4 |
5 | ### Patch Changes
6 |
7 | - ee62f92: fix: useDateFragments behabiour
8 | - Updated dependencies [ee62f92]
9 | - @illostack/react-calendar-ui@0.0.1-beta.2
10 |
11 | ## 0.0.1-beta.1
12 |
13 | ### Patch Changes
14 |
15 | -
16 | - Updated dependencies
17 | - @illostack/react-calendar-ui@0.0.1-beta.1
18 |
--------------------------------------------------------------------------------
/packages/react-calendar/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@illostack/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/packages/react-calendar/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@illostack/react-calendar",
3 | "version": "0.0.1-beta.2",
4 | "keywords": [],
5 | "description": "React Calendar",
6 | "author": "illodev",
7 | "license": "MIT",
8 | "exports": "./src/index.ts",
9 | "main": "./dist/index.js",
10 | "module": "./dist/index.mjs",
11 | "types": "./dist/index.d.ts",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/illostack/calendar.git",
15 | "directory": "packages/react-calendar"
16 | },
17 | "homepage": "https://github.com/illostack/calendar",
18 | "scripts": {
19 | "build": "tsup src/index.ts --format cjs,esm --dts",
20 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
21 | },
22 | "dependencies": {
23 | "@hookform/resolvers": "^3.10.0",
24 | "@illostack/react-store": "^0.0.1",
25 | "@illostack/react-calendar-ui": "workspace:*",
26 | "react-hook-form": "^7.55.0",
27 | "zod": "^3.24.2"
28 | },
29 | "devDependencies": {
30 | "@illostack/eslint-config": "^0.0.3",
31 | "@illostack/typescript-config": "^0.0.3",
32 | "@types/react": "^19.0.0",
33 | "react": "^19.0.0",
34 | "react-dom": "^19.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/react-calendar/src/components/calendar-event-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@illostack/react-calendar-ui";
4 | import * as React from "react";
5 |
6 | import { CalendarEvent } from "../types";
7 | import { useCalendar } from "./calendar";
8 |
9 | interface CalendarEventCardResizeHandleProps {
10 | event: CalendarEvent;
11 | orientation?: "vertical" | "horizontal";
12 | }
13 |
14 | const CalendarEventCardResizeHandle: React.FC<
15 | CalendarEventCardResizeHandleProps
16 | > = ({ event, orientation }) => {
17 | return (
18 |
19 |
29 |
39 |
40 | );
41 | };
42 |
43 | interface CalendarEventCardProps extends React.HTMLAttributes {
44 | event: CalendarEvent;
45 | disabledDrag?: boolean;
46 | disabledResize?: boolean;
47 | resizeOrientation?: "vertical" | "horizontal";
48 | }
49 |
50 | const CalendarEventCard = React.forwardRef<
51 | HTMLDivElement,
52 | CalendarEventCardProps
53 | >(
54 | (
55 | {
56 | children,
57 | event,
58 | className,
59 | disabledDrag,
60 | disabledResize,
61 | resizeOrientation = "vertical",
62 | ...props
63 | },
64 | ref
65 | ) => {
66 | const calendar = useCalendar();
67 | const isCutted = calendar.useIsCuttedEvent(event.id);
68 | const isDragging = calendar.useIsDraggingEvent(event.id);
69 | const isResizing = calendar.useIsResizingEvent(event.id);
70 | const isActive = calendar.useIsActiveEvent(event.id);
71 |
72 | return (
73 |
92 | {children}
93 | {!disabledResize && (
94 |
98 | )}
99 |
100 | );
101 | }
102 | );
103 |
104 | CalendarEventCard.displayName = "CalendarEventCard";
105 |
106 | export { CalendarEventCard, CalendarEventCardResizeHandle };
107 |
--------------------------------------------------------------------------------
/packages/react-calendar/src/components/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | DialogContainer,
5 | SheetContainer,
6 | Toaster
7 | } from "@illostack/react-calendar-ui";
8 | import * as React from "react";
9 |
10 | import { useReactCalendar } from "../hooks/use-react-calendar";
11 | import {
12 | CalendarApi,
13 | CalendarOptions,
14 | CalendarProvidedEvent,
15 | CalendarView,
16 | CalendarViewConfiguration,
17 | CalendarViewId,
18 | CalendarViewMeta
19 | } from "../types";
20 |
21 | const CalendarContext = React.createContext<
22 | CalendarApi<
23 | CalendarProvidedEvent,
24 | CalendarView[]
25 | >
26 | >(
27 | {} as CalendarApi<
28 | CalendarProvidedEvent,
29 | CalendarView[]
30 | >
31 | );
32 | const useCalendar = () => {
33 | const context = React.useContext(CalendarContext);
34 | if (!context)
35 | throw new Error("useCalendar must be used within a CalendarContext");
36 | return context;
37 | };
38 |
39 | interface CalendarProviderProps<
40 | TEvent extends CalendarProvidedEvent,
41 | TViews extends CalendarView<
42 | CalendarViewId,
43 | CalendarViewMeta,
44 | CalendarViewConfiguration
45 | >[]
46 | > extends CalendarOptions {
47 | toasterTheme?: "light" | "dark" | "system" | undefined;
48 | children?: React.ReactNode;
49 | }
50 |
51 | const DEFAULT_TOASTER_THEME = "system";
52 |
53 | const CalendarProvider = <
54 | TEvent extends CalendarProvidedEvent,
55 | TViews extends CalendarView<
56 | CalendarViewId,
57 | CalendarViewMeta,
58 | CalendarViewConfiguration
59 | >[]
60 | >({
61 | children,
62 | toasterTheme = DEFAULT_TOASTER_THEME,
63 | ...options
64 | }: CalendarProviderProps) => {
65 | const calendar = useReactCalendar(options);
66 |
67 | calendar.useActiveEventKeyboardEvents();
68 |
69 | return (
70 | [0]
78 | >[]
79 | >
80 | }
81 | >
82 | {children}
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | CalendarProvider.displayName = "CalendarProvider";
91 |
92 | const Calendar = <
93 | TEvent extends CalendarProvidedEvent,
94 | TViews extends CalendarView<
95 | CalendarViewId,
96 | CalendarViewMeta,
97 | CalendarViewConfiguration
98 | >[]
99 | >(
100 | props: CalendarProviderProps
101 | ) => {
102 | return ;
103 | };
104 |
105 | interface CalendarContentProps extends React.HTMLAttributes {
106 | children?: React.ReactNode;
107 | }
108 |
109 | const CalendarContent = React.memo((props) => {
110 | const calendar = useCalendar();
111 | const currentView = calendar.useWatch((s) => s.currentView);
112 |
113 | return ;
114 | });
115 | CalendarContent.displayName = "CalendarContent";
116 |
117 | export { Calendar, CalendarContent, useCalendar };
118 |
--------------------------------------------------------------------------------
/packages/react-calendar/src/hooks/use-date-fragments.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { useCalendar } from "../components/calendar";
5 | import { addMinutes } from "../lib/time";
6 |
7 | export type DateFragmentType = "start" | "end" | "full" | "middle";
8 |
9 | interface DateFragment {
10 | id: string;
11 | type: DateFragmentType;
12 | date: Date;
13 | startAt: Date;
14 | endAt: Date;
15 | }
16 |
17 | const useDateFragments = (startAt?: Date, endAt?: Date): DateFragment[] => {
18 | const calendar = useCalendar();
19 | const dates = calendar.getDates();
20 | const minDate = new Date(dates[0]?.date!.setHours(0, 0, 0, 0)!);
21 | const maxDate = new Date(
22 | dates[dates.length - 1]?.date!.setHours(23, 59, 59, 59)!
23 | );
24 |
25 | const fragments = React.useMemo(() => {
26 | if (!startAt || !endAt) {
27 | return [];
28 | }
29 |
30 | const result: DateFragment[] = [];
31 | let currentDate = new Date(startAt);
32 | const endDate = addMinutes(endAt, -1);
33 |
34 | while (currentDate <= endDate) {
35 | if (currentDate < minDate || currentDate > maxDate) {
36 | currentDate.setDate(currentDate.getDate() + 1);
37 | currentDate.setHours(0, 0, 0, 0);
38 | continue;
39 | }
40 |
41 | if (
42 | currentDate.toDateString() === startAt.toDateString() &&
43 | currentDate.toDateString() === endDate.toDateString()
44 | ) {
45 | result.push({
46 | id: `fragment-${currentDate.toISOString()}-${minDate.toISOString()}-${maxDate.toISOString()}`,
47 | type: "full",
48 | date: new Date(currentDate),
49 | startAt: new Date(startAt),
50 | endAt: new Date(endAt)
51 | });
52 | } else if (currentDate.getDate() === startAt.getDate()) {
53 | result.push({
54 | id: `fragment-${currentDate.toISOString()}-${minDate.toISOString()}-${maxDate.toISOString()}`,
55 | type: "start",
56 | date: new Date(currentDate),
57 | startAt: new Date(startAt),
58 | endAt: new Date(new Date(currentDate).setHours(24, 0, 0, 0))
59 | });
60 | } else if (currentDate.getDate() === endDate.getDate()) {
61 | result.push({
62 | id: `fragment-${currentDate.toISOString()}-${minDate.toISOString()}-${maxDate.toISOString()}`,
63 | type: "end",
64 | date: new Date(currentDate),
65 | startAt: new Date(new Date(currentDate).setHours(0, 0, 0, 0)),
66 | endAt: new Date(endAt)
67 | });
68 | } else {
69 | result.push({
70 | id: `fragment-${currentDate.toISOString()}-${minDate.toISOString()}-${maxDate.toISOString()}`,
71 | type: "middle",
72 | date: new Date(currentDate),
73 | startAt: new Date(new Date(currentDate).setHours(0, 0, 0, 0)),
74 | endAt: new Date(new Date(currentDate).setHours(24, 0, 0, 0))
75 | });
76 | }
77 |
78 | currentDate.setDate(currentDate.getDate() + 1);
79 | currentDate.setHours(0, 0, 0, 0);
80 | }
81 |
82 | return result;
83 | }, [startAt, endAt, minDate, maxDate]);
84 |
85 | return fragments;
86 | };
87 |
88 | export { useDateFragments };
89 |
--------------------------------------------------------------------------------
/packages/react-calendar/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./components/calendar";
2 | export * from "./components/calendar-event-card";
3 |
4 | export * from "./lib/calendar";
5 | export * from "./lib/time";
6 | export * from "./lib/utils";
7 |
8 | export * from "./hooks/use-date-fragments";
9 | export * from "./hooks/use-react-calendar";
10 |
11 | export * from "./types";
12 |
--------------------------------------------------------------------------------
/packages/react-calendar/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | function generateUUID() {
2 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
3 | const r = (Math.random() * 16) | 0;
4 | const v = c === "x" ? r : (r & 0x3) | 0x8;
5 | return v.toString(16);
6 | });
7 | }
8 |
9 | const mergeRefs = (...refs: React.Ref[]) => {
10 | return (value: T | null) => {
11 | refs.forEach((ref) => {
12 | if (typeof ref === "function") {
13 | ref(value);
14 | } else if (ref) {
15 | ref.current = value;
16 | }
17 | });
18 | };
19 | };
20 |
21 | export { generateUUID, mergeRefs };
22 |
--------------------------------------------------------------------------------
/packages/react-calendar/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@illostack/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'apps/*'
3 | - 'packages/*'
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
8 | "outputs": [".next/**", "!.next/cache/**", "dist/**"]
9 | },
10 | "lint": {
11 | "dependsOn": ["^lint"]
12 | },
13 | "check-types": {
14 | "dependsOn": ["^check-types"]
15 | },
16 | "dev": {
17 | "cache": false,
18 | "persistent": true
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------