{"_id":"@daxence/flexy-date-picker","_rev":"3-8cdad80d91c1ed44f5f970ccc1c9d48f","name":"@daxence/flexy-date-picker","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@daxence/flexy-date-picker","version":"1.0.0","keywords":["react","datepicker","date-range","calendar","customizable","css-variables","dayjs"],"author":{"name":"Aymane D.","email":"https://github.com/daxence"},"license":"MIT","_id":"@daxence/flexy-date-picker@1.0.0","maintainers":[{"name":"daxence","email":"wissam.saidi.daif@gmail.com"}],"homepage":"https://github.com/daxence/flexy-date-picker#readme","bugs":{"url":"https://github.com/daxence/flexy-date-picker/issues"},"dist":{"shasum":"e87949199ff2e514561781f44eee2faae7ce0069","tarball":"https://registry.npmjs.org/@daxence/flexy-date-picker/-/flexy-date-picker-1.0.0.tgz","fileCount":9,"integrity":"sha512-rZsAtBcZZkvzC01vrcVQx5BpwJgQ+V5/Ziwlov5d3uwIOYa9SaAo5YSftgTbuRIKqviZEzfJZ+NR6fzvBT8NQQ==","signatures":[{"sig":"MEQCIHWVQeJPWgWy7dflbKOq4LKSnfvHxMPp8Uw4pEq0VlR1AiANBYCPcKUNsRqBwN67GDu1T3i2p8WKMdPx23aqqF9F6Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@daxence%2fflexy-date-picker@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1241528},"main":"./dist/flexy-date-picker.umd.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/flexy-date-picker.js","engines":{"node":">=20.9.0"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/flexy-date-picker.js","require":"./dist/flexy-date-picker.umd.cjs"},"./styles":"./dist/style.css"},"gitHead":"1adbdac446a7a58c26d7b672faa622e73e491a3c","scripts":{"dev":"vite","lint":"eslint .","test":"vitest run","build":"tsc -p tsconfig.build.json && vite build","prepare":"husky","preview":"vite preview","release":"semantic-release","lint:fix":"eslint . --fix","storybook":"storybook dev -p 6006","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","build-storybook":"storybook build"},"_npmUser":{"name":"daxence","email":"wissam.saidi.daif@gmail.com"},"repository":{"url":"git+https://github.com/daxence/flexy-date-picker.git","type":"git"},"_npmVersion":"11.16.0","description":"A fully customizable React date picker with single & range selection, injectable CSS theming, tooltips, and full event support.","directories":{},"lint-staged":{"*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}":["eslint --fix"]},"_nodeVersion":"22.23.2","dependencies":{"@floating-ui/react":"^0.27.19"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.3.0","dayjs":"^1.11.11","husky":"^9.1.7","jsdom":"^29.1.1","react":"^19.0.0","eslint":"^9.39.4","vitest":"^3.2.7","globals":"^17.6.0","react-dom":"^19.0.0","storybook":"^10.4.2","@eslint/js":"^9.39.4","typescript":"^5.5.0","@types/node":"^20.14.0","lint-staged":"^16.4.0","@types/react":"^19.0.0","@commitlint/cli":"^21.0.2","vite-plugin-dts":"^4.0.0","@storybook/react":"^10.4.2","@types/react-dom":"^19.0.0","semantic-release":"^25.0.3","@vitest/coverage-v8":"^3.2.7","eslint-plugin-react":"^7.37.5","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^4.3.0","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^13.1.5","@storybook/addon-a11y":"^10.4.2","@storybook/addon-docs":"^10.4.2","@storybook/react-vite":"^10.4.2","eslint-plugin-unicorn":"^65.0.1","@testing-library/react":"^16.3.2","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.16.2","eslint-plugin-jsx-a11y":"^6.10.2","@semantic-release/github":"^12.0.8","@testing-library/jest-dom":"^6.9.1","@typescript-eslint/parser":"^8.61.0","eslint-plugin-react-hooks":"^7.1.1","@semantic-release/changelog":"^6.0.3","@testing-library/user-event":"^14.6.1","@commitlint/config-conventional":"^21.0.2","@typescript-eslint/eslint-plugin":"^8.61.0","@semantic-release/commit-analyzer":"^13.0.1"},"peerDependencies":{"dayjs":">=1.11.0","react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/flexy-date-picker_1.0.0_1789228770029_0.6729027560060372","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@daxence/flexy-date-picker","version":"1.0.1","keywords":["react","datepicker","date-range","calendar","customizable","css-variables","dayjs"],"author":{"name":"Aymane D.","email":"https://github.com/daxence"},"license":"MIT","_id":"@daxence/flexy-date-picker@1.0.1","maintainers":[{"name":"daxence","email":"wissam.saidi.daif@gmail.com"}],"homepage":"https://github.com/daxence/flexy-date-picker#readme","bugs":{"url":"https://github.com/daxence/flexy-date-picker/issues"},"dist":{"shasum":"3def44a9b53ee3ce94337645c2c89f4158fbdfb6","tarball":"https://registry.npmjs.org/@daxence/flexy-date-picker/-/flexy-date-picker-1.0.1.tgz","fileCount":9,"integrity":"sha512-LwO+C8MEgzuBHmbo0jGi06JCd8riw/AKE+B1rYPSS207t3FYwo/CZ2OfYmVPDCKmj4zxjh4GoitsUXb856aeTw==","signatures":[{"sig":"MEUCIBZRU3PaCdLDsE6ZHlGRWbxAk9jEUrmpKp1eg1qQ2dwKAiEA7VI+eKQmj3EzDdpuTI4r1xsGGwm7WJEJnY0G+PwHCRY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@daxence%2fflexy-date-picker@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1242611},"main":"./dist/flexy-date-picker.umd.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/flexy-date-picker.js","engines":{"node":">=20.9.0"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/flexy-date-picker.js","require":"./dist/flexy-date-picker.umd.cjs"},"./styles":"./dist/style.css"},"gitHead":"85b528a4fd0d628ee98590fc02f6c150b1e41632","scripts":{"dev":"vite","lint":"eslint .","test":"vitest run","build":"tsc -p tsconfig.build.json && vite build","prepare":"husky","preview":"vite preview","release":"semantic-release","lint:fix":"eslint . --fix","storybook":"storybook dev -p 6006","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","build-storybook":"storybook build"},"_npmUser":{"name":"daxence","email":"wissam.saidi.daif@gmail.com"},"repository":{"url":"git+https://github.com/daxence/flexy-date-picker.git","type":"git"},"_npmVersion":"11.16.0","description":"A fully customizable React date picker with single & range selection, injectable CSS theming, tooltips, and full event support.","directories":{},"lint-staged":{"*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}":["eslint --fix"]},"_nodeVersion":"22.23.2","dependencies":{"@floating-ui/react":"^0.27.20"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.3.0","dayjs":"^1.11.11","husky":"^9.1.7","jsdom":"^29.1.1","react":"^19.0.0","eslint":"^9.39.5","vitest":"^3.2.7","globals":"^17.6.0","react-dom":"^19.0.0","storybook":"^10.4.2","@eslint/js":"^9.39.5","typescript":"^5.5.0","@types/node":"^20.14.0","lint-staged":"^16.4.0","@types/react":"^19.0.0","@commitlint/cli":"^21.0.2","vite-plugin-dts":"^4.0.0","@storybook/react":"^10.4.2","@types/react-dom":"^19.0.0","semantic-release":"^25.0.3","@vitest/coverage-v8":"^3.2.7","eslint-plugin-react":"^7.37.5","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^4.3.0","@semantic-release/npm":"^13.1.5","@storybook/addon-a11y":"^10.4.2","@storybook/addon-docs":"^10.4.2","@storybook/react-vite":"^10.4.2","eslint-plugin-unicorn":"^65.0.0","@testing-library/react":"^16.3.2","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.16.2","eslint-plugin-jsx-a11y":"^6.10.2","@semantic-release/github":"^12.0.8","@testing-library/jest-dom":"^6.9.1","@typescript-eslint/parser":"^8.61.0","eslint-plugin-react-hooks":"^7.1.1","@testing-library/user-event":"^14.6.1","@commitlint/config-conventional":"^21.0.2","@typescript-eslint/eslint-plugin":"^8.61.0","@semantic-release/commit-analyzer":"^13.0.1"},"peerDependencies":{"dayjs":">=1.11.0","react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/flexy-date-picker_1.0.1_1789246645046_0.20861838927382248","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@daxence/flexy-date-picker","version":"1.0.2","description":"A fully customizable React date picker with single & range selection, injectable CSS theming, tooltips, and full event support.","type":"module","main":"./dist/flexy-date-picker.umd.cjs","module":"./dist/flexy-date-picker.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/flexy-date-picker.js","require":"./dist/flexy-date-picker.umd.cjs","types":"./dist/index.d.ts"},"./styles":{"types":"./dist/styles.d.ts","default":"./dist/style.css"}},"scripts":{"dev":"vite","build":"tsc -p tsconfig.build.json && vite build","preview":"vite preview","lint":"eslint .","lint:fix":"eslint . --fix","test":"vitest run","test:coverage":"vitest run --coverage","storybook":"storybook dev -p 6006","build-storybook":"storybook build","release":"semantic-release","prepare":"husky","prepublishOnly":"npm run build"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"engines":{"node":">=20.9.0"},"peerDependencies":{"dayjs":">=1.11.0","react":">=18.0.0","react-dom":">=18.0.0"},"devDependencies":{"@commitlint/cli":"^21.0.2","@commitlint/config-conventional":"^21.0.2","@eslint/js":"^9.39.5","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/github":"^12.0.8","@semantic-release/npm":"^13.1.5","@storybook/addon-a11y":"^10.4.2","@storybook/addon-docs":"^10.4.2","@storybook/react":"^10.4.2","@storybook/react-vite":"^10.4.2","@testing-library/dom":"^10.4.1","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.2","@testing-library/user-event":"^14.6.1","@types/node":"^20.14.0","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@typescript-eslint/eslint-plugin":"^8.61.0","@typescript-eslint/parser":"^8.61.0","@vitejs/plugin-react":"^4.3.0","@vitest/coverage-v8":"^3.2.7","dayjs":"^1.11.11","eslint":"^9.39.5","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.16.2","eslint-plugin-jsx-a11y":"^6.10.2","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.1.1","eslint-plugin-unicorn":"^65.0.0","globals":"^17.6.0","husky":"^9.1.7","jsdom":"^29.1.1","lint-staged":"^16.4.0","react":"^19.0.0","react-dom":"^19.0.0","semantic-release":"^25.0.3","storybook":"^10.4.2","typescript":"^5.5.0","vite":"^5.3.0","vite-plugin-dts":"^4.0.0","vitest":"^3.2.7"},"dependencies":{"@floating-ui/react":"^0.27.20"},"lint-staged":{"*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}":["eslint --fix"]},"repository":{"type":"git","url":"git+https://github.com/daxence/flexy-date-picker.git"},"keywords":["react","datepicker","date-range","calendar","customizable","css-variables","dayjs"],"author":{"name":"Aymane D.","email":"https://github.com/daxence"},"license":"MIT","bugs":{"url":"https://github.com/daxence/flexy-date-picker/issues"},"homepage":"https://github.com/daxence/flexy-date-picker#readme","gitHead":"4cd0ee384faf3470a591c920b10956fefb059748","_id":"@daxence/flexy-date-picker@1.0.2","_nodeVersion":"22.23.2","_npmVersion":"11.16.0","dist":{"integrity":"sha512-avMKOGbHcg7s0/gRQ/DbjSNt/lOKuQcAVrnqGW2iXFP9NrmfddWnVYl5/naQG1pNcVavX8uNw6NDxb1S+BKKIg==","shasum":"bf60df4b2c9785667956e8be773281966265a4aa","tarball":"https://registry.npmjs.org/@daxence/flexy-date-picker/-/flexy-date-picker-1.0.2.tgz","fileCount":41,"unpackedSize":1283680,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@daxence%2fflexy-date-picker@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAbPx2ciZDikx8CMW6zNgjot0paRYTCA40iwRWx2OaiiAiBC+5oRI7iVKP4KMLvHovk/1laoXVCoqQsSG/zsuDUnSQ=="}]},"_npmUser":{"name":"daxence","email":"wissam.saidi.daif@gmail.com"},"directories":{},"maintainers":[{"name":"daxence","email":"wissam.saidi.daif@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/flexy-date-picker_1.0.2_1789263869046_0.5588830948309877"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-12T15:59:29.860Z","modified":"2026-09-13T01:44:29.514Z","1.0.0":"2026-09-12T15:59:30.167Z","1.0.1":"2026-09-12T20:57:25.190Z","1.0.2":"2026-09-13T01:44:29.188Z"},"bugs":{"url":"https://github.com/daxence/flexy-date-picker/issues"},"author":{"name":"Aymane D.","email":"https://github.com/daxence"},"license":"MIT","homepage":"https://github.com/daxence/flexy-date-picker#readme","keywords":["react","datepicker","date-range","calendar","customizable","css-variables","dayjs"],"repository":{"type":"git","url":"git+https://github.com/daxence/flexy-date-picker.git"},"description":"A fully customizable React date picker with single & range selection, injectable CSS theming, tooltips, and full event support.","maintainers":[{"name":"daxence","email":"wissam.saidi.daif@gmail.com"}],"readme":"# flexy-date-picker\n\n[![CI](https://github.com/daxence/flexy-date-picker/actions/workflows/ci.yml/badge.svg)](https://github.com/daxence/flexy-date-picker/actions/workflows/ci.yml)\n[![Release](https://github.com/daxence/flexy-date-picker/actions/workflows/release.yml/badge.svg)](https://github.com/daxence/flexy-date-picker/actions/workflows/release.yml)\n[![npm version](https://img.shields.io/npm/v/%40daxence%2Fflexy-date-picker.svg)](https://www.npmjs.com/package/@daxence/flexy-date-picker)\n[![npm downloads](https://img.shields.io/npm/dm/%40daxence%2Fflexy-date-picker.svg)](https://www.npmjs.com/package/@daxence/flexy-date-picker)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![React](https://img.shields.io/badge/react-%3E%3D18-61dafb.svg)](https://react.dev)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](https://www.typescriptlang.org/)\n[![Test coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](#quality--testing)\n[![Conventional Commits](https://img.shields.io/badge/commits-conventional-fe5196.svg)](https://www.conventionalcommits.org)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)\n\nA **fully customizable** React date & time picker with single / range selection, an optional time picker, injectable CSS theming, tooltips, and a complete event API. Built with TypeScript, Day.js, and zero hard-coded visual opinions — every color, radius, and slot can be overridden.\n\n![Desktop calendar](./screenshots/desktop-calendar.png)\n\n\n## Table of contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Live examples](#live-examples)\n- [Components](#components)\n  - [`DatePicker`](#datepicker)\n  - [`TimePicker`](#timepicker-standalone)\n  - [`CalendarLegend`](#calendarlegend)\n- [Theming via CSS custom properties](#theming-via-css-custom-properties)\n- [Per-slot class / style injection](#per-slot-class--style-injection)\n- [Tooltips](#tooltips)\n- [Disabled dates](#disabled-dates)\n- [Date + time picker](#date--time-picker)\n- [Per-date time schedules](#per-date-time-schedules)\n- [Range picker](#range-picker)\n- [Custom renderers](#custom-renderers)\n- [Accessibility](#accessibility)\n- [Quality & testing](#quality--testing)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n- ⚛️ React 19 + TypeScript, works with any React ≥ 18 host app\n- 📅 [Day.js](https://day.js.org/) under the hood — no moment, no deprecated libs\n- 🔀 Single-date and date-range selection modes\n- ⏰ Optional built-in time picker (uniform step, custom ranges, or full per-date schedules) and a fully standalone `TimePicker` component\n- 🎨 CSS custom properties — inject any color/shape/spacing without a build step\n- 🧩 Per-slot `classNames` and `styles` props for surgical overrides (30+ slots)\n- 🌍 Locale & RTL-friendly via dayjs locales (`fr`, `ar`, …) and configurable week start\n- 📣 Full event API: `onBeforeSelect`, `onAfterSelect`, `onDateChange`, `onRangeChange`, `onTimeChange`, `onMonthChange`, `onYearChange`, `onDayHover`, `onOpen`, `onClose`, `onClear`\n- 💬 Tooltip support (string, `ReactNode`, or render-prop with rich per-day context)\n- 🖌️ Custom renderers for days, nav buttons, and the input trigger\n- 🧱 `inline` mode to embed the calendar directly in a layout (no popover)\n- ♿ Accessible by default: `role=\"grid\"`, `aria-pressed`, `aria-disabled`, `aria-label`, keyboard-navigable listboxes\n- 🚀 Vite library mode — ESM + UMD builds, tree-shakeable, fully typed\n\n---\n\n## Installation\n\n```bash\nnpm install @daxence/flexy-date-picker dayjs\n```\n\n```bash\npnpm add @daxence/flexy-date-picker dayjs\n```\n\n`dayjs` is a peer dependency and must be installed alongside the package. `react` and `react-dom` (≥ 18) are also required as peer dependencies.\n\n## Quick start\n\n```tsx\nimport { DatePicker } from '@daxence/flexy-date-picker';\nimport '@daxence/flexy-date-picker/styles'; // import the CSS once, anywhere in your app\n\nfunction App() {\n  return <DatePicker mode=\"single\" onDateChange={(date) => console.log(date)} />;\n}\n```\n\n## Live examples\n\n- **Demo app** — run `npm run dev` for a playground covering every prop combination (booking calendars, day content injection, multilingual pickers, time schedules, custom themes, and more). Each demo section includes a \"View code\" toggle with the exact snippet used.\n- **Storybook** — run `npm run storybook` for isolated, documented stories per component (`DatePicker`, `TimePicker`, `CalendarLegend`), including controls for every prop.\n\n---\n\n## Components\n\n### `DatePicker`\n\nThe main component. Supports single-date and range selection, an optional time picker, and full styling/theming control.\n\n#### Selection & value props\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `mode` | `'single' \\| 'range'` | `'single'` | Selection mode |\n| `value` | `Dayjs \\| null` | — | Controlled single value |\n| `defaultValue` | `Dayjs \\| null` | — | Uncontrolled default |\n| `rangeValue` | `{ start, end }` | — | Controlled range value |\n| `defaultRangeValue` | `{ start, end }` | — | Uncontrolled range default |\n\n#### Formatting & locale\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `format` | `string` | `'MMM D, YYYY'` | dayjs format string |\n| `locale` | `string` | — | dayjs locale (e.g. `'fr'`, `'ar'`) |\n| `lang` | `string` | — | Alias for `locale` |\n| `weekStartsOn` | `0–6` | `0` (Sunday) | First day of week |\n\n#### Calendar display\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `numberOfMonths` | `number` | `1` | Months displayed at once |\n| `showMonthYearSelectors` | `boolean` | `true` | Show month/year dropdown selectors |\n| `showAdjacentMonthDays` | `boolean` | `false` | Show adjacent-month days in the grid |\n| `showOutsideDays` | `boolean` | — | Deprecated alias for `showAdjacentMonthDays` |\n| `highlightToday` | `boolean` | `true` | Highlight today |\n| `inline` | `boolean` | `false` | Render the calendar directly, without an input/popover |\n| `placement` | `'top' \\| 'bottom' \\| 'auto'` | `'auto'` | Popover placement |\n\n#### Constraints & state\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `minDate` / `maxDate` | `Dayjs` | — | Earliest / latest selectable date |\n| `disabled` | `DisabledConfig \\| boolean` | — | Disable dates (`before`, `after`, `dates`, `predicate`) |\n| `isDisabled` | `boolean` | `false` | Disable the whole picker |\n| `readOnly` | `boolean` | `false` | Read-only mode |\n| `placeholder` | `string \\| [string, string]` | — | Input placeholder(s) |\n| `clearable` | `boolean` | `true` | Show the clear button |\n\n#### Time picker (built into `DatePicker`)\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `enableTime` | `boolean` | `false` | Show a time slot list alongside the calendar |\n| `timeStep` | `number` | `15` | Uniform slot interval in minutes |\n| `timeFormat` | `string` | `'HH:mm'` | dayjs format for time labels |\n| `minTime` / `maxTime` | `string` (`HH:mm`) | `'00:00'` / `'23:59'` | Bounds for generated slots |\n| `disabledTime` | `DisabledTimeConfig` | — | Disable slots by weekday, date, or predicate |\n| `timeSchedule` | `Record<'YYYY-MM-DD', DateSchedule>` | — | Per-date schedule override (multiple ranges, custom steps, unavailability) |\n| `defaultTimeSchedule` | `DateSchedule` | — | Fallback schedule for dates not present in `timeSchedule` |\n\n#### Styling\n\n| Prop | Type | Description |\n|---|---|---|\n| `theme` | `DatePickerTheme` | CSS variable overrides (see [theming](#theming-via-css-custom-properties)) |\n| `classNames` | `DatePickerClassNames` | Per-slot className overrides (see [slots](#per-slot-class--style-injection)) |\n| `styles` | `DatePickerStyles` | Per-slot inline style overrides |\n| `className` / `style` | `string` / `CSSProperties` | Extra class/style on the root element |\n\n#### Custom renderers\n\n| Prop | Signature | Description |\n|---|---|---|\n| `renderDay` | `(date: Dayjs, props: TooltipRenderProps) => ReactNode` | Fully custom day cell content |\n| `renderNavButton` | `(direction: 'prev' \\| 'next', onClick: () => void) => ReactNode` | Custom prev/next month buttons |\n| `renderInput` | `(props: RenderInputProps) => ReactNode` | Fully custom trigger input |\n\n#### Tooltip\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `tooltip` | `string \\| ReactNode \\| (props: TooltipRenderProps) => ReactNode` | — | Day tooltip content |\n| `tooltipDelay` | `number` | `300` | Tooltip delay (ms) |\n\n#### Events\n\n| Prop | Signature | Description |\n|---|---|---|\n| `onDateChange` | `(date: Dayjs \\| null) => void` | Fires on single-date selection/clear |\n| `onRangeChange` | `(range: { start, end }) => void` | Fires on range selection/clear |\n| `onTimeChange` | `(date: Dayjs \\| null, time: string) => void` | Fires when a time slot is picked |\n| `onBeforeSelect` | `(date: Dayjs, meta: DateSelectionMeta) => boolean \\| void` | Return `false` to cancel a selection |\n| `onAfterSelect` | `(date: Dayjs, meta: DateSelectionMeta) => void` | Fires right after a selection is committed |\n| `onMonthChange` | `(month: number, year: number) => void` | Fires when the visible month changes |\n| `onYearChange` | `(year: number) => void` | Fires when the visible year changes |\n| `onDayHover` | `(date: Dayjs \\| null) => void` | Fires on day hover (range preview) |\n| `onOpen` / `onClose` | `() => void` | Popover open/close |\n| `onClear` | `() => void` | Fires when the clear button is used |\n\n---\n\n### `TimePicker` (standalone)\n\nA self-contained time-of-day picker that can be used independently of `DatePicker` — as a popover-triggering input or rendered inline.\n\n```tsx\nimport { TimePicker } from '@daxence/flexy-date-picker';\n\n<TimePicker\n  value={time}\n  onChange={setTime}\n  timeStep={15}\n  minTime=\"08:00\"\n  maxTime=\"18:00\"\n/>\n```\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `value` | `string` (`HH:mm`) | — | Controlled value |\n| `defaultValue` | `string` | — | Uncontrolled initial value |\n| `onChange` | `(time: string) => void` | — | Called with the selected `HH:mm`, or `''` after clear |\n| `timeStep` | `number` | `15` | Uniform slot interval in minutes (ignored when `schedule` is set) |\n| `minTime` / `maxTime` | `string` (`HH:mm`) | `'00:00'` / `'23:59'` | Slot bounds (ignored when `schedule` is set) |\n| `schedule` | `DateSchedule` | — | Schedule-based generation: multiple time ranges, each with its own step, with gaps between them producing no slots. Overrides `timeStep`/`minTime`/`maxTime` |\n| `timeFormat` | `string` | `'HH:mm'` | dayjs format for slot labels and display |\n| `placeholder` | `string` | — | Placeholder text when no value is selected |\n| `locale` | `string` | — | dayjs locale |\n| `date` | `Dayjs` | today | Reference date used by `disabledTime` |\n| `disabledTime` | `DisabledTimeConfig` | — | Disable slots by weekday, date, or predicate |\n| `inline` | `boolean` | `false` | Render the slot list directly, without an input/popover |\n| `isDisabled` | `boolean` | `false` | Disable all interaction |\n| `readOnly` | `boolean` | `false` | Show the value but prevent changes |\n| `clearable` | `boolean` | `true` | Show the clear button |\n| `onOpen` / `onClose` / `onClear` | `() => void` | — | Popover lifecycle events |\n| `className` / `style` | `string` / `CSSProperties` | — | Root element overrides |\n\n---\n\n### `CalendarLegend`\n\nA small presentational component for labeling calendar states (e.g. availability legends in booking UIs).\n\n```tsx\nimport { CalendarLegend } from '@daxence/flexy-date-picker';\n\n<CalendarLegend\n  items={[\n    { label: 'Available', className: 'fldp-legend-box--available' },\n    { label: 'Selected', className: 'fldp-legend-box--selected' },\n    { label: 'Unavailable', className: 'fldp-legend-box--unavailable' },\n  ]}\n/>\n```\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `items` | `{ label: string \\| ReactNode; className: string }[]` | 5 default French labels | Legend rows, each rendered as a colored box + label |\n| `className` | `string` | — | Extra class on the root element |\n| `style` | `CSSProperties` | — | Extra inline style on the root element |\n\n---\n\n## Theming via CSS custom properties\n\nOverride any token on a parent element:\n\n```css\n.my-app {\n  --fldp-primary: #7c3aed;\n  --fldp-primary-hover: #6d28d9;\n  --fldp-muted: #ede9fe;\n  --fldp-border-radius: 12px;\n  --fldp-day-border-radius: 50%;\n  --fldp-font-family: 'Inter', sans-serif;\n}\n```\n\nOr pass the `theme` prop:\n\n```tsx\n<DatePicker\n  lang=\"fr\"\n  weekStartsOn={1}\n  theme={{\n    primary: '#7c3aed',\n    primaryHover: '#6d28d9',\n    muted: '#ede9fe',\n    borderRadius: '12px',\n    dayBorderRadius: '50%',\n  }}\n/>\n```\n\n### All available tokens\n\n| Token | Default | Description |\n|---|---|---|\n| `--fldp-primary` | `#3b82f6` | Accent / selected color |\n| `--fldp-primary-hover` | `#2563eb` | Accent hover |\n| `--fldp-primary-foreground` | `#ffffff` | Text on accent background |\n| `--fldp-background` | `#ffffff` | Popover background |\n| `--fldp-surface` | `rgba(255, 255, 255, 0.88)` | Trigger + menu surface |\n| `--fldp-surface-strong` | `rgba(255, 255, 255, 0.98)` | Strong surface tone |\n| `--fldp-foreground` | `#0f172a` | Default text color |\n| `--fldp-muted` | `#eff6ff` | In-range highlight |\n| `--fldp-accent-soft` | `rgba(15, 118, 110, 0.12)` | Soft hover/accent state |\n| `--fldp-muted-foreground` | `#64748b` | Placeholder / weekday text |\n| `--fldp-border` | `#e2e8f0` | Border color |\n| `--fldp-input-background` | `#ffffff` | Input background |\n| `--fldp-today-color` | `#3b82f6` | Today dot + text |\n| `--fldp-border-radius` | `8px` | Outer radius |\n| `--fldp-day-border-radius` | `6px` | Day cell radius |\n| `--fldp-shadow` | `…` | Popover box shadow |\n| `--fldp-font-family` | system-ui | Font family |\n| `--fldp-font-size` | `14px` | Base font size |\n| `--fldp-disabled-opacity` | `0.38` | Disabled element opacity |\n| `--fldp-transition-duration` | `150ms` | Animation speed |\n| `--fldp-z-index` | `9999` | Popover z-index |\n\n---\n\n## Per-slot class / style injection\n\nEvery visual slot accepts a className and inline style:\n\n```tsx\n<DatePicker\n  classNames={{\n    calendar: 'my-calendar',\n    daySelected: 'my-selected-day',\n    popover: 'my-popover',\n  }}\n  styles={{\n    popover: { background: '#fefce8' },\n    dayInRange: { fontWeight: 700 },\n  }}\n/>\n```\n\nAvailable slots: `root`, `inputWrapper`, `input`, `separator`, `clearButton`, `popover`, `calendar`, `calendarHeader`, `navButton`, `monthYearLabel`, `weekdays`, `weekday`, `daysGrid`, `day`, `dayToday`, `daySelected`, `dayRangeStart`, `dayRangeEnd`, `dayInRange`, `dayDisabled`, `dayOutside`, `dayHovered`, `timePicker`, `timeTitle`, `timeSlotList`, `timeSlot`, `timeSlotSelected`, `timeSlotDisabled`, `tooltip`.\n\n---\n\n## Tooltips\n\n```tsx\n// Static string\n<DatePicker tooltip=\"Click to select\" />\n\n// ReactNode\n<DatePicker tooltip={<strong>Pick a date</strong>} />\n\n// Render prop — full control, including hotel-style \"X nights\" hints\n<DatePicker\n  mode=\"range\"\n  tooltip={({ date, isToday, isDisabled, isRangeStart, isRangeEnd, isInRange, nights }) => {\n    if (isDisabled) return '🚫 Unavailable';\n    if (nights && isRangeEnd) return `${nights} night${nights > 1 ? 's' : ''}`;\n    if (isRangeStart) return '📌 Check-in';\n    if (isRangeEnd) return '🏁 Check-out';\n    if (isToday) return '📅 Today';\n    return date.format('dddd, MMM D');\n  }}\n  tooltipDelay={200}\n/>\n```\n\n---\n\n## Disabled dates\n\n```tsx\n<DatePicker\n  disabled={{\n    before: dayjs('2024-01-01'),   // disable all before Jan 1\n    after: dayjs('2024-12-31'),    // disable all after Dec 31\n    dates: [dayjs('2024-06-15')],  // specific dates\n    predicate: (d) => d.day() === 0 || d.day() === 6, // weekends\n  }}\n/>\n```\n\n---\n\n## Date + time picker\n\nCombine calendar selection with a time slot list in one component:\n\n```tsx\n<DatePicker\n  mode=\"single\"\n  value={meetingDate}\n  onDateChange={setMeetingDate}\n  onTimeChange={(date, time) => console.log(date, time)}\n  enableTime\n  timeStep={15}\n  timeFormat=\"HH:mm\"\n  minTime=\"08:00\"\n  maxTime=\"20:00\"\n  disabledTime={{\n    weekdays: { 0: ['08:00', '08:15', '08:30'], 6: ['08:00', '08:15', '08:30'] },\n  }}\n/>\n```\n\n## Per-date time schedules\n\nGive individual dates their own set of time ranges — with gaps, different step intervals, or full unavailability — via `timeSchedule` / `defaultTimeSchedule`:\n\n```tsx\n<DatePicker\n  mode=\"single\"\n  enableTime\n  defaultTimeSchedule={{ ranges: [{ from: '09:00', to: '17:00', step: 30 }] }}\n  timeSchedule={{\n    '2026-06-15': { unavailable: true }, // fully booked, no slots shown\n    '2026-06-16': {\n      ranges: [\n        { from: '08:00', to: '12:00', step: 15 },\n        { from: '14:00', to: '16:00', step: 5 },\n        { from: '16:00', to: '18:00', step: 10 },\n      ],\n    },\n  }}\n/>\n```\n\n---\n\n## Range picker\n\n```tsx\nconst [range, setRange] = useState({ start: null, end: null });\n\n<DatePicker\n  mode=\"range\"\n  rangeValue={range}\n  onRangeChange={setRange}\n  numberOfMonths={2}\n/>\n```\n\n---\n\n## Custom renderers\n\n```tsx\n<DatePicker\n  renderDay={(date, { isSelected }) => (\n    <span style={{ fontWeight: isSelected ? 700 : 400 }}>{date.date()}</span>\n  )}\n  renderNavButton={(direction, onClick) => (\n    <button onClick={onClick}>{direction === 'prev' ? '←' : '→'}</button>\n  )}\n  renderInput={({ value, onClick, isOpen }) => (\n    <button onClick={onClick}>{value || 'Pick a date'} {isOpen ? '▲' : '▼'}</button>\n  )}\n/>\n```\n\n---\n\n## Accessibility\n\nThe calendar grid, navigation, and inputs follow WAI-ARIA authoring practices out of the box:\n\n- Day grid uses `role=\"grid\"` / `role=\"row\"` / `role=\"columnheader\"`, with each day exposing `aria-label` (full localized date), `aria-pressed` (selected state), and `aria-disabled`.\n- Month/year navigation exposes `aria-label=\"Previous month\"` / `\"Next month\"`, and the month/year dropdowns use `role=\"listbox\"` / `role=\"option\"` with `aria-expanded` / `aria-selected`.\n- The input trigger uses `role=\"button\"`, `aria-haspopup=\"dialog\"`, and `aria-expanded`; the popover itself uses `role=\"dialog\"`.\n- The standalone time picker region is exposed via `role=\"region\"` with a descriptive `aria-label`.\n- Decorative icons/carets are marked `aria-hidden=\"true\"` so they're skipped by screen readers.\n\n---\n\n## Quality & testing\n\nThis project is built and gated the way a production library should be:\n\n| Check | Status |\n|---|---|\n| **Unit test coverage** | 100% statements / branches / functions / lines, enforced as a hard CI gate (`vitest --coverage`, thresholds in [vite.config.ts](./vite.config.ts)) |\n| **Test suite** | 100+ tests across every component, context, and utility (`vitest`, React Testing Library) |\n| **Type safety** | `strict` TypeScript, zero `any` leaking through the public API, full `.d.ts` output |\n| **Linting** | ESLint with `typescript-eslint`, `react-hooks`, `jsx-a11y`, and `unicorn` rule sets, zero warnings allowed |\n| **Accessibility** | WAI-ARIA roles/attributes on every interactive element (see [Accessibility](#accessibility)) and an automated `@storybook/addon-a11y` check on every story |\n| **Bundle** | Tree-shakeable ESM + UMD output via `vite-plugin-dts`, ~35 kB gzipped for the full library |\n| **Commits & releases** | [Conventional Commits](https://www.conventionalcommits.org/) enforced via `commitlint` + `husky`, versioning and changelog automated with `semantic-release` |\n\nEvery prop, event, and edge case documented above (disabled dates, per-date time schedules, range reordering, keyboard navigation, placement auto-resolution, etc.) is backed by a dedicated test — the goal is that the README and the test suite never drift apart.\n\n```bash\nnpm run test            # Run the full suite\nnpm run test:coverage   # Run with the 100% coverage gate enforced\nnpm run lint             # Zero-warning lint pass\n```\n\n---\n\n## Development\n\n```bash\nnpm install\nnpm run dev              # Vite dev server with the demo app (with copy-pasteable code snippets)\nnpm run build             # Build the library to /dist\nnpm run test               # Run the test suite\nnpm run test:coverage      # Run tests with coverage\nnpm run storybook          # Explore components interactively\nnpm run build-storybook    # Build a static Storybook site\nnpm run lint               # Lint the codebase\n```\n\n## Contributing\n\nIssues and pull requests are welcome. Please:\n\n1. Keep the codebase feature-based (`src/features/<feature>/...`) and add tests + Storybook stories for new behavior.\n2. Run `npm run lint` and `npm run test` before opening a PR.\n3. Follow [Conventional Commits](https://www.conventionalcommits.org/) — releases are automated with `semantic-release`.\n\n## License\n\n[MIT](./LICENSE) © [Daxence Solutions](https://github.com/daxence)","readmeFilename":"README.md"}