diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4caecf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI Pipeline + +on: + pull_request: + +permissions: + contents: read + +jobs: + build_and_test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x, 22.x] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Check code formatting + run: npm run format:check + + - name: Run ESLint + run: npm run lint + + - name: Verify build + run: npm run build + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 0e6d889..cdb5310 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,7 @@ yarn-error.log* docs -storybook-static \ No newline at end of file +storybook-static + +# Claude Code +.claude/settings.local.json \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..0398b7a --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1a92dbd --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +# Ignore Artifacts +node_modules +dist +storybook-static +coverage +.cache + +# Misc +.env +.npmrc +eslint.config.js +package-lock.json + +# Ignore generated documentation +docs/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1832759 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 073582e..3b80344 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,11 +1,11 @@ -import type { Preview } from '@storybook/react-vite' +import type { Preview } from "@storybook/react-vite"; const preview: Preview = { parameters: { controls: { matchers: { - color: /(background|color)$/i, - date: /Date$/i, + color: /(background|color)$/i, + date: /Date$/i, }, }, @@ -13,9 +13,9 @@ const preview: Preview = { // 'todo' - show a11y violations in the test UI only // 'error' - fail CI on a11y violations // 'off' - skip a11y checks entirely - test: 'todo' - } + test: "todo", + }, }, }; -export default preview; \ No newline at end of file +export default preview; diff --git a/CHANGELOG.md b/CHANGELOG.md index 247f2df..81822f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,87 +1,79 @@ # [1.2.0](https://github.com/Jaganath-MSJ/CalendarSimple/compare/v1.1.1...v1.2.0) (2026-02-17) - ### Bug Fixes -* add explicit type annotations to map callbacks ([fea1b24](https://github.com/Jaganath-MSJ/CalendarSimple/commit/fea1b24babb9ebf8767dd34f2a4658b77d3a54c9)) -* centralize calendar constants for consistency ([a7c8e02](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a7c8e02406d20c78893fcf5d5cd456e95092b530)) -* **popover:** change positioning to fixed and anchor to button ([03a8413](https://github.com/Jaganath-MSJ/CalendarSimple/commit/03a841355022cc27100971ce96c34cd45f2c5369)) -* remove maxEvents from interface and make it dynamic ([b0d5495](https://github.com/Jaganath-MSJ/CalendarSimple/commit/b0d5495096316331a5f0f376ec4759dfb70a1351)) - +- add explicit type annotations to map callbacks ([fea1b24](https://github.com/Jaganath-MSJ/CalendarSimple/commit/fea1b24babb9ebf8767dd34f2a4658b77d3a54c9)) +- centralize calendar constants for consistency ([a7c8e02](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a7c8e02406d20c78893fcf5d5cd456e95092b530)) +- **popover:** change positioning to fixed and anchor to button ([03a8413](https://github.com/Jaganath-MSJ/CalendarSimple/commit/03a841355022cc27100971ce96c34cd45f2c5369)) +- remove maxEvents from interface and make it dynamic ([b0d5495](https://github.com/Jaganath-MSJ/CalendarSimple/commit/b0d5495096316331a5f0f376ec4759dfb70a1351)) ### Features -* add Storybook setup for component documentation and testing ([2ab6e12](https://github.com/Jaganath-MSJ/CalendarSimple/commit/2ab6e127005371f99c80212f96ec3053a07dcc36)) -* improve popover positioning and scrolling ([61ff399](https://github.com/Jaganath-MSJ/CalendarSimple/commit/61ff399a86cd3f810e47cd10c103ca8ac8ece5cd)) +- add Storybook setup for component documentation and testing ([2ab6e12](https://github.com/Jaganath-MSJ/CalendarSimple/commit/2ab6e127005371f99c80212f96ec3053a07dcc36)) +- improve popover positioning and scrolling ([61ff399](https://github.com/Jaganath-MSJ/CalendarSimple/commit/61ff399a86cd3f810e47cd10c103ca8ac8ece5cd)) ## [1.1.1](https://github.com/Jaganath-MSJ/CalendarSimple/compare/v1.1.0...v1.1.1) (2026-02-11) - ### Bug Fixes -* fix date selection logic and dependency arrays ([78aa7e7](https://github.com/Jaganath-MSJ/CalendarSimple/commit/78aa7e789d7122d628c409a640a896641411d4de)) +- fix date selection logic and dependency arrays ([78aa7e7](https://github.com/Jaganath-MSJ/CalendarSimple/commit/78aa7e789d7122d628c409a640a896641411d4de)) ## [1.1.1-beta.1](https://github.com/Jaganath-MSJ/CalendarSimple/compare/v1.1.0...v1.1.1-beta.1) (2026-02-11) - ### Bug Fixes -* fix date selection logic and dependency arrays ([78aa7e7](https://github.com/Jaganath-MSJ/CalendarSimple/commit/78aa7e789d7122d628c409a640a896641411d4de)) +- fix date selection logic and dependency arrays ([78aa7e7](https://github.com/Jaganath-MSJ/CalendarSimple/commit/78aa7e789d7122d628c409a640a896641411d4de)) # [1.1.0](https://github.com/Jaganath-MSJ/CalendarSimple/compare/v1.0.2...v1.1.0) (2026-02-10) - ### Bug Fixes -* **calendar:** correct event width calculation and styling for multi-day events ([e38f00a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/e38f00a2700be8711e11a6e69847a0f7b76dfb7f)) -* ensure consistent date comparison by using start of day ([5bab11b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/5bab11b078e25bb422f91d328a35f8bd3b1adc97)) -* extract header into separate component ([d32e99f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/d32e99f6321e1da85f29b8cdae16b84609f0c281)) -* remove the registry-url in the setup node.js step ([05ecf99](https://github.com/Jaganath-MSJ/CalendarSimple/commit/05ecf99e3fd734260f26f29a40eaefd74d58cf2a)) -* update repository url for semantic-release ([bbf5d9a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/bbf5d9ab50e4bff6030f185d98da1cbe938d1274)) -* updating the package-lock file ([55e0c18](https://github.com/Jaganath-MSJ/CalendarSimple/commit/55e0c18bd0acec23ca214110d843378723207d6d)) - +- **calendar:** correct event width calculation and styling for multi-day events ([e38f00a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/e38f00a2700be8711e11a6e69847a0f7b76dfb7f)) +- ensure consistent date comparison by using start of day ([5bab11b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/5bab11b078e25bb422f91d328a35f8bd3b1adc97)) +- extract header into separate component ([d32e99f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/d32e99f6321e1da85f29b8cdae16b84609f0c281)) +- remove the registry-url in the setup node.js step ([05ecf99](https://github.com/Jaganath-MSJ/CalendarSimple/commit/05ecf99e3fd734260f26f29a40eaefd74d58cf2a)) +- update repository url for semantic-release ([bbf5d9a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/bbf5d9ab50e4bff6030f185d98da1cbe938d1274)) +- updating the package-lock file ([55e0c18](https://github.com/Jaganath-MSJ/CalendarSimple/commit/55e0c18bd0acec23ca214110d843378723207d6d)) ### Features -* add maxEvents prop to limit displayed events per day ([864ba96](https://github.com/Jaganath-MSJ/CalendarSimple/commit/864ba9652accc49d207b5fa6125607e6746d2dab)) -* add onEventClick and onMoreClick callbacks ([90b4d82](https://github.com/Jaganath-MSJ/CalendarSimple/commit/90b4d82fff13b7c7a0fd2e7702033e1bc041f323)) -* add optional color property to calendar events ([de976db](https://github.com/Jaganath-MSJ/CalendarSimple/commit/de976db618a75004a0cdc2747bdd31d7112ee18f)) -* add spacer handling and improve event limit logic ([9160689](https://github.com/Jaganath-MSJ/CalendarSimple/commit/9160689a6d662173e6209bb5fbd247b14c02cf9b)) -* add theme support for calendar date styling ([38a199b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/38a199ba9fd2e73d670fe95819572831e77c3214)) -* calculate maxEvents dynamically based on calendar height ([ae3e7ad](https://github.com/Jaganath-MSJ/CalendarSimple/commit/ae3e7ade0a0cf6186c8299f9d98564ef7b0ad178)) -* **calendar:** implement week-based event layout with proper spacing ([550fa51](https://github.com/Jaganath-MSJ/CalendarSimple/commit/550fa51405109e6d5d9adb1dbc164833805cc488)) -* **calendar:** improve event rendering across week boundaries ([93dac33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/93dac33e833c52b9fb4145452f090d82deb6f8d1)) -* **calendar:** support date range events with visual rendering ([437c197](https://github.com/Jaganath-MSJ/CalendarSimple/commit/437c197c746de5edf04836db029ee3149772e3eb)) -* enable event item click to select specific date ([cfb43cc](https://github.com/Jaganath-MSJ/CalendarSimple/commit/cfb43ccaf93276e1d28ef9c82a89e8a508b177ce)) -* **EventPopover:** add popover for hidden events ([117197f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/117197faf0c599468bbc4d508aa5b1b4ef8c99bf)) -* **EventPopover:** correct popover event styling and date color logic ([6fc5662](https://github.com/Jaganath-MSJ/CalendarSimple/commit/6fc56622320af4d8ed9577d318e7f58b8e2c9efa)) -* make calendar responsive using resize observer ([a01af33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a01af33dea77261e15d452e4ec00aff6063975b9)) +- add maxEvents prop to limit displayed events per day ([864ba96](https://github.com/Jaganath-MSJ/CalendarSimple/commit/864ba9652accc49d207b5fa6125607e6746d2dab)) +- add onEventClick and onMoreClick callbacks ([90b4d82](https://github.com/Jaganath-MSJ/CalendarSimple/commit/90b4d82fff13b7c7a0fd2e7702033e1bc041f323)) +- add optional color property to calendar events ([de976db](https://github.com/Jaganath-MSJ/CalendarSimple/commit/de976db618a75004a0cdc2747bdd31d7112ee18f)) +- add spacer handling and improve event limit logic ([9160689](https://github.com/Jaganath-MSJ/CalendarSimple/commit/9160689a6d662173e6209bb5fbd247b14c02cf9b)) +- add theme support for calendar date styling ([38a199b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/38a199ba9fd2e73d670fe95819572831e77c3214)) +- calculate maxEvents dynamically based on calendar height ([ae3e7ad](https://github.com/Jaganath-MSJ/CalendarSimple/commit/ae3e7ade0a0cf6186c8299f9d98564ef7b0ad178)) +- **calendar:** implement week-based event layout with proper spacing ([550fa51](https://github.com/Jaganath-MSJ/CalendarSimple/commit/550fa51405109e6d5d9adb1dbc164833805cc488)) +- **calendar:** improve event rendering across week boundaries ([93dac33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/93dac33e833c52b9fb4145452f090d82deb6f8d1)) +- **calendar:** support date range events with visual rendering ([437c197](https://github.com/Jaganath-MSJ/CalendarSimple/commit/437c197c746de5edf04836db029ee3149772e3eb)) +- enable event item click to select specific date ([cfb43cc](https://github.com/Jaganath-MSJ/CalendarSimple/commit/cfb43ccaf93276e1d28ef9c82a89e8a508b177ce)) +- **EventPopover:** add popover for hidden events ([117197f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/117197faf0c599468bbc4d508aa5b1b4ef8c99bf)) +- **EventPopover:** correct popover event styling and date color logic ([6fc5662](https://github.com/Jaganath-MSJ/CalendarSimple/commit/6fc56622320af4d8ed9577d318e7f58b8e2c9efa)) +- make calendar responsive using resize observer ([a01af33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a01af33dea77261e15d452e4ec00aff6063975b9)) # [1.1.0-beta.1](https://github.com/Jaganath-MSJ/CalendarSimple/compare/v1.0.2...v1.1.0-beta.1) (2026-02-10) - ### Bug Fixes -* **calendar:** correct event width calculation and styling for multi-day events ([e38f00a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/e38f00a2700be8711e11a6e69847a0f7b76dfb7f)) -* ensure consistent date comparison by using start of day ([5bab11b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/5bab11b078e25bb422f91d328a35f8bd3b1adc97)) -* extract header into separate component ([d32e99f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/d32e99f6321e1da85f29b8cdae16b84609f0c281)) -* remove the registry-url in the setup node.js step ([05ecf99](https://github.com/Jaganath-MSJ/CalendarSimple/commit/05ecf99e3fd734260f26f29a40eaefd74d58cf2a)) -* update repository url for semantic-release ([bbf5d9a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/bbf5d9ab50e4bff6030f185d98da1cbe938d1274)) -* updating the package-lock file ([55e0c18](https://github.com/Jaganath-MSJ/CalendarSimple/commit/55e0c18bd0acec23ca214110d843378723207d6d)) - +- **calendar:** correct event width calculation and styling for multi-day events ([e38f00a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/e38f00a2700be8711e11a6e69847a0f7b76dfb7f)) +- ensure consistent date comparison by using start of day ([5bab11b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/5bab11b078e25bb422f91d328a35f8bd3b1adc97)) +- extract header into separate component ([d32e99f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/d32e99f6321e1da85f29b8cdae16b84609f0c281)) +- remove the registry-url in the setup node.js step ([05ecf99](https://github.com/Jaganath-MSJ/CalendarSimple/commit/05ecf99e3fd734260f26f29a40eaefd74d58cf2a)) +- update repository url for semantic-release ([bbf5d9a](https://github.com/Jaganath-MSJ/CalendarSimple/commit/bbf5d9ab50e4bff6030f185d98da1cbe938d1274)) +- updating the package-lock file ([55e0c18](https://github.com/Jaganath-MSJ/CalendarSimple/commit/55e0c18bd0acec23ca214110d843378723207d6d)) ### Features -* add maxEvents prop to limit displayed events per day ([864ba96](https://github.com/Jaganath-MSJ/CalendarSimple/commit/864ba9652accc49d207b5fa6125607e6746d2dab)) -* add onEventClick and onMoreClick callbacks ([90b4d82](https://github.com/Jaganath-MSJ/CalendarSimple/commit/90b4d82fff13b7c7a0fd2e7702033e1bc041f323)) -* add optional color property to calendar events ([de976db](https://github.com/Jaganath-MSJ/CalendarSimple/commit/de976db618a75004a0cdc2747bdd31d7112ee18f)) -* add spacer handling and improve event limit logic ([9160689](https://github.com/Jaganath-MSJ/CalendarSimple/commit/9160689a6d662173e6209bb5fbd247b14c02cf9b)) -* add theme support for calendar date styling ([38a199b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/38a199ba9fd2e73d670fe95819572831e77c3214)) -* calculate maxEvents dynamically based on calendar height ([ae3e7ad](https://github.com/Jaganath-MSJ/CalendarSimple/commit/ae3e7ade0a0cf6186c8299f9d98564ef7b0ad178)) -* **calendar:** implement week-based event layout with proper spacing ([550fa51](https://github.com/Jaganath-MSJ/CalendarSimple/commit/550fa51405109e6d5d9adb1dbc164833805cc488)) -* **calendar:** improve event rendering across week boundaries ([93dac33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/93dac33e833c52b9fb4145452f090d82deb6f8d1)) -* **calendar:** support date range events with visual rendering ([437c197](https://github.com/Jaganath-MSJ/CalendarSimple/commit/437c197c746de5edf04836db029ee3149772e3eb)) -* enable event item click to select specific date ([cfb43cc](https://github.com/Jaganath-MSJ/CalendarSimple/commit/cfb43ccaf93276e1d28ef9c82a89e8a508b177ce)) -* **EventPopover:** add popover for hidden events ([117197f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/117197faf0c599468bbc4d508aa5b1b4ef8c99bf)) -* **EventPopover:** correct popover event styling and date color logic ([6fc5662](https://github.com/Jaganath-MSJ/CalendarSimple/commit/6fc56622320af4d8ed9577d318e7f58b8e2c9efa)) -* make calendar responsive using resize observer ([a01af33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a01af33dea77261e15d452e4ec00aff6063975b9)) +- add maxEvents prop to limit displayed events per day ([864ba96](https://github.com/Jaganath-MSJ/CalendarSimple/commit/864ba9652accc49d207b5fa6125607e6746d2dab)) +- add onEventClick and onMoreClick callbacks ([90b4d82](https://github.com/Jaganath-MSJ/CalendarSimple/commit/90b4d82fff13b7c7a0fd2e7702033e1bc041f323)) +- add optional color property to calendar events ([de976db](https://github.com/Jaganath-MSJ/CalendarSimple/commit/de976db618a75004a0cdc2747bdd31d7112ee18f)) +- add spacer handling and improve event limit logic ([9160689](https://github.com/Jaganath-MSJ/CalendarSimple/commit/9160689a6d662173e6209bb5fbd247b14c02cf9b)) +- add theme support for calendar date styling ([38a199b](https://github.com/Jaganath-MSJ/CalendarSimple/commit/38a199ba9fd2e73d670fe95819572831e77c3214)) +- calculate maxEvents dynamically based on calendar height ([ae3e7ad](https://github.com/Jaganath-MSJ/CalendarSimple/commit/ae3e7ade0a0cf6186c8299f9d98564ef7b0ad178)) +- **calendar:** implement week-based event layout with proper spacing ([550fa51](https://github.com/Jaganath-MSJ/CalendarSimple/commit/550fa51405109e6d5d9adb1dbc164833805cc488)) +- **calendar:** improve event rendering across week boundaries ([93dac33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/93dac33e833c52b9fb4145452f090d82deb6f8d1)) +- **calendar:** support date range events with visual rendering ([437c197](https://github.com/Jaganath-MSJ/CalendarSimple/commit/437c197c746de5edf04836db029ee3149772e3eb)) +- enable event item click to select specific date ([cfb43cc](https://github.com/Jaganath-MSJ/CalendarSimple/commit/cfb43ccaf93276e1d28ef9c82a89e8a508b177ce)) +- **EventPopover:** add popover for hidden events ([117197f](https://github.com/Jaganath-MSJ/CalendarSimple/commit/117197faf0c599468bbc4d508aa5b1b4ef8c99bf)) +- **EventPopover:** correct popover event styling and date color logic ([6fc5662](https://github.com/Jaganath-MSJ/CalendarSimple/commit/6fc56622320af4d8ed9577d318e7f58b8e2c9efa)) +- make calendar responsive using resize observer ([a01af33](https://github.com/Jaganath-MSJ/CalendarSimple/commit/a01af33dea77261e15d452e4ec00aff6063975b9)) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..90c6d09 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,237 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Building & Bundling + +- `npm run build` — Build the library with Vite (outputs CJS, ESM, IIFE formats to `dist/`) +- `npm run build-storybook` — Build Storybook static site + +### Testing + +- `npm test` — Run all tests once +- `npm run test:watch` — Run tests in watch mode + +### Code Quality + +- `npm run lint` — Check for ESLint violations +- `npm run lint:fix` — Fix ESLint violations automatically +- `npm run format` — Format all code with Prettier +- `npm run format:check` — Check if code is formatted correctly + +### Documentation & Stories + +- `npm run storybook` — Start Storybook dev server on port 6006 +- Live demo: http://calendarsimple.netlify.app + +### Single Test Execution + +Use `npm test -- ` to run specific tests: + +```bash +npm test -- MonthView.test.tsx # Run one test file +npm test -- useEvents # Run tests matching pattern +``` + +## High-Level Architecture + +### Compound Component Pattern + +The Calendar is a compound component using a **Provider + Hook** pattern: + +- **`Calendar`** — Root component that wraps content in `CalendarProvider` +- **`CalendarProvider`** — Context provider managing calendar state via reducer +- **`useCalendar()`** — Hook consuming the context for state access and dispatch +- **Child components** (views, header, etc.) use `useCalendarProps()` to merge config with local overrides + +### State Management (CalendarContext) + +- **State shape**: `{ selectedDate: DateTime, view: ECalendarViewType, customDays?: number }` +- **Actions**: `SET_DATE`, `SET_VIEW`, `NEXT`, `PREV`, `TODAY` (defined in `CALENDAR_ACTIONS`) +- **Reducer logic**: Handles navigation math (e.g., next month, previous week) based on current view +- **Date handling**: Uses `DateType` (Luxon `DateTime`) throughout—never raw JS `Date` in internal logic + +### Views Architecture + +Each view is a separate component receiving props via `useCalendarProps()`: + +- **MonthView** — Grid layout showing full month with day cells +- **WeekView** — Time-grid layout with hourly slots across 7 days +- **DayView** — Single day in time-grid with hourly slots +- **CustomDaysView** — Configurable multi-day time-grid (e.g., 3-day view) +- **ScheduleView** — Continuous scrollable list of events grouped by date +- **View** — Wrapper component that renders the appropriate view based on context state + +### Prop Distribution Pattern + +**`useCalendarProps(localProps: T)`** merges context config with local overrides: + +1. Reads config from `CalendarContext` +2. Overlays local props (undefined values don't override) +3. Returns merged `CalendarContentProps` with type safety + +This enables prop composition: global calendar props + view-specific overrides. + +### Layout & Styling + +- **CSS Modules**: All styles co-located in `Component.module.css` files +- **Theme system**: `CalendarTheme` type provides color overrides (`default`, `selected`, `today`) +- **Custom classes**: `CalendarClassNames` type allows targeting specific elements +- **CSS Variables**: Layout dimensions (`--calendar-width`, `--calendar-height`) set at root level +- **No Tailwind/SCSS**: Vanilla CSS modules only + +### Core Hooks (Event & Layout Logic) + +- **`useEvents`** — Filters and enriches events for the current date range; supports O(1) lookup via `enrichedEventsByDate` +- **`useAllDayBanner`** — Extracts all-day events from event list; handles multi-day spanning +- **`useDayEventLayout`** — Implements "Tetris" collision detection for overlapping timed events; returns positioning data +- **`useMonthGrid`** — Builds month calendar grid with week rows; handles adjacent month visibility +- **`useScheduleView`** — Groups events by date for schedule layout; handles sorting +- **`useResizeObserver`** — Observes container resize; provides width/height for responsive layout + +### Event Types & Interfaces + +**`CalendarEvent`** — The core event shape: + +```typescript +{ + id?: string; + startDate: string; // ISO format: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss + endDate?: string; // Same format; undefined = same day as startDate + title: string; + style?: CSSProperties; + [key: string]: unknown; // Extensible for custom fields +} +``` + +Multi-day events use `startDate` and `endDate` without time components; timed events include `Txx:xx:xx` in ISO format. + +### Date Handling (Luxon-Only) + +- **Internal representation**: `DateTime` from Luxon library (not JS `Date`) +- **Utilities in `src/utils/date.ts`**: `dateFn()` normalizes inputs (JS Date, ISO string, timestamp) → `DateTime` +- **No raw `Date` in components**: Convert at boundaries (props input/output) using `toJSDate()` / `dateFn()` +- **Localization**: Full Luxon support via `locale` prop; date formatting respects language + +### Accessibility + +- **Keyboard Navigation**: Tab focus management, Enter/Space activation, Escape to close popovers +- **ARIA Support**: Semantic roles, labels, and attributes on interactive elements +- **Color Contrast**: Utility functions in `src/utils/contrast.ts` ensure WCAG compliance +- See `src/stories/Accessibility.stories.tsx` for examples + +## Testing Patterns + +### Setup & Mocks + +- **Test entry**: `src/setupTests.ts` — Configures Vitest globals, DOM assertions, and mocks +- **ResizeObserver mock**: `useResizeObserver` is mocked as a hook returning fixed `{ width: 1200, height: 600 }` +- **Context mocking**: Tests use `` with initial state; no external mock library needed +- **JSDOM environment**: `vitest.config.ts` specifies `jsdom` for DOM testing + +### Test Structure + +- **Unit tests**: Hooks and utilities tested in isolation (e.g., `useEvents.test.ts`) +- **Component tests**: Render with `` and RTL queries +- **Stories as tests**: Storybook stories serve as integration tests; manually run in Storybook dev mode +- **Test data**: Use realistic event fixtures; avoid generic mock factories + +### Common Assertions + +- Query elements: `getByRole`, `getByTestId`, `queryByText` +- Interaction: `userEvent.click()`, `userEvent.keyboard()` +- State: Check rendered output after dispatch (no direct state inspection) + +## Implementation Patterns + +### Adding a New Feature + +1. **Plan first** (using /writing-plans if non-trivial) +2. **Use TDD**: Write test, then component/hook +3. **Type everything**: No implicit `any`; leverage TypeScript +4. **Co-locate styles**: Create `Component.module.css` alongside `.tsx` +5. **Use Luxon**: Never raw `Date` in internal logic +6. **Test in Storybook**: Create stories demonstrating the feature +7. **Update exports**: Add new types/components to `src/index.ts` + +### Naming Conventions + +- **Hooks**: Lowercase `use*` (e.g., `useEvents`, `useDayEventLayout`) +- **Components**: PascalCase, feature-focused (e.g., `MonthEventItem`, `DayColumn`) +- **Utilities**: Lowercase, exported individually (e.g., `dateFn`, `getStartOfMonth`) +- **Types**: PascalCase prefix + descriptive (e.g., `CalendarProps`, `RenderDateCellProps`) +- **Enums**: UPPERCASE with `E` prefix (e.g., `ECalendarViewType`, `EDayType`) + +### CSS Patterns + +- **Selectors**: Use CSS classes defined in module; avoid element selectors for encapsulation +- **Responsive**: Container query style — width/height props + ResizeObserver +- **Theme colors**: Applied via inline `style` prop or `classNames` override, not CSS variables +- **Layout**: Flexbox/Grid; no absolute positioning except for current-time line + +### Git & Commits + +- **Branch strategy**: Feature branches off `dev`; PR to `dev` (not `main`) +- **Commit style**: Conventional Commits (feat:, fix:, test:, docs:, refactor:, chore:) +- **Pre-commit hooks**: Husky + lint-staged auto-fixes code on commit +- **Semantic Release**: Automatic versioning from commit messages + +## Key Files & Directory Structure + +| Path | Purpose | +| --------------------------------- | -------------------------------------------------------------------- | +| `src/Calendar.tsx` | Root component; wraps in provider and dispatches to View | +| `src/context/CalendarContext.tsx` | State management, reducer, provider, and `useCalendar()` hook | +| `src/components/views/` | View implementations (Month, Week, Day, Schedule, CustomDays) | +| `src/components/core/` | Reusable core components (AllDayBanner, DayColumn, EventItems, etc.) | +| `src/hooks/` | Custom hooks (useEvents, useCalendarProps, layout hooks, etc.) | +| `src/utils/date.ts` | Luxon wrappers and date calculations | +| `src/utils/common.ts` | General utilities (event filtering, sorting, etc.) | +| `src/types/` | TypeScript interfaces and type definitions | +| `src/constants/` | Theme defaults, action types, layout constants | +| `src/styles/` | Global CSS variables | +| `src/stories/` | Storybook stories for all features and QA scenarios | +| `dist/` | Build output (auto-generated) | + +## Important Context from Memory + +This project uses specific patterns documented in prior sessions: + +- **Luxon-only dates**: All internal date handling uses `DateTime`; convert to/from JS `Date` at API boundaries +- **CSS co-location**: Styles live alongside components in `.module.css` files +- **Context mocks in tests**: No Vitest mock() calls; use `CalendarProvider` with test props +- **ResizeObserver mock**: Mocked in `setupTests.ts` for consistent test sizes +- **Compound components + prop distribution**: Central `useCalendarProps()` hook merges context config with local overrides +- **Event layout hooks**: Sophisticated math for collision detection (Tetris algorithm) and multi-day rendering + +## Common Development Tasks + +### Running Tests for a Feature + +```bash +npm test -- Month # All month-related tests +npm test -- events # All event-related tests +``` + +### Adding a Custom Renderer + +1. Add the render prop to `CalendarProps` type +2. Pass through `useCalendarProps()` +3. In the component, check if prop exists; fallback to default render +4. Document in README and create a Storybook story + +### Fixing a Bug in Event Layout + +1. Add test case reproducing the issue (MonthEventItem.test.tsx or DayWeekEventItem.test.tsx) +2. Debug using event data and expected positioning +3. Update `useDayEventLayout` or `useMonthGrid` logic +4. Verify in Storybook with edge case stories (see `src/stories/QA/LayoutLimits.stories.tsx`) + +### Extending Theme System + +1. Add new color key to `CalendarTheme` type +2. Add default value to `defaultTheme` in `src/constants/theme.ts` +3. Update components to use the new color +4. Create Storybook story showing the customization diff --git a/FEATURES.md b/FEATURES.md index 51e000e..7aedd44 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,65 +1,332 @@ -# Calendar Simple - Detailed Feature Guide +# Calendar Simple — Detailed Feature Guide -This document provides a comprehensive breakdown of the features available in the `calendar-simple` library and how you can take full advantage of them. +> **Quick links:** [README](./README.md) · [npm](https://www.npmjs.com/package/calendar-simple) · [Live Demo](https://calendarsimple.netlify.app) -## 🗓️ Multiple Views +A comprehensive reference for all features in the `calendar-simple` library. -The calendar is designed to provide users with multiple perspectives of their schedule. You can switch between these views using the `view` prop (`ECalendarViewType`). +## Table of Contents -- **Month View (`"month"`)**: The default view, displaying a traditional grid of the entire month. Events are stacked on each day, and if there are too many events to fit, a customizable "+X more" button appears. -- **Week View (`"week"`)**: Displays a 7-day column layout with a time grid. Events are rendered as blocks spanning their respective time slots, making it easy to identify overlapping schedules and free time. -- **Day View (`"day"`)**: Similar to the Week View but focused entirely on a single day. This is perfect for detailed daily planning and provides maximum horizontal space for event details. -- **Schedule View (`"schedule"`)**: A chronological list of upcoming events grouped by date. This view is highly optimized for mobile devices or sidebars where space is limited and users just need to see "what's next." +- [Multiple Views](#multiple-views) +- [Event Handling](#event-handling) +- [Theming & Customization](#theming--customization) +- [Time Formatting](#time-formatting) +- [Localization](#localization) +- [Right-to-Left (RTL) Support](#right-to-left-rtl-support) +- [Color Schemes & Dark Mode](#color-schemes--dark-mode) +- [Interactive Callbacks](#interactive-callbacks) +- [Responsive Layout](#responsive-layout) +- [Performance Options](#performance-options) +- [Keyboard Navigation & Accessibility](#keyboard-navigation--accessibility) +- [Props Reference](#props-reference) +- [TypeScript Support](#typescript-support) -## ✨ Event Handling +--- -`calendar-simple` provides robust capabilities for rendering and interacting with events. +## Multiple Views -- **Data Structure**: Pass an array of `CalendarEvent` objects to the `events` prop. Each event strictly requires a `startDate` and a `title`, but optionally accepts an `endDate`, custom `color`, and an `id`. -- **Custom Metadata**: The `CalendarEvent` interface allows any `[key: string]: unknown`, meaning you can attach custom database IDs, descriptions, or payload data directly to the event object. This metadata is returned to you intact when the event is clicked. -- **Time Parsing**: `startDate` and `endDate` gracefully handle both Date-only formats (`YYYY-MM-DD`) for full-day events, and DateTime formats (`YYYY-MM-DDTHH:mm:ss`) for specific time blocks. +Switch between views using the `view` prop (`ECalendarViewType`). -## 🎨 Theming & Customization +- **Month (`"month"`)** — Traditional month grid. Events stack per day; overflow shows a "+X more" button. + - `showAdjacentMonths` — Toggle visibility of dates from adjacent months. + - `showWeekNumbers` — Display ISO week numbers per row. + - `weekStartsOn` / `weekEndsOn` — Configure which days begin and end the layout. +- **Week (`"week"`)** — 7-column time grid. All-day and multi-day events appear in a top banner. + - `showAllDayRow={false}` — Pushes all-day events into the time grid as 24-hour blocks. + - Overlapping events are tiled to prevent collision; use `eventOverlapOffset` for a layered style. +- **Day (`"day"`)** — Single-day time grid. Maximum horizontal space for event details. +- **Custom Days (`"customDays"`)** — Multi-day time grid starting from `selectedDate`. Set the number of days with `customDays`. +- **Schedule (`"schedule"`)** — Chronological event list grouped by date. Optimized for mobile or sidebar use. + - `renderScheduleSeparator` — Render a custom divider between date groups. -We provide two powerful layers of visual customization to ensure the calendar seamlessly integrates with your application's design system. +--- -### 1. The `theme` Prop (Quick Colors) +## Event Handling -The `theme` object allows you to quickly override the core accent colors of the calendar without touching CSS. +Pass an array of `CalendarEvent` objects to the `events` prop. -- **`today`**: Style the current real-world date. You can set the text `color` and the background `bgColor`. -- **`selected`**: Style the date that is currently selected by the user. -- **`default`**: Base colors for the calendar text and backgrounds. +- **Required fields** — `startDate` (string) and `title` (string). +- **Optional fields** — `id`, `endDate`, `style` (inline CSS), and any custom metadata via `[key: string]: unknown`. +- **Date-only events** — Use `YYYY-MM-DD` format for full-day events. +- **Timed events** — Use `YYYY-MM-DDTHH:mm:ss` format for specific time blocks. +- **Custom metadata** — Attach any extra fields (database IDs, descriptions, etc.) directly to the event object. These are returned intact in click callbacks. -### 2. The `classNames` Prop (Deep Customization) +--- -For complete control over the layout, borders, font weights, and spacing, you can pass a `classNames` object. This allows you to inject your own CSS classes (like Tailwind utility classes or CSS Modules) directly into specific DOM elements. +## Theming & Customization -- Example keys include `root`, `header`, `dayColumn`, `timeSlot`, `event`, `scheduleTitle`, and many more. +### The `theme` Prop -## 🕒 Time Formatting +Quickly override the calendar's core accent colors without touching CSS. -Global applications require flexible time display options. +- **`today`** — Style the current real-world date (`color`, `bgColor`). +- **`selected`** — Style the user-selected date. +- **`default`** — Base text and background colors. -- **12-Hour vs 24-Hour**: By default, time is shown in the 24-hour format (e.g., `14:00`). By passing the `is12Hour={true}` prop, all time indicators across the Week, Day, and Schedule views, as well as event tooltips, will automatically switch to the 12-hour AM/PM format (e.g., `02:00 PM`). -- **Day Name Formatting**: Use the `dayType` prop to dictate how the days of the week are displayed in the headers. Choose between `"full"` (Monday, Tuesday) or `"half"` (Mon, Tue). -- **Current Time Indicator**: Display a line indicating the current time in the Day and Week views by passing `showCurrentTime={true}`. You can also automatically scroll to this time when the view loads by passing `autoScrollToCurrentTime={true}`. +### The `classNames` Prop -## 👆 Interactive Callbacks +Inject your own CSS classes (Tailwind, CSS Modules, etc.) into specific DOM elements. Available keys: -Make your calendar reactive to user input by hooking into these extensive callback props: +`root`, `header`, `table`, `tableHeader`, `tableDate`, `weekNumber`, `event`, `selected`, `today`, `dayHeader`, `dayName`, `dayNumber`, `timeColumn`, `timeSlot`, `dayColumn`, `scheduleDateGroup`, `scheduleDateNumber`, `scheduleDateSubInfo`, `scheduleTime`, `scheduleTitle` -- `onDateClick(date: Date)`: Triggered when a user clicks an empty cell or day header. Use this to update your local state or open an "Add Event" modal. -- `onEventClick(event: CalendarEvent)`: Triggered when a user clicks on a rendered event. Perfect for opening event details or edit screens. -- `onViewChange(view: ECalendarViewType)`: Fired when the user uses the built-in header tabs to change the view (e.g., switching from Month to Week). -- `onNavigate(date: Date)`: Fired when the user clicks the "Next" or "Previous" buttons to flip through months/weeks, or uses the Month/Year dropdowns. -- `onMoreClick(date: Date, hiddenEvents?: CalendarEvent[])`: In the month view, if a day has too many events, a "+X more" text appears. Clicking it fires this callback, returning the specific date and an array of the events that were pushed out of view. +### Custom Renderers -## 📱 Responsive Layout +Replace core UI elements entirely with render props. -- The calendar utilizes CSS Grid and Flexbox to fluidly adapt to the width and height of its parent container. -- If no explicitly fixed `width` or `height` props are provided, it relies on a ResizeObserver hook to monitor the DOM wrapper and recalculates internal sizes automatically, ensuring events and columns always align perfectly to the available space. +- **`renderEvent(event)`** — Replaces the default event chip across all views. +- **`renderHeader(props)`** — Replaces the default navigation header. Receives `currentDate`, `view`, `onNavigate`, `onViewChange`. +- **`renderHourCell(date)`** — Customizes background hour slots in Day, Week, and Custom Days views. +- **`renderDateCell(props)`** — Customizes date headers (Week/Day) and date cells (Month). Receives `date`, `isToday`, `isSelected`, `isCurrentMonth`. +- **`renderScheduleSeparator(date)`** — Renders a custom separator between date groups in Schedule view. -## 🛡️ TypeScript Support +--- -`calendar-simple` was built ground-up in TypeScript. All props, callback payloads, and internal data structures are exported, ensuring your IDE provides full intellisense and compile-time safety. +## Time Formatting + +- **`is12Hour`** — Switch all time displays to 12-hour AM/PM format (default: 24-hour). +- **`dayType`** — Day name format: `"full"` (Monday) or `"half"` (Mon). +- **`minHour` / `maxHour`** — Constrain the visible time range (0–24) in Day and Week views. +- **`showCurrentTime`** — Display a line at the current time in Day and Week views. +- **`autoScrollToCurrentTime`** — Automatically scroll to the current time line on load. + +--- + +## Localization + +The calendar uses Luxon internally for all date formatting. + +- **`locale`** — Any valid Luxon locale string (e.g., `"fr"`, `"es-MX"`, `"zh"`, `"ar"`). All month names, day headers, and date strings adapt automatically. +- **`localeMessages`** — Translate built-in UI labels that aren't date-derived. + + Supported keys: `today`, `day`, `week`, `month`, `schedule`, `days`. + + ```tsx + localeMessages={{ today: "Hoy", schedule: "Agenda" }} + ``` + +- **`weekStartsOn`** — Set the first day of the week (0 = Sunday, 1 = Monday, etc.). Not inferred from `locale` — explicit control is intentional. +- **`is12Hour`** — When combined with a locale, time designators render in the appropriate regional format. + +--- + +## Right-to-Left (RTL) Support + +- **Auto-detection** — RTL layout activates automatically for: `ar`, `he`, `fa`, `ur`, `ps`, `sd`, `ckb`, `yi` locales. +- **Manual control** — Override with `direction="rtl"` or `direction="ltr"`. +- **CSS logical properties** — All layout uses `inset-inline-start`, `margin-inline-end`, etc., so the layout flips without separate stylesheets. +- **Full layout flip** — Navigation buttons, date pickers, event placement, and text alignment all adapt to RTL. + +--- + +## Color Schemes & Dark Mode + +- **`colorScheme="auto"`** (default) — Detects OS preference via `prefers-color-scheme` and updates live when the user toggles their system theme. +- **`colorScheme="light"`** — Forces the light palette. +- **`colorScheme="dark"`** — Forces the dark palette. + +Palettes are implemented as CSS custom properties scoped to `[data-color-scheme="light"]` and `[data-color-scheme="dark"]` on the calendar root. Override individual variables for custom theming: + +```css +[data-color-scheme="dark"] { + --primary-bg: #1a1a1a; + --primary-text: #ffffff; + --accent-color: #818cf8; +} + +[data-color-scheme="light"] { + --primary-bg: #ffffff; + --primary-text: #000000; + --accent-color: #2563eb; +} +``` + +The `theme` prop (inline per-element colors) takes final precedence over color scheme variables. No extra configuration or additional CSS imports are needed. + +--- + +## Interactive Callbacks + +- **`onDateClick(date)`** — Fired when an empty cell or day header is clicked. Use to update selected date or open an "Add Event" modal. +- **`onEventClick(event)`** — Fired when an event chip is clicked. Use to open event detail or edit screens. +- **`onViewChange(view)`** — Fired when the user switches views via the header tabs. + - `resetDateOnViewChange={true}` — Snaps the calendar back to today on each view switch. +- **`onNavigate(date)`** — Fired when the user navigates forward/backward or uses the month/year dropdowns. +- **`onMoreClick(date, hiddenEvents)`** — Fired when the "+X more" indicator is clicked in Month view. Returns the date and the array of overflowed events. +- **`creatable` + `onSlotClick(startDate, endDate)`** — Enable slot-click creation intent. + - In time-grid views: fires with `hour:00` → `hour+1:00`. + - In Month view: fires with `startOfDay` → `endOfDay`. + - Clicking an existing event still fires `onEventClick` — slot clicks do not bubble through events. + - `creatable` and `selectable` can be used together. + +--- + +## Responsive Layout + +The calendar has two layers of responsive behavior. + +### Container-width adaptation + +When no `width` or `height` props are provided, a `ResizeObserver` monitors the wrapper and feeds pixel dimensions back into the layout engine. Events, columns, and multi-day chips recalculate automatically as the container resizes. + +### CSS media-query breakpoints + +**768px — Tablet** + +| Area | What changes | +| ----------------- | ---------------------------------------------------------- | +| Header | Controls collapse into two rows; smaller font and padding | +| Month | Day-name header row shrinks to 30px; cell padding tightens | +| Month events | Chip height reduces to 1.25rem; font shrinks to 0.6875rem | +| Week / CustomDays | Columns fix to 100px wide, enabling horizontal scroll | +| Day | Day-number font reduces from 20px to 16px | + +**480px — Phone** + +| Area | What changes | +| ----------------- | ---------------------------------------------------------------------- | +| Month events | Chips become 6px colored dot-bars (no text, no "+N more") | +| Week / CustomDays | Each column expands to `calc(100vw − 90px)` — one day fills the screen | +| Schedule | Padding halves; time column narrows from 140px to 100px; fonts reduce | + +### Usage tip + +Drop the `width` prop and let the parent container control sizing for breakpoints to activate naturally: + +```tsx +
+ +
+``` + +--- + +## Performance Options + +Use these when rendering thousands of events simultaneously. + +- **`enableEnrichedEvents` + `enrichedEventsByDate`** — Pass a pre-mapped `Record` for O(1) day-rendering lookups instead of filtering a flat array. +- **`eventsAreSorted`** — Skip the initial sort when your input `events` are already ordered. +- **`isEventOrderingEnabled={false}`** — Bypass sweep-line collision detection and Tetris overlap resolution entirely. Events render linearly — fastest path for massive payloads at the cost of visual collision spacing. +- **`sortedMonthView`** — Enforce a custom priority / sort function for Month view Tetris slot allocation, or disable it entirely. + +--- + +## Keyboard Navigation & Accessibility + +### Keyboard Navigation + +- **Enter & Space** — Activate any interactive element (buttons, date cells, events). +- **Tab / Shift+Tab** — Move focus forward and backward through interactive elements in standard tab order. +- **Popover focus trap** — When a popover opens, Tab cycles within it; the last item wraps back to the first. +- **Escape** — Close open popovers; focus returns to the triggering element. +- **Focus indicators** — All keyboard-navigable elements show a visible `2px solid #005fcc` outline. + +### ARIA & Semantic HTML + +- Interactive non-button elements carry `role="button"`. +- Popover dialogs use `role="dialog"` with `aria-modal="true"`. +- All interactive elements have descriptive `aria-label` attributes. +- Major view containers are marked with `role="region"` and `aria-label`. +- Expandable elements use `aria-expanded` to communicate state. +- The Month view table uses `scope="col"` on headers. +- The header is a `