diff --git a/.eslintrc.js b/.eslintrc.js index 904e432..a672b47 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,12 @@ module.exports = { parser: '@typescript-eslint/parser', - extends: ['plugin:react-hooks/recommended','plugin:jsx-a11y/recommended', 'turbo', 'prettier'], - plugins: ['sort-imports-es6-autofix', '@typescript-eslint'], + extends: [ + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + 'turbo', + 'prettier', + ], + plugins: ['sort-imports-es6-autofix', '@typescript-eslint', 'unused-imports'], rules: { 'react/jsx-key': 'off', 'sort-imports-es6-autofix/sort-imports-es6': [ @@ -12,5 +17,6 @@ module.exports = { memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], }, ], + 'unused-imports/no-unused-imports': 'error', }, }; diff --git a/.husky/pre-push b/.husky/pre-push index 672198c..e8a402f 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -# npm run test \ No newline at end of file +npm run test \ No newline at end of file diff --git a/apps/schedulely-docs/components/HomePageSchedulely.tsx b/apps/schedulely-docs/components/HomePageSchedulely.tsx index 1b641a6..1e16d6b 100644 --- a/apps/schedulely-docs/components/HomePageSchedulely.tsx +++ b/apps/schedulely-docs/components/HomePageSchedulely.tsx @@ -1,7 +1,7 @@ import { Resizable } from 're-resizable'; import { Schedulely, WeekDay, createDefaultAdapter } from 'schedulely'; import { storyEvents } from './helpers.stories'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useTheme } from 'nextra-theme-docs'; const HomepageSchedulely = () => { diff --git a/package-lock.json b/package-lock.json index a51a8a7..a6a813f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "eslint-plugin-react": "^7.33.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", + "eslint-plugin-unused-imports": "^3.0.0", "husky": "^8.0.3", "prettier": "^3.0.0", "turbo": "^1.10.9", @@ -5884,6 +5885,36 @@ "eslint": ">6.6.0" } }, + "node_modules/eslint-plugin-unused-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz", + "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0", + "eslint": "^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", @@ -15156,7 +15187,7 @@ }, "packages/Schedulely": { "name": "schedulely", - "version": "0.3.2", + "version": "0.3.3", "license": "Apache-2.0", "devDependencies": { "@ladle/react": "^2.16.1", diff --git a/package.json b/package.json index 47aa93c..774799a 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "eslint-plugin-react": "^7.33.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", + "eslint-plugin-unused-imports": "^3.0.0", "husky": "^8.0.3", "prettier": "^3.0.0", "turbo": "^1.10.9", diff --git a/packages/Schedulely/__stories__/CalendarTester.tsx b/packages/Schedulely/__stories__/CalendarTester.tsx index 838f3f1..c985110 100644 --- a/packages/Schedulely/__stories__/CalendarTester.tsx +++ b/packages/Schedulely/__stories__/CalendarTester.tsx @@ -111,11 +111,11 @@ export const CalendarStoryTester = (props: PropsWithChildren) => { changeEvents(events); }} > - Add Event + Add Event To Initial Month
- +

diff --git a/packages/Schedulely/__tests__/hooks/useCalendar.spec.tsx b/packages/Schedulely/__tests__/hooks/useCalendar.spec.tsx index 1de5d79..bfde886 100644 --- a/packages/Schedulely/__tests__/hooks/useCalendar.spec.tsx +++ b/packages/Schedulely/__tests__/hooks/useCalendar.spec.tsx @@ -1,6 +1,7 @@ import { CalendarProvider } from '@/providers'; import { Chance } from 'chance'; -import { ComponentSize, DateTimeAdapter } from '@/types'; +import { ComponentSize } from '@/types'; +import { EventPriority } from '@/types/EventPriority'; import { ReactNode } from 'react'; import { act, renderHook } from '@testing-library/react'; import { createDefaultAdapter } from '@/dateAdapters/date'; @@ -117,6 +118,7 @@ const wrapper = ({ children }: { children: ReactNode }) => ( dateAdapter={testDateAdapter} initialDate={testDate.toISOString()} calendarEvents={[]} + eventPriority={EventPriority.long} > {children} diff --git a/packages/Schedulely/__tests__/layouts/EventWeekLayout.spec.tsx b/packages/Schedulely/__tests__/layouts/EventWeekLayout.spec.tsx index 546e4ee..c6c0b39 100644 --- a/packages/Schedulely/__tests__/layouts/EventWeekLayout.spec.tsx +++ b/packages/Schedulely/__tests__/layouts/EventWeekLayout.spec.tsx @@ -49,6 +49,7 @@ let mockIsHighlighted = vi.fn((eventId: string) => false); let mockEventOnClickHandler = vi.fn(() => {}); let mockSetParentContainerRef = vi.fn((eventId: string) => {}); +let mockGetEvent = vi.fn((eventId: string) => {}); let mockEventPriority = vi.fn(() => {}); vi.mock('@/hooks', () => ({ @@ -65,6 +66,7 @@ vi.mock('@/hooks', () => ({ })), useEventIntersection: vi.fn(() => ({ setParentContainerRef: mockSetParentContainerRef, + getEvent: mockGetEvent, })), useCalendar: vi.fn(() => ({ eventPriority: mockEventPriority, diff --git a/packages/Schedulely/package.json b/packages/Schedulely/package.json index b33cd41..1b36cf6 100644 --- a/packages/Schedulely/package.json +++ b/packages/Schedulely/package.json @@ -1,6 +1,6 @@ { "name": "schedulely", - "version": "0.3.3", + "version": "0.3.4", "keywords": [ "schedule", "calendar", diff --git a/packages/Schedulely/src/components/defaultHeader/DefaultHeader.tsx b/packages/Schedulely/src/components/defaultHeader/DefaultHeader.tsx index 576d389..49f6e67 100644 --- a/packages/Schedulely/src/components/defaultHeader/DefaultHeader.tsx +++ b/packages/Schedulely/src/components/defaultHeader/DefaultHeader.tsx @@ -1,5 +1,4 @@ import { HeaderComponent } from '@/types'; -import { useCallback } from 'react'; /** * The default header representation diff --git a/packages/Schedulely/src/layouts/eventWeekLayout/EventWeekLayout.tsx b/packages/Schedulely/src/layouts/eventWeekLayout/EventWeekLayout.tsx index 1d268a9..10d2676 100644 --- a/packages/Schedulely/src/layouts/eventWeekLayout/EventWeekLayout.tsx +++ b/packages/Schedulely/src/layouts/eventWeekLayout/EventWeekLayout.tsx @@ -60,7 +60,7 @@ export const EventWeekLayout = ({ const { eventComponent: EventComponent } = useComponents(); const { setHighlight, clearHighlight, isHighlighted } = useEventHighlight(); const { onEventClick } = useActions(); - const { setParentContainerRef } = useEventIntersection(); + const { setParentContainerRef, getEvent } = useEventIntersection(); const { eventPriority } = useCalendar(); const calculateOrder = (event: InternalCalendarEvent) => { @@ -81,7 +81,7 @@ export const EventWeekLayout = ({ data-eventid={event.id} style={{ gridColumn: getEventPosition(event, daysInweek), - visibility: 'hidden', // start hidden to avoid flashes of events that will be hidden + visibility: getEvent(event.id)?.visible ? undefined : 'hidden', // start hidden to avoid flashes of events that will be hidden order: calculateOrder(event), }} onMouseOver={() => setHighlight(event.id)} diff --git a/packages/Schedulely/src/layouts/monthLayout/MonthLayout.tsx b/packages/Schedulely/src/layouts/monthLayout/MonthLayout.tsx index 5372829..1dc59c3 100644 --- a/packages/Schedulely/src/layouts/monthLayout/MonthLayout.tsx +++ b/packages/Schedulely/src/layouts/monthLayout/MonthLayout.tsx @@ -20,7 +20,7 @@ export const MonthLayout = () => { className="week-container" data-week={idx} > - + diff --git a/packages/Schedulely/src/providers/ActionProvider.tsx b/packages/Schedulely/src/providers/ActionProvider.tsx index 55c1beb..bd15113 100644 --- a/packages/Schedulely/src/providers/ActionProvider.tsx +++ b/packages/Schedulely/src/providers/ActionProvider.tsx @@ -1,5 +1,5 @@ import { ActionContextState } from '@/types'; -import { PropsWithChildren, createContext, useCallback } from 'react'; +import { PropsWithChildren, createContext } from 'react'; export const ActionContext = createContext(null); ActionContext.displayName = 'ActionContext'; diff --git a/packages/Schedulely/src/providers/CalendarProvider.tsx b/packages/Schedulely/src/providers/CalendarProvider.tsx index 95a4cf2..7cf382d 100644 --- a/packages/Schedulely/src/providers/CalendarProvider.tsx +++ b/packages/Schedulely/src/providers/CalendarProvider.tsx @@ -116,12 +116,13 @@ export const CalendarProvider = ({ const calendarWithEvents = useMemo( () => - calendarView.map((week) => ({ + calendarView.map((week, weekNumber) => ({ daysInWeek: week, events: events .filter((event) => dateAdapter.isEventInWeek(event.start, event.end, week) ) + .map((x) => ({ ...x, weekNumber: weekNumber + 1 })) .sort( (x, y) => x.end.valueOf() - diff --git a/packages/Schedulely/src/providers/EventIntersectionProvider.tsx b/packages/Schedulely/src/providers/EventIntersectionProvider.tsx index ec5b42a..b36f6db 100644 --- a/packages/Schedulely/src/providers/EventIntersectionProvider.tsx +++ b/packages/Schedulely/src/providers/EventIntersectionProvider.tsx @@ -1,8 +1,4 @@ -import { - EventIntersectionState, - InternalCalendarEvent, - InternalEventWeek, -} from '@/types'; +import { EventIntersectionState, InternalCalendarEvent } from '@/types'; import { ReactNode, createContext, @@ -11,7 +7,7 @@ import { useRef, useState, } from 'react'; -import { useCalendar } from '@/hooks'; +import { useCalendar } from '../hooks/useCalendar'; export const EventIntersectionContext = createContext(null); @@ -26,9 +22,11 @@ EventIntersectionContext.displayName = 'EventIntersectionContext'; export const EventIntersectionProvider = ({ children, eventsInWeek, + weekNumber, }: { children: ReactNode; eventsInWeek: InternalCalendarEvent[]; + weekNumber: number; }) => { const { dateAdapter: { isDateBetween }, @@ -39,9 +37,19 @@ export const EventIntersectionProvider = ({ const observerRef = useRef(); + const getEventIntersectionId = useCallback( + (eventId: string) => [eventId, weekNumber].join('-'), + [weekNumber] + ); + const [eventVisibility, setEventVisibility] = useState< Record - >(Object.assign({}, ...eventsInWeek.map((x) => ({ [x.id]: x })))); + >( + Object.assign( + {}, + ...eventsInWeek.map((x) => ({ [getEventIntersectionId(x.id)]: x })) + ) + ); const getEventsOnDate = useCallback( (date: Date) => @@ -52,42 +60,34 @@ export const EventIntersectionProvider = ({ ); /** - * This method checks if an event is fully visible, and if not hides it - * We do this via direct Refs because direct updates are faster and cleaner than relying upon - * React to route the property before and after a render. - * - * This could possibly be done in a more React-y way by splitting this context, but this seems pretty straight-forward as it. + * this controls the event data that is sent back to the DayComponent for retrieving event visibility via getEventsOnDate */ const checkIntersection: IntersectionObserverCallback = useCallback( (entries) => - entries.map((x) => { - const currentStyle = - x.target - .getAttribute('style') - ?.split(';') - .filter((x) => x && !x.includes('visibility')) || []; - - if (x.isIntersecting) - x.target.setAttribute('style', currentStyle.join(';')); - else { - currentStyle.push('visibility: hidden'); - x.target.setAttribute('style', currentStyle.join(';')); - } - - // this controls the event data that is sent back to the DayComponent for event visibility + entries.map(({ target, isIntersecting }) => setEventVisibility((current) => { - var eventId = x.target.attributes.getNamedItem('data-eventid')?.value; - if (!eventId) return { ...current }; + var eventId = target.attributes.getNamedItem('data-eventid')?.value; + + if (!eventId) + throw new Error( + 'Event does not have a data-eventid attribute! Did you manually create it?' + ); if (!current[eventId]) { - const matchingEvent = eventsInWeek.find((x) => x.id === eventId)!; - current[eventId] = matchingEvent; + const matchingEvent = eventsInWeek.find(({ id }) => id === eventId); + + if (!matchingEvent) { + throw new Error( + `Event ${eventId} not found in event intersection dictionary!` + ); + } + current[getEventIntersectionId(eventId)] = matchingEvent; } - current[eventId].visible = x.isIntersecting; + current[getEventIntersectionId(eventId)].visible = isIntersecting; return { ...current }; - }); - }), - [eventsInWeek] + }) + ), + [eventsInWeek, getEventIntersectionId] ); useEffect(() => { @@ -113,6 +113,7 @@ export const EventIntersectionProvider = ({ const value: EventIntersectionState = { setParentContainerRef, getEventsOnDate, + getEvent: (id) => eventVisibility[getEventIntersectionId(id)], }; return ( diff --git a/packages/Schedulely/src/types/state/EventIntersectionContextState.ts b/packages/Schedulely/src/types/state/EventIntersectionContextState.ts index f7b65cc..04dbc29 100644 --- a/packages/Schedulely/src/types/state/EventIntersectionContextState.ts +++ b/packages/Schedulely/src/types/state/EventIntersectionContextState.ts @@ -8,4 +8,7 @@ export type EventIntersectionState = { /** Gets an array of events that occur on or span the supplied date. */ getEventsOnDate: (date: Date) => InternalCalendarEvent[]; + + /** Gets an array of events that occur on or span the supplied date. */ + getEvent: (eventId: string) => InternalCalendarEvent; };