commit b4abe210600607f26ed4bab66acfd42217e28698 Author: Guillermo Pages Date: Thu Feb 6 17:55:54 2025 +0100 feat: first commit, working diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b86588 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# Minimal Calendar Component + +A flexible and customizable calendar component built with React, TypeScript, and styled-components. + +## Features + +- 📅 Year, month, and week views +- 🎨 Multiple header styles (expanded, compacted, tiny, none) +- 🎯 Customizable month cutoff behavior +- 🎭 Weekend highlighting +- 📱 Fully responsive design +- 🔧 TypeScript support +- 💅 Styled with styled-components + +## Installation + +```bash +npm install date-fns styled-components +``` + +## Basic Usage + +```tsx +import { Year } from './components/calendar/Year'; + +function App() { + return ( + + ); +} +``` + +## Advanced Usage + +### With Date Selection + +```tsx +import { useState } from 'react'; +import { Year } from './components/calendar/Year'; + +function App() { + const [selectedDate, setSelectedDate] = useState(); + + return ( + + ); +} +``` + +### With Controls + +```tsx +import { useState } from 'react'; +import { Year } from './components/calendar/Year'; +import { Controls } from './components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType } from './types/calendar'; + +function App() { + const [headerStyle, setHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + + return ( + <> + + + + ); +} +``` + +## Component Props + +### Year Component + +| Prop | Type | Description | +|------|------|-------------| +| year | number | The year to display | +| dayHeaderStyle | 'expanded' \| 'compacted' \| 'tiny' \| 'none' | Style of day headers | +| monthDayOfWeekHeaderStyle | HeaderStyle (optional) | Style of month day headers | +| monthCutoff | 'dimmed' \| 'truncate' \| undefined | How to handle days from other months | +| weekendDays | number[] | Array of day indices to mark as weekends (0-6) | +| selectedDate | Date (optional) | Currently selected date | +| rangeStart | Date (optional) | Start date for range selection | +| rangeEnd | Date (optional) | End date for range selection | +| onDateSelect | (date: Date) => void (optional) | Date selection callback | +| compact | boolean (optional) | Use compact layout | + +### Controls Component + +| Prop | Type | Description | +|------|------|-------------| +| headerStyle | HeaderStyle | Current header style | +| monthCutoff | MonthCutoffType | Current month cutoff type | +| onHeaderStyleChange | (type: HeaderStyle) => void | Header style change handler | +| onMonthCutoffChange | (type: MonthCutoffType) => void | Month cutoff change handler | + +## Styling + +The calendar uses styled-components for styling. You can customize the appearance by: + +1. Using the built-in props +2. Extending the styled components +3. Wrapping components with custom styled containers + +Example of custom styling: + +```tsx +import styled from 'styled-components'; +import { Year } from './components/calendar/Year'; + +const CustomCalendarContainer = styled.div` + padding: 2rem; + background: #fafafa; + + // Custom styles for the calendar + .month-title { + color: #1a73e8; + } +`; + +function App() { + return ( + + + + ); +} +``` + +## Header Styles + +- **expanded**: Full day names (e.g., "MONDAY") +- **compacted**: Three-letter day names (e.g., "MON") +- **tiny**: Single letter day names (e.g., "M") +- **none**: No day headers, only numbers + +## Month Cutoff Options + +- **dimmed**: Show days from other months with reduced opacity +- **truncate**: Hide days from other months +- **undefined**: Show all days normally + +## Browser Support + +The calendar component supports all modern browsers: + +- Chrome (latest) +- Firefox (latest) +- Safari (latest) +- Edge (latest) + +## License + +MIT diff --git a/index.html b/index.html new file mode 100644 index 0000000..eb972f4 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Calendar + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b8f024b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1739 @@ +{ + "name": "react-calendar", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "react-calendar", + "version": "0.0.0", + "dependencies": { + "classnames": "^2.3.2", + "date-fns": "^2.30.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.3", + "sass": "^1.69.5", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", + "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.7", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", + "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", + "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/immutable": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass": { + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz", + "integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==", + "dev": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", + "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7772e33 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-calendar", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "date-fns": "^2.30.0", + "classnames": "^2.3.2" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.3", + "typescript": "^5.0.2", + "vite": "^4.4.5", + "sass": "^1.69.5" + } +} diff --git a/sorted_colors.txt b/sorted_colors.txt new file mode 100644 index 0000000..d2b11c2 --- /dev/null +++ b/sorted_colors.txt @@ -0,0 +1,86 @@ + +$border-color; +$border-color; // replaced shared.$border-color +$content-normal-bg; // replaced shared.$content-background-color +$content-normal-bg; // replaced shared.$content-background-color-selecting +$content-normal-text; // replaced shared.$content-color +$day-content-defaultmode-defaultstate-background-color; +$day-content-defaultmode-defaultstate-color; +$day-content-defaultmode-defaultstate-color; // replaced shared.$day-content-defaultmode-defaultstate-color +$day-content-defaultmode-selectedstate-midrange-background-color; +$day-content-defaultmode-selectedstate-midrange-color; +$day-content-defaultmode-selectedstate-rangeend-background-color; +$day-content-defaultmode-selectedstate-rangeend-color; +$day-content-defaultmode-selectedstate-rangeend-hover-background-color; +$day-content-defaultmode-selectedstate-rangestart-background-color; +$day-content-defaultmode-selectedstate-rangestart-color; +$day-content-defaultmode-selectedstate-rangestart-hover-background-color; +$day-content-defaultmode-selectingstate-midrange-background-color; +$day-content-defaultmode-selectingstate-midrange-color; +$day-content-defaultmode-selectingstate-midrange-hover-background-color; +$day-content-defaultmode-selectingstate-rangeend-background-color; +$day-content-defaultmode-selectingstate-rangeend-color; +$day-content-defaultmode-selectingstate-rangeend-hover-background-color; +$day-content-defaultmode-selectingstate-rangestart-background-color; +$day-content-defaultmode-selectingstate-rangestart-color; +$day-content-defaultmode-selectingstate-rangestart-hover-background-color; +$day-content-greyedmode-defaultstate-background-color; +$day-content-greyedmode-defaultstate-color; +$day-content-greyedmode-selectedstate-midrange-background-color; +$day-content-greyedmode-selectedstate-midrange-color; +$day-content-greyedmode-selectedstate-rangeend-background-color; +$day-content-greyedmode-selectedstate-rangeend-color; +$day-content-greyedmode-selectedstate-rangeend-hover-background-color; +$day-content-greyedmode-selectedstate-rangestart-background-color; +$day-content-greyedmode-selectedstate-rangestart-color; +$day-content-greyedmode-selectedstate-rangestart-hover-background-color; +$day-content-greyedmode-selectingstate-midrange-background-color; +$day-content-greyedmode-selectingstate-midrange-color; +$day-content-greyedmode-selectingstate-rangeend-background-color; +$day-content-greyedmode-selectingstate-rangeend-color; +$day-content-greyedmode-selectingstate-rangeend-hover-background-color; +$day-content-greyedmode-selectingstate-rangestart-background-color; +$day-content-greyedmode-selectingstate-rangestart-color; +$day-content-greyedmode-selectingstate-rangestart-hover-background-color; +$day-header-defaultmode-defaultstate-background-color; +$day-header-defaultmode-defaultstate-color; +$day-header-defaultmode-selectedstate-midrange-background-color; +$day-header-defaultmode-selectedstate-midrange-color; +$day-header-defaultmode-selectedstate-rangeend-background-color; +$day-header-defaultmode-selectedstate-rangeend-color; +$day-header-defaultmode-selectedstate-rangeend-hover-background-color; +$day-header-defaultmode-selectedstate-rangestart-background-color; +$day-header-defaultmode-selectedstate-rangestart-color; +$day-header-defaultmode-selectedstate-rangestart-hover-background-color; +$day-header-defaultmode-selectingstate-midrange-background-color; +$day-header-defaultmode-selectingstate-midrange-color; +$day-header-defaultmode-selectingstate-rangeend-background-color; +$day-header-defaultmode-selectingstate-rangeend-color; +$day-header-defaultmode-selectingstate-rangeend-hover-background-color; +$day-header-defaultmode-selectingstate-rangestart-background-color; +$day-header-defaultmode-selectingstate-rangestart-color; +$day-header-defaultmode-selectingstate-rangestart-hover-background-color; +$day-header-greyedmode-defaultstate-background-color; +$day-header-greyedmode-defaultstate-background-color; // replaced shared.$header-background-color-greyed +$day-header-greyedmode-defaultstate-color; +$day-header-greyedmode-defaultstate-color; // replaced shared.$header-color-greyed +$day-header-greyedmode-selectedstate-midrange-background-color; +$day-header-greyedmode-selectedstate-midrange-color; +$day-header-greyedmode-selectedstate-rangeend-background-color; +$day-header-greyedmode-selectedstate-rangeend-color; +$day-header-greyedmode-selectedstate-rangeend-hover-background-color; +$day-header-greyedmode-selectedstate-rangestart-background-color; +$day-header-greyedmode-selectedstate-rangestart-color; +$day-header-greyedmode-selectedstate-rangestart-hover-background-color; +$day-header-greyedmode-selectingstate-midrange-background-color; +$day-header-greyedmode-selectingstate-midrange-color; +$day-header-greyedmode-selectingstate-rangeend-background-color; +$day-header-greyedmode-selectingstate-rangeend-color; +$day-header-greyedmode-selectingstate-rangeend-hover-background-color; +$day-header-greyedmode-selectingstate-rangestart-background-color; +$day-header-greyedmode-selectingstate-rangestart-color; +$day-header-greyedmode-selectingstate-rangestart-hover-background-color; +$focus-blue; +$header-normal-bg; // replaced shared.$header-background-color +$header-normal-text; // replaced shared.$header-color +scss) diff --git a/src/App.module.scss b/src/App.module.scss new file mode 100644 index 0000000..a1b2fd1 --- /dev/null +++ b/src/App.module.scss @@ -0,0 +1,48 @@ +.container { + padding: 1rem; + width: 100%; + margin: 0 auto; + min-height: 100vh; + background: #fafafa; + + @media (min-width: 768px) { + padding: 2rem; + } +} + +.section { + margin-bottom: 4rem; + padding: 2rem; + background: white; + border-radius: 12px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + + &:last-child { + margin-bottom: 0; + } +} + +.sectionTitle { + font-size: 2rem; + margin: 0 0 1rem; + color: #2c2c2c; +} + +.sectionDescription { + font-size: 1rem; + color: #666; + margin: 0 0 2rem; +} + +.demoContainer { + margin-top: 2rem; +} + +.rangeInfo { + margin-top: 1rem; + padding: 1rem; + background: #f5f5f5; + border-radius: 8px; + font-family: monospace; + font-size: 0.9rem; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..9593eca --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { DateRangePicker } from './examples/DateRangePicker'; +import { SingleMonth } from './examples/SingleMonth'; +import { WeekView } from './examples/WeekView'; +import { CompactYear } from './examples/CompactYear'; +import styles from './App.module.scss'; + +const App: React.FC = () => { + return ( +
+ + + + +
+ ); +}; + +export default App; diff --git a/src/components/calendar/Controls.module.scss b/src/components/calendar/Controls.module.scss new file mode 100644 index 0000000..d00f744 --- /dev/null +++ b/src/components/calendar/Controls.module.scss @@ -0,0 +1,16 @@ +.controlsContainer { + margin-bottom: 1.5rem; + display: flex; + gap: 0.75rem; + justify-content: center; + flex-wrap: wrap; + flex-direction: column; + max-width: 1200px; + margin: 0 auto; + + @media (min-width: 768px) { + flex-direction: row; + gap: 1rem; + margin-bottom: 2rem; + } +} diff --git a/src/components/calendar/Controls.tsx b/src/components/calendar/Controls.tsx new file mode 100644 index 0000000..6806b10 --- /dev/null +++ b/src/components/calendar/Controls.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { Button } from '../ui/Button'; +import { ButtonGroup } from '../ui/ButtonGroup'; +import { RangeInput } from '../ui/RangeInput'; +import { HeaderStyle, MonthCutoffType, DaySize } from '../../types/calendar'; +import styles from './Controls.module.scss'; + +interface ControlsProps { + headerStyle: HeaderStyle; + monthCutoff: MonthCutoffType; + size: DaySize; + fontProportion: number; + magnify: boolean; + onHeaderStyleChange: (type: HeaderStyle) => void; + onMonthCutoffChange: (type: MonthCutoffType) => void; + onSizeChange: (size: DaySize) => void; + onFontProportionChange: (proportion: number) => void; + onMagnifyChange: (magnify: boolean) => void; +} + +export const Controls: React.FC = ({ + headerStyle, + monthCutoff, + size, + fontProportion, + magnify, + onHeaderStyleChange, + onMonthCutoffChange, + onSizeChange, + onFontProportionChange, + onMagnifyChange, +}) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/src/components/calendar/Day/Day.module.scss b/src/components/calendar/Day/Day.module.scss new file mode 100644 index 0000000..91c1b55 --- /dev/null +++ b/src/components/calendar/Day/Day.module.scss @@ -0,0 +1,463 @@ +@use 'variables' as v; +@use '../shared/variables' as shared; +@use '../shared/colors' as colors; + +.Day { + + &__Header { + color: colors.$day-header-defaultmode-defaultstate-color; + background: colors.$day-header-defaultmode-defaultstate-background-color; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; + width: 100%; + display: flex; + align-items: center; + } + + &__HeaderText { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0; + width: 100%; + } + + &__Content { + display: flex; + align-items: center; + justify-content: center; + padding: v.$content-padding-base; + position: relative; + } + + &__Container { + display: flex; + flex-direction: column; + align-items: stretch; + border: shared.$border-size solid colors.$border-color; + width: 100%; + min-width: 0; + background: colors.$day-content-defaultmode-defaultstate-background-color; + overflow: hidden; + position: relative; + border-radius: shared.$border-radius-sm; + + @media (min-width: shared.$breakpoint-tablet) { + border-radius: shared.$border-radius; + } + + &--interactive { + cursor: pointer; + + &:hover { + box-shadow: shared.$shadow-hover; + background: colors.$day-content-defaultmode-selectingstate-midrange-hover-background-color; + z-index: 1; + } + + &:focus-visible { + outline: none; + box-shadow: shared.$shadow-focus; + z-index: 1; + } + } + + &--otherMonth { + opacity: 0.2; + } + + // ***************** + // Border Management + // ***************** + &--selecting, &--selected { + &:not(.Day__Container--rowStart):not(.Day__Container--rowEnd):not(.Day__Container--rangeStart):not(.Day__Container--rangeEnd) { + border-left: 0; + border-right: 0; + border-radius: 0; + } + &.Day__Container--rangeStart:not(.Day__Container--rangeEnd):not(.Day__Container--rowEnd) { + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + &.Day__Container--rangeEnd:not(.Day__Container--rangeStart):not(.Day__Container--rowStart) { + border-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + &.Day__Container--rowStart:not(.Day__Container--rangeEnd) { + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &.Day__Container--rowEnd:not(.Day__Container--rangeStart) { + border-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + // implicit (defaultstate(not selected not selecting)) { + // implicit (defaultmode(not greyed)) { + + .Day__Header { + background: colors.$day-header-defaultmode-defaultstate-background-color; + color: colors.$day-header-defaultmode-defaultstate-color; + } + + .Day__Number { + color: colors.$day-content-defaultmode-defaultstate-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-defaultstate-background-color; + + .Day__Header { + background: colors.$day-header-greyedmode-defaultstate-background-color; + color: colors.$day-header-greyedmode-defaultstate-color; + } + + .Day__Number { + color: colors.$day-content-greyedmode-defaultstate-color; + } + } + // end implicit (defaultstate(not selected not selecting)) + + // implicit (midrange) { + &.Day__Container--selecting { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectingstate-midrange-background-color; + + .Day__Header { + background: colors.$day-header-defaultmode-selectingstate-midrange-background-color; + color: colors.$day-header-defaultmode-selectingstate-midrange-color; + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectingstate-midrange-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectingstate-midrange-background-color; + + .Day__Header { + background: colors.$day-header-greyedmode-selectingstate-midrange-background-color; + color: colors.$day-header-greyedmode-selectingstate-midrange-color; + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectingstate-midrange-color; + } + } + } + + &.Day__Container--selected { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectedstate-midrange-background-color; + + .Day__Header { + background: colors.$day-header-defaultmode-selectedstate-midrange-background-color; + color: colors.$day-header-defaultmode-selectedstate-midrange-color; + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectedstate-midrange-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectedstate-midrange-background-color; + + .Day__Header { + background: colors.$day-header-greyedmode-selectedstate-midrange-background-color; + color: colors.$day-header-greyedmode-selectedstate-midrange-color; + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectedstate-midrange-color; + } + } + } + // end implicit (midrange) { + + // rangestart (selected|selecting)(greyed|defaultMode) + &.Day__Container--rangeStart:not(.Day__Container--rangeEnd) { + // implicit .Day__Container--selected { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectedstate-rangestart-background-color; + + &:hover { + background: colors.$day-content-defaultmode-selectedstate-rangestart-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-defaultmode-selectedstate-rangestart-background-color; + color: colors.$day-header-defaultmode-selectedstate-rangestart-color; + + &:hover { + background: colors.$day-header-defaultmode-selectedstate-rangestart-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectedstate-rangestart-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectedstate-rangestart-background-color; + + &:hover { + background: colors.$day-content-greyedmode-selectedstate-rangestart-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-greyedmode-selectedstate-rangestart-background-color; + color: colors.$day-header-greyedmode-selectedstate-rangestart-color; + + &:hover { + background: colors.$day-header-greyedmode-selectedstate-rangestart-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectedstate-rangestart-color; + } + } + // end implicit selected + + // selecting + &.Day__Container--selecting { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectingstate-rangestart-background-color; + + &:hover { + background: colors.$day-content-defaultmode-selectingstate-rangestart-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-defaultmode-selectingstate-rangestart-background-color; + color: colors.$day-header-defaultmode-selectingstate-rangestart-color; + + &:hover { + background: colors.$day-header-defaultmode-selectingstate-rangestart-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectingstate-rangestart-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectingstate-rangestart-background-color; + + &:hover { + background: colors.$day-content-greyedmode-selectingstate-rangestart-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-greyedmode-selectingstate-rangestart-background-color; + color: colors.$day-header-greyedmode-selectingstate-rangestart-color; + + &:hover { + background: colors.$day-header-greyedmode-selectingstate-rangestart-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectingstate-rangestart-color; + } + } + } + } + + // RangeEnd (selected|selecting)(greyed|defaultMode) + &.Day__Container--rangeEnd:not(.Day__Container--rangeStart) { + // implicit .Day__Container--selected { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectedstate-rangeend-background-color; + + &:hover { + background: colors.$day-content-defaultmode-selectedstate-rangeend-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-defaultmode-selectedstate-rangeend-background-color; + color: colors.$day-header-defaultmode-selectedstate-rangeend-color; + + &:hover { + background: colors.$day-header-defaultmode-selectedstate-rangeend-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectedstate-rangeend-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectedstate-rangeend-background-color; + + &:hover { + background: colors.$day-content-greyedmode-selectedstate-rangeend-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-greyedmode-selectedstate-rangeend-background-color; + color: colors.$day-header-greyedmode-selectedstate-rangeend-color; + + &:hover { + background: colors.$day-header-greyedmode-selectedstate-rangeend-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectedstate-rangeend-color; + } + } + // end implicit selected + + // selecting + &.Day__Container--selecting { + // implicit (defaultmode(not greyed)) { + background: colors.$day-content-defaultmode-selectingstate-rangeend-background-color; + + &:hover { + background: colors.$day-content-defaultmode-selectingstate-rangeend-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-defaultmode-selectingstate-rangeend-background-color; + color: colors.$day-header-defaultmode-selectingstate-rangeend-color; + + &:hover { + background: colors.$day-header-defaultmode-selectingstate-rangeend-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-defaultmode-selectingstate-rangeend-color; + } + // end implicit (defaultmode(not greyed)) { + + &.Day__Container--greyed { + background: colors.$day-content-greyedmode-selectingstate-rangeend-background-color; + + &:hover { + background: colors.$day-content-greyedmode-selectingstate-rangeend-hover-background-color; + } + + .Day__Header { + background: colors.$day-header-greyedmode-selectingstate-rangeend-background-color; + color: colors.$day-header-greyedmode-selectingstate-rangeend-color; + + &:hover { + background: colors.$day-header-greyedmode-selectingstate-rangeend-hover-background-color; + } + } + + .Day__Number { + color: colors.$day-content-greyedmode-selectingstate-rangeend-color; + } + } + } + } + } + +} + +// Base case (no magnify) +.Day__Container:not(.magnify) { + &.Day__Container--rangeStart:not(.Day__Container--rangeEnd) .Day__Content, + &.Day__Container--selected:not(.Day__Container--rowEnd):not(.Day__Container--rangeEnd) .Day__Content, + &.Day__Container--selecting:not(.Day__Container--rowEnd):not(.Day__Container--rangeEnd) .Day__Content { + padding-right: v.$content-padding-base + v.$content-padding-border-compensation + shared.$week-wrapper-padding; + + @media (min-width: shared.$breakpoint-tablet) { + padding-right: v.$content-padding-base + v.$content-padding-border-compensation + shared.$week-wrapper-padding-desktop; + } + } + + &.Day__Container--rangeEnd:not(.Day__Container--rangeStart) .Day__Content, + &.Day__Container--selected:not(.Day__Container--rowStart):not(.Day__Container--rangeStart) .Day__Content, + &.Day__Container--selecting:not(.Day__Container--rowStart):not(.Day__Container--rangeStart) .Day__Content { + padding-left: v.$content-padding-base + v.$content-padding-border-compensation + shared.$week-wrapper-padding; + + @media (min-width: shared.$breakpoint-tablet) { + padding-left: v.$content-padding-base + v.$content-padding-border-compensation + shared.$week-wrapper-padding-desktop; + } + } +} + +// Magnify case +.Day__Container.magnify { + &.Day__Container--rangeStart:not(.Day__Container--rangeEnd) .Day__Content, + &.Day__Container--selected:not(.Day__Container--rowEnd):not(.Day__Container--rangeEnd) .Day__Content, + &.Day__Container--selecting:not(.Day__Container--rowEnd):not(.Day__Container--rangeEnd) .Day__Content { + padding-right: v.$content-padding-base + v.$content-padding-border-compensation; + } + &.Day__Container--rangeStart:not(.Day__Container--rangeEnd) .Day__Content, + &.Day__Container--selected.Day__Container--rowStart .Day__Content { + padding-left: 0; + } + + &.Day__Container--rangeEnd:not(.Day__Container--rangeStart) .Day__Content, + &.Day__Container--selected:not(.Day__Container--rowStart):not(.Day__Container--rangeStart) .Day__Content, + &.Day__Container--selecting:not(.Day__Container--rowStart):not(.Day__Container--rangeStart) .Day__Content { + padding-left: v.$content-padding-base + v.$content-padding-border-compensation; + } + &.Day__Container--rangeEnd:not(.Day__Container--rangeStart) .Day__Content, + &.Day__Container--selected.Day__Container--rowEnd .Day__Content { + padding-right: 0; + } +} + +// Size variants +@each $size in ('xl', 'l', 'm', 's', 'xs') { + .Day--#{$size} { + .Day__Header { + height: v.get-size-value($size, 'header-height'); + font-size: v.get-size-value($size, 'header-font'); + + @media (min-width: shared.$breakpoint-tablet) { + height: v.get-size-value($size, 'header-height-desktop'); + font-size: v.get-size-value($size, 'header-font-desktop'); + } + } + + .Day__Content { + height: v.get-size-value($size, 'header-height') * 2; + + @media (min-width: shared.$breakpoint-tablet) { + height: v.get-size-value($size, 'header-height-desktop') * 2; + } + } + } +} + +// Header style variants +.Day--expanded .Day__Header { + padding: 0 shared.$spacing-unit-quadruple; + .Day__HeaderText { + text-align: left; + } +} + +.Day--compacted .Day__Header { + padding: 0 shared.$spacing-unit-double; + .Day__HeaderText { + text-align: center; + } +} + +.Day--tiny .Day__Header { + padding: 0 shared.$spacing-unit; + .Day__HeaderText { + text-align: center; + } +} diff --git a/src/components/calendar/Day/_variables.scss b/src/components/calendar/Day/_variables.scss new file mode 100644 index 0000000..80db65b --- /dev/null +++ b/src/components/calendar/Day/_variables.scss @@ -0,0 +1,63 @@ +@use '../shared/variables' as shared; +@use "sass:map"; + +// Content padding +$content-padding-base: shared.$spacing-unit-double; +$content-padding-border-compensation: shared.$border-size; +$content-padding-border-compensation-magnify: shared.$border-size * 0.5; +$content-padding-wrapper-compensation: shared.$week-wrapper-padding; + +// Sizes +$size-xl: ( + header-height: 48px, + header-height-desktop: 56px, + header-font: 1rem, + header-font-desktop: 1.1rem +); + +$size-l: ( + header-height: 36px, + header-height-desktop: 42px, + header-font: 0.9rem, + header-font-desktop: 1rem +); + +$size-m: ( + header-height: 32px, + header-height-desktop: 36px, + header-font: 0.8rem, + header-font-desktop: 0.9rem +); + +$size-s: ( + header-height: 28px, + header-height-desktop: 32px, + header-font: 0.75rem, + header-font-desktop: 0.8rem +); + +$size-xs: ( + header-height: 24px, + header-height-desktop: 28px, + header-font: 0.7rem, + header-font-desktop: 0.75rem +); + +// Functions +@function get-size-value($size, $property) { + $size-map: null; + + @if $size == 'xl' { + $size-map: $size-xl; + } @else if $size == 'l' { + $size-map: $size-l; + } @else if $size == 'm' { + $size-map: $size-m; + } @else if $size == 's' { + $size-map: $size-s; + } @else if $size == 'xs' { + $size-map: $size-xs; + } + + @return map.get($size-map, $property); +} diff --git a/src/components/calendar/Day/index.tsx b/src/components/calendar/Day/index.tsx new file mode 100644 index 0000000..ad28dd5 --- /dev/null +++ b/src/components/calendar/Day/index.tsx @@ -0,0 +1,86 @@ +import React, { useCallback, KeyboardEvent } from 'react'; +import { format } from 'date-fns'; +import { getDayLabel } from '../utils'; +import styles from './Day.module.scss'; +import classNames from 'classnames'; +import { DayProps } from '../../../types/calendar'; +import { DayNumber } from '../DayNumber'; + +export const Day: React.FC = ({ + date, + headerStyle, + isOtherMonth, + variations = [], + onSelect, + onHover, + size = 'l', + fontProportion = 100, + magnify = false +}) => { + const isInteractive = Boolean(onSelect || onHover); + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (!onSelect) return; + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onSelect(); + } + }, [onSelect]); + + const containerClasses = classNames( + 'Day__Container', + styles.Day__Container, + { + [styles['Day__Container--interactive']]: isInteractive, + [styles['Day__Container--otherMonth']]: isOtherMonth, + [styles['Day__Container--selected']]: variations.includes('selected'), + [styles['Day__Container--rangeStart']]: variations.includes('rangeStart'), + [styles['Day__Container--rangeEnd']]: variations.includes('rangeEnd'), + [styles['Day__Container--selecting']]: variations.includes('selecting'), + [styles['Day__Container--greyed']]: variations.includes('greyed'), + [styles['Day__Container--rowStart']]: variations.includes('rowStart'), + [styles['Day__Container--rowEnd']]: variations.includes('rowEnd'), + [styles['magnify']]: magnify && (variations.includes('selected') || variations.includes('selecting')) + }, + styles[`Day--${headerStyle}`], + styles[`Day--${size}`] + ); + + const headerClasses = classNames( + styles.Day__Header, + { + [styles['Day__Header--greyed']]: variations.includes('greyed') + } + ); + + const interactiveProps = isInteractive ? { + onClick: onSelect, + onMouseEnter: onHover, + onKeyDown: handleKeyDown, + role: "button", + tabIndex: 0, + "aria-label": `Select ${format(date, 'PPP')}` + } : {}; + + return ( +
+ {headerStyle !== 'none' && ( +
+
+ {getDayLabel(date, headerStyle)} +
+
+ )} +
+ +
+
+ ); +}; diff --git a/src/components/calendar/Day/types.ts b/src/components/calendar/Day/types.ts new file mode 100644 index 0000000..3768e93 --- /dev/null +++ b/src/components/calendar/Day/types.ts @@ -0,0 +1,17 @@ +import { HeaderStyle, DayVariation } from '../../../types/calendar'; + +export interface DayContainerProps { + headerStyle: HeaderStyle; + isOtherMonth?: boolean; + variations?: DayVariation[]; +} + +export interface DayHeaderProps { + headerStyle: HeaderStyle; + variations?: DayVariation[]; +} + +export interface DayNumberProps { + headerStyle: HeaderStyle; + variations?: DayVariation[]; +} diff --git a/src/components/calendar/DayNumber/DayNumber.module.scss b/src/components/calendar/DayNumber/DayNumber.module.scss new file mode 100644 index 0000000..ce3f05e --- /dev/null +++ b/src/components/calendar/DayNumber/DayNumber.module.scss @@ -0,0 +1,27 @@ +@use '../shared/colors' as shared; + +.container { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + &.greyed { + .text { + fill: shared.$day-content-defaultmode-defaultstate-color; + } + } +} + +.svg { + width: 100%; + height: 100%; +} + +.text { + fill: shared.$day-content-defaultmode-defaultstate-color; + font-weight: 700; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-feature-settings: 'tnum' on, 'lnum' on; +} diff --git a/src/components/calendar/DayNumber/index.tsx b/src/components/calendar/DayNumber/index.tsx new file mode 100644 index 0000000..488bd58 --- /dev/null +++ b/src/components/calendar/DayNumber/index.tsx @@ -0,0 +1,72 @@ +import React, { useRef, useEffect, useState } from 'react'; +import styles from './DayNumber.module.scss'; +import classNames from 'classnames'; + +interface DayNumberProps { + number: number; + proportion: number; + isGreyed?: boolean; +} + +export const DayNumber: React.FC = ({ + number, + proportion, + isGreyed +}) => { + const textRef = useRef(null); + const [scale, setScale] = useState({ x: 1, y: 1 }); + const isSingleDigit = number < 10; + + useEffect(() => { + if (textRef.current) { + // Get the natural dimensions of the text + const bbox = textRef.current.getBBox(); + const naturalWidth = bbox.width; + const naturalHeight = bbox.height; + + // Calculate desired width based on proportion and single/double digit + const containerWidth = 100; // viewBox width + const maxWidth = containerWidth * 0.8; // 80% of container width + const baseWidth = maxWidth * (isSingleDigit ? 0.5 : 1); + // Apply the proportion (0-100) to scale the base width + const desiredWidth = baseWidth * (proportion / 100); + + // Calculate scale factors + const scaleX = desiredWidth / naturalWidth; + // Use the same scale for height to maintain proportion + const scaleY = scaleX; + + setScale({ x: scaleX, y: scaleY }); + + // Debug log + console.log(`Number: ${number}, Proportion: ${proportion}, Scale: ${scaleX}`); + } + }, [number, proportion, isSingleDigit]); + + return ( +
+ + + {number} + + +
+ ); +}; diff --git a/src/components/calendar/Month/Month.module.scss b/src/components/calendar/Month/Month.module.scss new file mode 100644 index 0000000..f4a6ce6 --- /dev/null +++ b/src/components/calendar/Month/Month.module.scss @@ -0,0 +1,103 @@ +@use '../shared/variables' as shared; +@use '../shared/colors' as colors; + +.Month { + width: 100%; + background: colors.$content-normal-bg; // replaced shared.$content-background-color + border-radius: shared.$border-radius-sm; + overflow: hidden; + box-shadow: shared.$shadow-month; + display: flex; + flex-direction: column; + + @media (min-width: shared.$breakpoint-tablet) { + border-radius: shared.$border-radius; + } + + &__Container { + display: flex; + gap: shared.$spacing-unit; + background: colors.$content-normal-bg; // replaced shared.$content-background-color-selecting + padding: 0.75rem; + min-width: 0; + height: 100%; + + @media (min-width: shared.$breakpoint-tablet) { + gap: shared.$spacing-unit-double; + padding: 1.5rem; + } + + &--row { + flex-direction: row; + } + + &--column { + flex-direction: column; + } + } + + &__Title { + font-size: 1.25rem; + padding: shared.$spacing-unit-double; + margin: 0; + color: colors.$day-content-defaultmode-defaultstate-color; // replaced shared.$day-content-defaultmode-defaultstate-color + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + border-bottom: shared.$border-size solid colors.$border-color; // replaced shared.$border-color + background: colors.$content-normal-bg; // replaced shared.$content-background-color + text-align: center; + + @media (min-width: shared.$breakpoint-tablet) { + font-size: 1.5rem; + padding: 1.5rem; + text-align: left; + } + } + + &__WeeksContainer { + width: 100%; + display: flex; + flex-direction: column; + gap: shared.$spacing-unit; + min-width: 0; + + @media (min-width: shared.$breakpoint-tablet) { + gap: shared.$spacing-unit-double; + } + } + + &__DayHeadersRow { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: shared.$spacing-unit; + margin-bottom: shared.$spacing-unit; + width: 100%; + + @media (min-width: shared.$breakpoint-tablet) { + gap: shared.$spacing-unit-double; + margin-bottom: shared.$spacing-unit-double; + } + } + + &__DayHeader { + font-size: 0.75rem; + padding: 0.4rem 0.1rem; + text-align: center; + font-weight: 500; + text-transform: uppercase; + background: transparent; + color: colors.$content-normal-text; // replaced shared.$header-color + border-bottom: shared.$spacing-unit solid colors.$header-normal-bg; // replaced shared.$header-background-color + + &--weekend { + color: colors.$day-header-greyedmode-defaultstate-color; // replaced shared.$header-color-greyed + border-bottom-color: colors.$day-header-greyedmode-defaultstate-background-color; // replaced shared.$header-background-color-greyed + } + + @media (min-width: shared.$breakpoint-tablet) { + font-size: 0.8rem; + padding: 0.5rem 0.1rem; + } + } +} diff --git a/src/components/calendar/Month/index.tsx b/src/components/calendar/Month/index.tsx new file mode 100644 index 0000000..ba29bf9 --- /dev/null +++ b/src/components/calendar/Month/index.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { startOfMonth, startOfWeek, addDays, addWeeks, endOfMonth, format } from 'date-fns'; +import { MonthProps, HeaderStyle } from '../../../types/calendar'; +import { Week } from '../Week'; +import styles from './Month.module.scss'; +import classNames from 'classnames'; + +export const Month: React.FC = ({ + date, + dayHeaderStyle, + monthDayOfWeekHeaderStyle, + direction, + monthCutoff, + weekendDays = [6, 0], + dateRange, + onDateSelect, + onDateHover, + size = 'l', + fontProportion = 100, + magnify = false +}) => { + const monthStart = startOfMonth(date); + const start = startOfWeek(monthStart, { weekStartsOn: 1 }); + const end = endOfMonth(date); + const referenceMonth = date.getMonth(); + const weeks: JSX.Element[] = []; + + const effectiveMonthDayOfWeekHeaderStyle: HeaderStyle = monthDayOfWeekHeaderStyle ?? + (dayHeaderStyle === 'none' ? 'compacted' : 'none'); + + let current = start; + + while (current <= end) { + weeks.push( + + ); + current = addWeeks(current, 1); + } + + return ( +
+

{format(date, 'MMMM yyyy')}

+
+ {effectiveMonthDayOfWeekHeaderStyle !== 'none' && ( +
+ {Array.from({ length: 7 }, (_, i) => { + const dayDate = addDays(start, i); + const dayOfWeek = dayDate.getDay(); + const isWeekend = weekendDays.includes(dayOfWeek); + + return ( +
+ {format(dayDate, 'EEEEE')} +
+ ); + })} +
+ )} +
+ {weeks} +
+
+
+ ); +}; diff --git a/src/components/calendar/Week/Week.module.scss b/src/components/calendar/Week/Week.module.scss new file mode 100644 index 0000000..8c1e229 --- /dev/null +++ b/src/components/calendar/Week/Week.module.scss @@ -0,0 +1,56 @@ +@use '../shared/variables' as shared; +@use '../shared/colors' as colors; + +.Week { + &__Container { + display: grid; + width: 100%; + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + &__DayWrapper { + padding: shared.$week-wrapper-padding; + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$week-wrapper-padding-desktop; + } + + &--rangeStart:not(.Week__DayWrapper--rangeEnd), + &--selected.Week__DayWrapper--rowStart:not(.Week__DayWrapper--rowEnd), + &--selecting.Week__DayWrapper--rowStart:not(.Week__DayWrapper--rowEnd) { + padding: shared.$week-wrapper-padding 0 shared.$week-wrapper-padding shared.$week-wrapper-padding; + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$week-wrapper-padding-desktop 0 shared.$week-wrapper-padding-desktop shared.$week-wrapper-padding-desktop; + } + } + + &--rangeEnd:not(.Week__DayWrapper--rangeStart), + &--selected.Week__DayWrapper--rowEnd:not(.Week__DayWrapper--rowStart), + &--selecting.Week__DayWrapper--rowEnd:not(.Week__DayWrapper--rowStart) { + padding: shared.$week-wrapper-padding shared.$week-wrapper-padding shared.$week-wrapper-padding 0; + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$week-wrapper-padding-desktop shared.$week-wrapper-padding-desktop shared.$week-wrapper-padding-desktop 0; + } + } + + &--selected:not(&--rangeStart):not(&--rangeEnd):not(&--rowStart):not(&--rowEnd), + &--selecting:not(&--rangeStart):not(&--rangeEnd):not(&--rowStart):not(&--rowEnd) { + padding: shared.$week-wrapper-padding 0; + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$week-wrapper-padding-desktop 0; + } + } + } + + &__EmptyCell { + padding: shared.$week-wrapper-padding; + background: colors.$content-normal-bg; // replaced shared.$content-background-color + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$week-wrapper-padding-desktop; + } + } +} diff --git a/src/components/calendar/Week/index.tsx b/src/components/calendar/Week/index.tsx new file mode 100644 index 0000000..a4b2819 --- /dev/null +++ b/src/components/calendar/Week/index.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { addDays } from 'date-fns'; +import { WeekProps } from '../../../types/calendar'; +import { Day } from '../Day'; +import { getDateVariations } from '../../../utils/dateUtils'; +import styles from './Week.module.scss'; +import classNames from 'classnames'; + +export const Week: React.FC = ({ + startDate, + headerStyle, + monthCutoff, + referenceMonth, + weekendDays = [6, 0], + dateRange, + onDateSelect, + onDateHover, + size = 'l', + fontProportion = 100, + magnify = false +}) => { + const allDays = Array.from({ length: 7 }, (_, i) => { + const date = addDays(startDate, i); + const isOtherMonth = date.getMonth() !== referenceMonth; + const variations = getDateVariations(date, dateRange, weekendDays, i); + + const wrapperClasses = classNames( + styles.Week__DayWrapper, + { + [styles['Week__DayWrapper--rangeStart']]: variations.includes('rangeStart'), + [styles['Week__DayWrapper--rangeEnd']]: variations.includes('rangeEnd'), + [styles['Week__DayWrapper--selected']]: variations.includes('selected'), + [styles['Week__DayWrapper--selecting']]: variations.includes('selecting'), + [styles['Week__DayWrapper--rowStart']]: variations.includes('rowStart'), + [styles['Week__DayWrapper--rowEnd']]: variations.includes('rowEnd') + } + ); + + if (monthCutoff && isOtherMonth) { + if (monthCutoff === 'truncate') { + return null; + } + return ( +
+ onDateSelect(date) : undefined} + onHover={onDateHover ? () => onDateHover(date) : undefined} + size={size} + fontProportion={fontProportion} + magnify={magnify} + /> +
+ ); + } + + return ( +
+ onDateSelect(date) : undefined} + onHover={onDateHover ? () => onDateHover(date) : undefined} + size={size} + fontProportion={fontProportion} + magnify={magnify} + /> +
+ ); + }); + + const firstValidIndex = allDays.findIndex(Boolean); + const lastValidIndex = allDays.length - [...allDays].reverse().findIndex(Boolean) - 1; + + if (monthCutoff === 'truncate') { + const truncatedDays = Array.from({ length: 7 }, (_, i) => { + if (i < firstValidIndex || i > lastValidIndex) { + return
; + } + return allDays[i]; + }); + + return ( +
+ {truncatedDays} +
+ ); + } + + return ( +
+ {allDays} +
+ ); +}; diff --git a/src/components/calendar/Week/types.ts b/src/components/calendar/Week/types.ts new file mode 100644 index 0000000..9793095 --- /dev/null +++ b/src/components/calendar/Week/types.ts @@ -0,0 +1,17 @@ +import { DayViewType, DayVariation } from '../../../types/calendar'; + +export interface WeekContainerProps { + viewType: DayViewType; + isOtherMonth?: boolean; + variations?: DayVariation[]; +} + +export interface DayHeaderProps { + viewType: DayViewType; + variations?: DayVariation[]; +} + +export interface DayNumberProps { + viewType: DayViewType; + variations?: DayVariation[]; +} diff --git a/src/components/calendar/Year/Year.module.scss b/src/components/calendar/Year/Year.module.scss new file mode 100644 index 0000000..8308f1c --- /dev/null +++ b/src/components/calendar/Year/Year.module.scss @@ -0,0 +1,41 @@ +@use '../shared/variables' as shared; +@use '../shared/colors' as colors; + +.Year { + &__Wrapper { + display: flex; + flex-direction: column; + gap: shared.$spacing-unit-quadruple; + padding: shared.$spacing-unit-double; + width: 100%; + max-width: 1400px; + margin: 0 auto; + + @media (min-width: shared.$breakpoint-tablet) { + padding: shared.$spacing-unit-quadruple; + } + } + + &__Title { + font-size: 2rem; + color: colors.$content-normal-text; // replaced shared.$content-color + margin: 0 0 shared.$spacing-unit-quadruple; + text-align: center; + font-weight: 700; + letter-spacing: 0.1em; + } + + &__MonthsGrid { + display: grid; + gap: shared.$spacing-unit-quadruple; + width: 100%; + + @media (min-width: shared.$breakpoint-tablet) { + grid-template-columns: repeat(2, 1fr); + } + + @media (min-width: 1200px) { + grid-template-columns: repeat(3, 1fr); + } + } +} diff --git a/src/components/calendar/Year/index.tsx b/src/components/calendar/Year/index.tsx new file mode 100644 index 0000000..5da6248 --- /dev/null +++ b/src/components/calendar/Year/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { YearProps } from '../../../types/calendar'; +import { Month } from '../Month'; +import styles from './Year.module.scss'; + +export const Year: React.FC = ({ + year, + dayHeaderStyle, + monthDayOfWeekHeaderStyle, + monthCutoff, + weekendDays, + dateRange, + onDateSelect, + onDateHover, + size = 'l', + fontProportion = 100, + magnify = false +}) => { + const months = Array.from({ length: 12 }, (_, index) => { + const monthDate = new Date(year, index, 1); + return ( + + ); + }); + + return ( +
+

{year}

+
+ {months} +
+
+ ); +}; diff --git a/src/components/calendar/shared/_colors.scss b/src/components/calendar/shared/_colors.scss new file mode 100644 index 0000000..27b494e --- /dev/null +++ b/src/components/calendar/shared/_colors.scss @@ -0,0 +1,189 @@ +@use "sass:color"; + +// ----------------------------------------------------------------------------- +// BORDER COLORS +// ----------------------------------------------------------------------------- +$border-color: #e5e5e5; +$border-color-selecting: #d1d1d1; +$border-color-in-range: $border-color; + +// ----------------------------------------------------------------------------- +// FOCUS COLOR +// ----------------------------------------------------------------------------- +$focus-blue: #2196f3; + +// ----------------------------------------------------------------------------- +// HEADER BASE COLORS +// ----------------------------------------------------------------------------- +$header-normal-bg: #2c2c2c; // Anthracite +$header-normal-text: #ffffff; + +$header-selected-bg: #f321c9; // Blue +$header-selected-text: #ffffff; + +$header-selecting-bg: #404040; // Lighter anthracite +$header-selecting-text: #ffffff; + +// Hover variants for header +$header-normal-hover-bg: #383838; +$header-selected-hover-bg: #1976d2; +$header-selecting-hover-bg: #4a4a4a; + +// ----------------------------------------------------------------------------- +// CONTENT BASE COLORS +// ----------------------------------------------------------------------------- +$content-normal-bg: #ffffff; +$content-normal-text: #333333; + +$content-selected-bg: #2196f3; +$content-selected-text: #ffffff; + +$content-selecting-bg: #f5f5f5; +$content-selecting-text: #333333; + +// Hover variants for content +$content-normal-hover-bg: #f8f8f8; +$content-selected-hover-bg: #1976d2; +$content-selecting-hover-bg: #eeeeee; + +$content-selecting-bg: color.mix($content-normal-bg, $content-selected-bg, 50%); +$content-selecting-text: color.mix($content-normal-text, $content-selected-text, 50%); + +// Hover variants for content. +$content-normal-hover-bg: color.adjust($content-normal-bg, $lightness: -5%); +$content-selected-hover-bg: color.adjust($content-selected-bg, $lightness: 5%); +$content-selecting-hover-bg: color.adjust($content-selecting-bg, $lightness: -5%); + +// ---------------------------------- + +// Colors +$day-color-primary: #2196f3; // day default mode selected sate container +$day-color-primary-dark: #1976d2; // day default mode selected sate header +$day-color-background: white; // day default mode default state container background +$day-color-border: #f0f0f0; // any day container border +$day-color-text: #2c2c2c; // day default mode default state container text color +$day-color-text-light: #757575; // day greyed mode default state text color +$day-color-hover: #f5f5f5; // day default mode default state hover container +$day-color-greyed: #bcbcbc; // day greyed mode default state container + +// Range colors +$day-color-selected: #e3f2fd; // day default mode selected state container +$day-color-selected-hover: #bbdefb; // day default mode selected state hover container +$day-color-selecting: #f5f5f5; // day default mode selecting state container +$day-color-selecting-border: #e0e0e0; // day default|greyed mode selecting container border +$day-color-selecting-hover: #eeeeee; + +// Header colors +$day-color-header: #2c2c2c; // day default mode default state header +$day-color-header-text: white; // day default mode default state header text +$day-color-header-greyed: #acacac; // day greyed mode default state header +$day-color-header-greyed-text: #101010; // day default mode default state header text + +// Base Colors +$day-border-color: $day-color-border; +$day-header-defaultmode-defaultstate-background-color: $day-color-header; +$day-header-defaultmode-defaultstate-color: $day-color-header-text; + +$day-content-defaultmode-defaultstate-background-color: $day-color-background; +$day-content-defaultmode-defaultstate-color: $day-color-text; + +$day-content-greyedmode-defaultstate-background-color: color.adjust($day-color-greyed, $lightness: 25%); // Much lighter grey +$day-content-greyedmode-defaultstate-color: $day-color-text-light; + +$day-header-greyedmode-defaultstate-background-color: $day-color-header-greyed; +$day-header-greyedmode-defaultstate-color: $day-color-header-greyed-text; + +// Selecting State Colors +$day-content-defaultmode-selectingstate-midrange-background-color: $day-color-selecting; +$day-content-defaultmode-selectingstate-midrange-color: $day-color-text; +$day-content-defaultmode-selectingstate-midrange-hover-background-color: $day-color-selecting-hover; + +$day-content-greyedmode-selectingstate-midrange-background-color: color.mix($day-color-selecting, $day-color-greyed, 80%); +$day-content-greyedmode-selectingstate-midrange-color: $day-color-text-light; + +$day-header-defaultmode-selectingstate-midrange-background-color: $day-color-header; +$day-header-defaultmode-selectingstate-midrange-color: $day-color-header-text; + +$day-header-greyedmode-selectingstate-midrange-background-color: color.mix($day-color-header-greyed, $day-color-selecting, 80%); +$day-header-greyedmode-selectingstate-midrange-color: $day-color-header-greyed-text; + +// Selected State Colors +$day-content-defaultmode-selectedstate-midrange-background-color: $day-color-selected; +$day-content-defaultmode-selectedstate-midrange-color: $day-color-text; + +$day-content-greyedmode-selectedstate-midrange-background-color: color.mix($day-color-selected, $day-color-greyed, 80%); +$day-content-greyedmode-selectedstate-midrange-color: $day-color-text-light; + +$day-header-defaultmode-selectedstate-midrange-background-color: $day-color-primary-dark; +$day-header-defaultmode-selectedstate-midrange-color: $day-color-header-text; + +$day-header-greyedmode-selectedstate-midrange-background-color: color.mix($day-color-primary-dark, $day-color-greyed, 80%); +$day-header-greyedmode-selectedstate-midrange-color: $day-color-header-text; // Changed to white for visibility + +// Range Start Colors +$day-content-defaultmode-selectedstate-rangestart-background-color: $day-color-primary; +$day-content-defaultmode-selectedstate-rangestart-color: $day-color-header-text; // White text +$day-content-defaultmode-selectedstate-rangestart-hover-background-color: $day-color-selected-hover; + +$day-content-greyedmode-selectedstate-rangestart-background-color: color.mix($day-color-primary, $day-color-greyed, 80%); +$day-content-greyedmode-selectedstate-rangestart-color: $day-color-header-text; // White text +$day-content-greyedmode-selectedstate-rangestart-hover-background-color: color.mix($day-color-selected-hover, $day-color-greyed, 80%); + +$day-header-defaultmode-selectedstate-rangestart-background-color: $day-color-primary-dark; +$day-header-defaultmode-selectedstate-rangestart-color: $day-color-header-text; +$day-header-defaultmode-selectedstate-rangestart-hover-background-color: color.adjust($day-color-primary-dark, $lightness: 10%); + +$day-header-greyedmode-selectedstate-rangestart-background-color: color.mix($day-color-primary-dark, $day-color-greyed, 80%); +$day-header-greyedmode-selectedstate-rangestart-color: $day-color-header-text; // White text +$day-header-greyedmode-selectedstate-rangestart-hover-background-color: color.adjust(color.mix($day-color-primary-dark, $day-color-greyed, 80%), $lightness: 10%); + +// Range End Colors +$day-content-defaultmode-selectedstate-rangeend-background-color: $day-color-primary; +$day-content-defaultmode-selectedstate-rangeend-color: $day-color-header-text; // White text +$day-content-defaultmode-selectedstate-rangeend-hover-background-color: $day-color-selected-hover; + +$day-content-greyedmode-selectedstate-rangeend-background-color: color.mix($day-color-primary, $day-color-greyed, 80%); +$day-content-greyedmode-selectedstate-rangeend-color: $day-color-header-text; // White text +$day-content-greyedmode-selectedstate-rangeend-hover-background-color: color.mix($day-color-selected-hover, $day-color-greyed, 80%); + +$day-header-defaultmode-selectedstate-rangeend-background-color: $day-color-primary-dark; +$day-header-defaultmode-selectedstate-rangeend-color: $day-color-header-text; +$day-header-defaultmode-selectedstate-rangeend-hover-background-color: color.adjust($day-color-primary-dark, $lightness: 10%); + +$day-header-greyedmode-selectedstate-rangeend-background-color: color.mix($day-color-primary-dark, $day-color-greyed, 80%); +$day-header-greyedmode-selectedstate-rangeend-color: $day-color-header-text; // White text +$day-header-greyedmode-selectedstate-rangeend-hover-background-color: color.adjust(color.mix($day-color-primary-dark, $day-color-greyed, 80%), $lightness: 10%); + +// Selecting State for Range +$day-content-defaultmode-selectingstate-rangestart-background-color: $day-color-selecting; +$day-content-defaultmode-selectingstate-rangestart-color: $day-color-text; +$day-content-defaultmode-selectingstate-rangestart-hover-background-color: $day-color-selecting-hover; + +$day-content-greyedmode-selectingstate-rangestart-background-color: color.mix($day-color-selecting, $day-color-greyed, 80%); +$day-content-greyedmode-selectingstate-rangestart-color: $day-color-text-light; +$day-content-greyedmode-selectingstate-rangestart-hover-background-color: color.mix($day-color-selecting-hover, $day-color-greyed, 80%); + +$day-header-defaultmode-selectingstate-rangestart-background-color: $day-color-header; +$day-header-defaultmode-selectingstate-rangestart-color: $day-color-header-text; +$day-header-defaultmode-selectingstate-rangestart-hover-background-color: color.adjust($day-color-header, $lightness: 10%); + +$day-header-greyedmode-selectingstate-rangestart-background-color: color.mix($day-color-header-greyed, $day-color-selecting, 80%); +$day-header-greyedmode-selectingstate-rangestart-color: $day-color-header-greyed-text; +$day-header-greyedmode-selectingstate-rangestart-hover-background-color: color.adjust(color.mix($day-color-header-greyed, $day-color-selecting, 80%), $lightness: 10%); + +// Selecting Range End +$day-content-defaultmode-selectingstate-rangeend-background-color: $day-color-selecting; +$day-content-defaultmode-selectingstate-rangeend-color: $day-color-text; +$day-content-defaultmode-selectingstate-rangeend-hover-background-color: $day-color-selecting-hover; + +$day-content-greyedmode-selectingstate-rangeend-background-color: color.mix($day-color-selecting, $day-color-greyed, 80%); +$day-content-greyedmode-selectingstate-rangeend-color: $day-color-text-light; +$day-content-greyedmode-selectingstate-rangeend-hover-background-color: color.mix($day-color-selecting-hover, $day-color-greyed, 80%); + +$day-header-defaultmode-selectingstate-rangeend-background-color: $day-color-header; +$day-header-defaultmode-selectingstate-rangeend-color: $day-color-header-text; +$day-header-defaultmode-selectingstate-rangeend-hover-background-color: color.adjust($day-color-header, $lightness: 10%); + +$day-header-greyedmode-selectingstate-rangeend-background-color: color.mix($day-color-header-greyed, $day-color-selecting, 80%); +$day-header-greyedmode-selectingstate-rangeend-color: $day-color-header-greyed-text; +$day-header-greyedmode-selectingstate-rangeend-hover-background-color: color.adjust(color.mix($day-color-header-greyed, $day-color-selecting, 80%), $lightness: 10%); diff --git a/src/components/calendar/shared/_variables.scss b/src/components/calendar/shared/_variables.scss new file mode 100644 index 0000000..c926564 --- /dev/null +++ b/src/components/calendar/shared/_variables.scss @@ -0,0 +1,26 @@ +// Base units +$base-unit: 2px; +$border-size: 1px; + +// Spacing derived from base unit +$spacing-unit: $base-unit; +$spacing-unit-double: $spacing-unit * 2; +$spacing-unit-quadruple: $spacing-unit * 4; + +// Week wrapper padding +$week-wrapper-padding: $spacing-unit; +$week-wrapper-padding-desktop: $spacing-unit-double; + +// Border radiuses +$border-radius-sm: 6px; +$border-radius: 8px; + +// Shadows (importing the new focus color from colors.scss) +@use "sass:color"; +@use "colors" as colors; +$shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.15); +$shadow-month: 0 2px 4px rgba(0, 0, 0, 0.15); +$shadow-focus: 0 0 0 2px colors.$focus-blue; + +// Breakpoints +$breakpoint-tablet: 768px; diff --git a/src/components/calendar/utils.ts b/src/components/calendar/utils.ts new file mode 100644 index 0000000..319d32a --- /dev/null +++ b/src/components/calendar/utils.ts @@ -0,0 +1,51 @@ +import { HeaderStyle } from '../../types/calendar'; +import { format } from 'date-fns'; + +export const getHeightByHeaderStyle = (headerStyle: HeaderStyle) => { + const baseHeights: Record = { + expanded: ['100px', '140px'], + compacted: ['80px', '100px'], + tiny: ['60px', '80px'], + none: ['50px', '60px'] + }; + + const [mobile, desktop] = baseHeights[headerStyle]; + return ` + height: ${mobile}; + @media (min-width: 768px) { + height: ${desktop}; + } + `; +}; + +export const getMinWidthByHeaderStyle = (headerStyle: HeaderStyle) => { + const baseWidths: Record = { + expanded: '120px', + compacted: '90px', + tiny: '60px', + none: '60px' + }; + + return `min-width: ${baseWidths[headerStyle]};`; +}; + +export const getDayLabel = (date: Date, headerStyle: HeaderStyle): string => { + const day = format(date, 'EEEE'); + switch (headerStyle) { + case 'expanded': + return day.toUpperCase(); + case 'compacted': + return day.slice(0, 3).toUpperCase(); + case 'tiny': + if (day === 'Thursday') return 'T'; + if (day === 'Tuesday') return 't'; + if (day === 'Saturday') return 's'; + if (day === 'Sunday') return 'S'; + if (day === 'Monday') return 'M'; + if (day === 'Wednesday') return 'W'; + if (day === 'Friday') return 'F'; + return day[0]; + default: + return day; + } +}; diff --git a/src/components/ui/Button.module.scss b/src/components/ui/Button.module.scss new file mode 100644 index 0000000..ef3357f --- /dev/null +++ b/src/components/ui/Button.module.scss @@ -0,0 +1,32 @@ +.button { + padding: 0.5rem 1rem; + border: none; + background: transparent; + color: #2c2c2c; + border-radius: 6px; + cursor: pointer; + font-family: inherit; + font-weight: 500; + font-size: 0.875rem; + // transition: all 0.2s ease; + flex: 1; + + &:hover { + background: #f0f0f0; + } + + &.active { + background: #2c2c2c; + color: white; + + &:hover { + background: #2c2c2c; + } + } + + @media (min-width: 768px) { + flex: 0 1 auto; + padding: 0.75rem 1.25rem; + font-size: 0.9rem; + } +} diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..c7f1401 --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import styles from './Button.module.scss'; +import classNames from 'classnames'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + active?: boolean; +} + +export const Button: React.FC = ({ + children, + active, + className, + ...props +}) => ( + +); diff --git a/src/components/ui/ButtonGroup.module.scss b/src/components/ui/ButtonGroup.module.scss new file mode 100644 index 0000000..2611a75 --- /dev/null +++ b/src/components/ui/ButtonGroup.module.scss @@ -0,0 +1,15 @@ +.buttonGroup { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + background: white; + padding: 0.25rem; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + width: 100%; + justify-content: center; + + @media (min-width: 768px) { + width: auto; + } +} diff --git a/src/components/ui/ButtonGroup.tsx b/src/components/ui/ButtonGroup.tsx new file mode 100644 index 0000000..e6eb977 --- /dev/null +++ b/src/components/ui/ButtonGroup.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import styles from './ButtonGroup.module.scss'; +import classNames from 'classnames'; + +interface ButtonGroupProps { + children: React.ReactNode; + className?: string; +} + +export const ButtonGroup: React.FC = ({ children, className }) => ( +
+ {children} +
+); diff --git a/src/components/ui/RangeInput.module.scss b/src/components/ui/RangeInput.module.scss new file mode 100644 index 0000000..16b2859 --- /dev/null +++ b/src/components/ui/RangeInput.module.scss @@ -0,0 +1,106 @@ +.container { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.75rem; + background: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + width: 100%; + + @media (min-width: 768px) { + width: auto; + min-width: 200px; + } +} + +.labelContainer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.label { + font-size: 0.875rem; + color: #2c2c2c; + font-weight: 500; +} + +.value { + font-size: 0.875rem; + color: #666; + font-weight: 500; + font-feature-settings: 'tnum' on, 'lnum' on; + font-family: monospace; +} + +.range { + position: relative; + width: 100%; + height: 20px; // Increased height to center the thumb + margin: 0; + background: transparent; + outline: none; + appearance: none; + cursor: pointer; + + // Track styles + &::-webkit-slider-runnable-track { + width: 100%; + height: 2px; + background: #e0e0e0; + border-radius: 2px; + border: none; + } + + &::-moz-range-track { + width: 100%; + height: 2px; + background: #e0e0e0; + border-radius: 2px; + border: none; + } + + // Thumb styles + &::-webkit-slider-thumb { + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #2c2c2c; + cursor: pointer; + margin-top: -7px; // Centers the thumb on the track: (thumb height - track height) / -2 + transition: background 0.2s; + + &:hover { + background: #404040; + } + } + + &::-moz-range-thumb { + width: 16px; + height: 16px; + border: none; + border-radius: 50%; + background: #2c2c2c; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: #404040; + } + } + + // Focus styles + &:focus { + outline: none; + + &::-webkit-slider-thumb { + box-shadow: 0 0 0 2px rgba(44, 44, 44, 0.2); + } + + &::-moz-range-thumb { + box-shadow: 0 0 0 2px rgba(44, 44, 44, 0.2); + } + } +} diff --git a/src/components/ui/RangeInput.tsx b/src/components/ui/RangeInput.tsx new file mode 100644 index 0000000..863cf14 --- /dev/null +++ b/src/components/ui/RangeInput.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import styles from './RangeInput.module.scss'; + +interface RangeInputProps { + value: number; + min: number; + max: number; + step: number; + onChange: (value: number) => void; + label: string; +} + +export const RangeInput: React.FC = ({ + value, + min, + max, + step, + onChange, + label +}) => { + return ( +
+
+ + {value}% +
+ onChange(Number(e.target.value))} + className={styles.range} + /> +
+ ); +}; diff --git a/src/examples/CompactYear.tsx b/src/examples/CompactYear.tsx new file mode 100644 index 0000000..be395e6 --- /dev/null +++ b/src/examples/CompactYear.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { Year } from '../components/calendar/Year'; +import { Controls } from '../components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType } from '../types/calendar'; +import styles from './Examples.module.scss'; + +export const CompactYear: React.FC = () => { + const [dayHeaderStyle, setDayHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + const [fontProportion, setFontProportion] = useState(100); + const [magnify, setMagnify] = useState(false); + + return ( +
+

Compact Year View

+

+ Full year view optimized for compact display. +

+ {}} + onFontProportionChange={setFontProportion} + /> +
+ +
+
+ ); +}; diff --git a/src/examples/DateRangePicker.tsx b/src/examples/DateRangePicker.tsx new file mode 100644 index 0000000..ef451eb --- /dev/null +++ b/src/examples/DateRangePicker.tsx @@ -0,0 +1,131 @@ +import React, { useState, useCallback } from 'react'; +import { format, isBefore, isEqual } from 'date-fns'; +import { Year } from '../components/calendar/Year'; +import { Controls } from '../components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType, DaySize, DateRange } from '../types/calendar'; +import styles from './Examples.module.scss'; + +export const DateRangePicker: React.FC = () => { + const [dateRange, setDateRange] = useState({ + startDate: null, + endDate: null, + selecting: false, + hoverDate: null, + anchorDate: null + }); + + const [dayHeaderStyle, setDayHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + const [size, setSize] = useState('l'); + const [fontProportion, setFontProportion] = useState(100); + const [magnify, setMagnify] = useState(false); + + const handleDateSelect = useCallback((date: Date) => { + setDateRange(prev => { + if (!prev.selecting || !prev.anchorDate) { + return { + startDate: date, + endDate: date, + selecting: true, + hoverDate: date, + anchorDate: date + }; + } + + if (isEqual(date, prev.anchorDate)) { + return { + startDate: date, + endDate: date, + selecting: false, + hoverDate: null, + anchorDate: null + }; + } + + const [start, end] = isBefore(date, prev.anchorDate) + ? [date, prev.anchorDate] + : [prev.anchorDate, date]; + + return { + startDate: start, + endDate: end, + selecting: false, + hoverDate: null, + anchorDate: null + }; + }); + }, []); + + const handleDateHover = useCallback((date: Date) => { + setDateRange(prev => { + if (prev.selecting && prev.anchorDate) { + if (isEqual(date, prev.anchorDate)) { + return { + ...prev, + startDate: date, + endDate: date, + hoverDate: date + }; + } + + const [start, end] = isBefore(date, prev.anchorDate) + ? [date, prev.anchorDate] + : [prev.anchorDate, date]; + + return { + ...prev, + startDate: start, + endDate: end, + hoverDate: date + }; + } + return prev; + }); + }, []); + + const formatDateOrNull = (date: Date | null) => { + return date ? format(date, 'PPP') : 'Not selected'; + }; + + return ( +
+

Date Range Picker

+

+ Click to select start date, then hover and click to select end date. +

+ +
+ +
+
+
Start Date: {formatDateOrNull(dateRange.startDate)}
+
End Date: {formatDateOrNull(dateRange.endDate)}
+
Selecting: {dateRange.selecting ? 'Yes' : 'No'}
+
Hover Date: {formatDateOrNull(dateRange.hoverDate)}
+
Anchor Date: {formatDateOrNull(dateRange.anchorDate)}
+
+
+ ); +}; diff --git a/src/examples/Examples.module.scss b/src/examples/Examples.module.scss new file mode 100644 index 0000000..13bf4f9 --- /dev/null +++ b/src/examples/Examples.module.scss @@ -0,0 +1,36 @@ +.section { + margin-bottom: 4rem; + padding: 2rem; + background: white; + border-radius: 12px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + + &:last-child { + margin-bottom: 0; + } +} + +.sectionTitle { + font-size: 2rem; + margin: 0 0 1rem; + color: #2c2c2c; +} + +.sectionDescription { + font-size: 1rem; + color: #666; + margin: 0 0 2rem; +} + +.demoContainer { + margin-top: 2rem; +} + +.rangeInfo { + margin-top: 1rem; + padding: 1rem; + background: #f5f5f5; + border-radius: 8px; + font-family: monospace; + font-size: 0.9rem; +} diff --git a/src/examples/SingleMonth.tsx b/src/examples/SingleMonth.tsx new file mode 100644 index 0000000..eaf0f22 --- /dev/null +++ b/src/examples/SingleMonth.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import { Month } from '../components/calendar/Month'; +import { Controls } from '../components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType, DaySize } from '../types/calendar'; +import styles from './Examples.module.scss'; + +export const SingleMonth: React.FC = () => { + const [dayHeaderStyle, setDayHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + const [size, setSize] = useState('l'); + const [fontProportion, setFontProportion] = useState(100); + + return ( +
+

Single Month View

+

+ Showcase of a single month component with various styling options. +

+ +
+ +
+
+ ); +}; diff --git a/src/examples/WeekView.tsx b/src/examples/WeekView.tsx new file mode 100644 index 0000000..ab129f0 --- /dev/null +++ b/src/examples/WeekView.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { Week } from '../components/calendar/Week'; +import { Controls } from '../components/calendar/Controls'; +import { HeaderStyle, MonthCutoffType, DaySize } from '../types/calendar'; +import styles from './Examples.module.scss'; + +export const WeekView: React.FC = () => { + const [dayHeaderStyle, setDayHeaderStyle] = useState('tiny'); + const [monthCutoff, setMonthCutoff] = useState('truncate'); + const [size, setSize] = useState('l'); + const [fontProportion, setFontProportion] = useState(100); + + return ( +
+

Week View

+

+ Display of a single week with various styling options. +

+ +
+ +
+
+ ); +}; diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..6638b60 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,5 @@ +// src/global.d.ts +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..2a6077e --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './style.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..7360a52 --- /dev/null +++ b/src/style.css @@ -0,0 +1,34 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: white; +} + +@media (max-width: 768px) { + html { + font-size: 14px; + } +} + +@media (max-width: 480px) { + html { + font-size: 12px; + } +} diff --git a/src/types/calendar.ts b/src/types/calendar.ts new file mode 100644 index 0000000..26f833e --- /dev/null +++ b/src/types/calendar.ts @@ -0,0 +1,75 @@ +export type DateRange = { + startDate: Date | null; + endDate: Date | null; + selecting: boolean; + hoverDate: Date | null; + anchorDate: Date | null; +}; + +export type HeaderStyle = 'expanded' | 'compacted' | 'tiny' | 'none'; +export type MonthCutoffType = 'dimmed' | 'truncate' | undefined; +export type DirectionType = 'row' | 'column'; +export type DayVariation = + | 'greyed' + | 'selected' + | 'rangeStart' + | 'rangeEnd' + | 'selecting' + | 'rowStart' + | 'rowEnd'; +export type DaySize = 'xl' | 'l' | 'm' | 's' | 'xs'; + +export interface YearProps { + year: number; + dayHeaderStyle: HeaderStyle; + monthDayOfWeekHeaderStyle?: HeaderStyle; + monthCutoff?: MonthCutoffType; + weekendDays?: number[]; + dateRange?: DateRange; + onDateSelect?: (date: Date) => void; + onDateHover?: (date: Date) => void; + size?: DaySize; + fontProportion?: number; + magnify?: boolean; +} + +export interface MonthProps { + date: Date; + dayHeaderStyle: HeaderStyle; + monthDayOfWeekHeaderStyle?: HeaderStyle; + direction: DirectionType; + monthCutoff?: MonthCutoffType; + weekendDays?: number[]; + dateRange?: DateRange; + onDateSelect?: (date: Date) => void; + onDateHover?: (date: Date) => void; + size?: DaySize; + fontProportion?: number; + magnify?: boolean; +} + +export interface WeekProps { + startDate: Date; + headerStyle: HeaderStyle; + monthCutoff?: MonthCutoffType; + referenceMonth: number; + weekendDays?: number[]; + dateRange?: DateRange; + onDateSelect?: (date: Date) => void; + onDateHover?: (date: Date) => void; + size?: DaySize; + fontProportion?: number; + magnify?: boolean; +} + +export interface DayProps { + date: Date; + headerStyle: HeaderStyle; + isOtherMonth?: boolean; + variations?: DayVariation[]; + onSelect?: () => void; + onHover?: () => void; + size?: DaySize; + fontProportion?: number; + magnify?: boolean; +} diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 0000000..f3f4041 --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,54 @@ +import { isSameDay, isWithinInterval, startOfDay } from 'date-fns'; +import { DateRange, DayVariation } from '../types/calendar'; + +export const getDateVariations = ( + date: Date, + dateRange: DateRange | undefined, + weekendDays: number[] = [], + dayIndex: number = 0 +): DayVariation[] => { + const greyedClasses: DayVariation[] = weekendDays.includes(date.getDay()) + ? ['greyed'] + : []; + + const greyedAndRowRelativeClasses = ['rowStart', 'rowEnd'].reduce((p, c) => { + switch (c) { + case 'rowStart': + return dayIndex === 0 ? [...p, c] : p; + case 'rowEnd': + return dayIndex === 6 ? [...p, c] : p; + default: + return p; + } + }, greyedClasses); + + if (!dateRange?.startDate || !dateRange?.endDate) { + return greyedAndRowRelativeClasses; + } + + const currentDate = startOfDay(date); + const startDate = startOfDay(dateRange.startDate); + const endDate = startOfDay(dateRange.endDate); + + if (!isWithinInterval(currentDate, { start: startDate, end: endDate })) { + return greyedAndRowRelativeClasses; + } + + const withSelectingOrSelected: DayVariation[] = [ + ...greyedAndRowRelativeClasses, + dateRange.selecting + ? 'selecting' + : 'selected', + ]; + + return ['rangeStart', 'rangeEnd'].reduce((p, c) => { + switch (c) { + case 'rangeStart': + return isSameDay(currentDate, startDate) ? [...p, c] : p; + case 'rangeEnd': + return isSameDay(currentDate, endDate) ? [...p, c] : p; + default: + return p; + } + }, withSelectingOrSelected); +}; \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..057caf8 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..e408291 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + css: { + modules: { + localsConvention: 'camelCase' + } + } +})